优化完善支付应用和支付渠道代码逻辑,完善单元测试,基于validator完成手动校验config

This commit is contained in:
aquan
2021-11-21 19:31:20 +08:00
parent b18cd457c8
commit 6069a387ea
37 changed files with 1623 additions and 1021 deletions

View File

@@ -123,7 +123,7 @@ public class PayAppController {
// 得到所有的应用编号,查出所有的通道
Collection<Long> payAppIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getId);
List<PayChannelDO> channels = channelService.getSimpleChannels(payAppIds);
List<PayChannelDO> channels = channelService.getChannelListByAppIds(payAppIds);
// 得到所有的商户信息
Collection<Long> merchantIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getMerchantId);

View File

@@ -27,8 +27,8 @@ public class PayAppExportReqVO {
@ApiModelProperty(value = "退款结果的回调地址")
private String refundNotifyUrl;
@ApiModelProperty(value = "商户编号")
private Long merchantId;
@ApiModelProperty(value = "商户名称")
private String merchantName;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "开始创建时间")

View File

@@ -1,40 +1,35 @@
package cn.iocoder.yudao.adminserver.modules.pay.controller.channel;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.annotations.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert;
import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService;
import org.springframework.web.multipart.MultipartFile;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
/**
* 支付渠道 controller 组件
* @author aquan
*/
@Api(tags = "支付渠道")
@RestController
@RequestMapping("/pay/channel")
@@ -44,7 +39,7 @@ public class PayChannelController {
@Resource
private PayChannelService channelService;
// todo 芋艿 这几个生成的方法是没用到的 您看要不删除了把? -----start
@PostMapping("/create")
@ApiOperation("创建支付渠道 ")
@PreAuthorize("@ss.hasPermission('pay:channel:create')")
@@ -108,28 +103,7 @@ public class PayChannelController {
ExcelUtils.write(response, "支付渠道.xls", "数据", PayChannelExcelVO.class, datas);
}
// todo 芋艿 这几个生成的方法是没用到的 您看要不删除了把? -----end
@PostMapping("/parsing-pem")
@ApiOperation("解析pem证书转换为字符串")
@PreAuthorize("@ss.hasPermission('pay:channel:parsing')")
@ApiImplicitParam(name = "file", value = "pem文件", required = true, dataTypeClass = MultipartFile.class)
public CommonResult<String> parsingPemFile(@RequestParam("file") MultipartFile file) {
return success(channelService.parsingPemFile(file));
}
@PostMapping("/create-wechat")
@ApiOperation("创建支付渠道 ")
@PreAuthorize("@ss.hasPermission('pay:channel:create')")
public CommonResult<Long> createWechatChannel(@Valid @RequestBody PayWechatChannelCreateReqVO reqVO) {
// 针对于 V2 或者 V3 版本的参数校验
this.paramAdvanceCheck(reqVO.getWeChatConfig().getApiVersion(),reqVO.getWeChatConfig().getMchKey(),
reqVO.getWeChatConfig().getPrivateKeyContent(),reqVO.getWeChatConfig().getPrivateCertContent());
return success(channelService.createWechatChannel(reqVO));
}
@GetMapping("/get-wechat")
@GetMapping("/get-channel")
@ApiOperation("根据条件查询微信支付渠道")
@ApiImplicitParams({
@ApiImplicitParam(name = "merchantId", value = "商户编号",
@@ -140,51 +114,16 @@ public class PayChannelController {
required = true, example = "wx_pub", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('pay:channel:query')")
public CommonResult<PayWeChatChannelRespVO> getWeChatChannel(
public CommonResult<PayChannelRespVO> getChannel(
@RequestParam Long merchantId, @RequestParam Long appId, @RequestParam String code) {
// 獲取渠道
PayChannelDO channel = channelService.getChannelByConditions(merchantId, appId, code);
if (channel == null) {
return success(new PayWeChatChannelRespVO());
return success(new PayChannelRespVO());
}
// 拼凑数据
PayWeChatChannelRespVO respVo = PayChannelConvert.INSTANCE.convert2(channel);
WXPayClientConfig config = (WXPayClientConfig) channel.getConfig();
respVo.setWeChatConfig(PayChannelConvert.INSTANCE.configConvert(config));
PayChannelRespVO respVo = PayChannelConvert.INSTANCE.convert(channel);
return success(respVo);
}
@PutMapping("/update-wechat")
@ApiOperation("更新微信支付渠道 ")
@PreAuthorize("@ss.hasPermission('pay:channel:update')")
public CommonResult<Boolean> updateWechatChannel(@Valid @RequestBody PayWechatChannelUpdateReqVO updateReqVO) {
// 针对于 V2 或者 V3 版本的参数校验
this.paramAdvanceCheck(updateReqVO.getWeChatConfig().getApiVersion(),updateReqVO.getWeChatConfig().getMchKey(),
updateReqVO.getWeChatConfig().getPrivateKeyContent(),updateReqVO.getWeChatConfig().getPrivateCertContent());
channelService.updateWechatChannel(updateReqVO);
return success(true);
}
/**
* 预检测微信秘钥参数
* @param version 版本
* @param mchKey v2版本秘钥
* @param privateKeyContent v3版本apiclient_key
* @param privateCertContent v3版本中apiclient_cert
*/
private void paramAdvanceCheck(String version, String mchKey, String privateKeyContent, String privateCertContent) {
// 针对于 V2 或者 V3 版本的参数校验
if (version.equals(WXPayClientConfig.API_VERSION_V2)) {
Assert.notNull(mchKey, "v2版本中商户密钥不可为空");
}
if (version.equals(WXPayClientConfig.API_VERSION_V3)) {
Assert.notNull(privateKeyContent, "v3版本apiclient_key.pem不可为空");
Assert.notNull(privateCertContent, "v3版本中apiclient_cert.pem不可为空");
}
}
}

View File

@@ -1,18 +1,23 @@
package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
@ApiModel("支付渠道 创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayChannelCreateReqVO extends PayChannelBaseVO {
// TODO @aquan我在想要不这个创建和修改特殊一点。前端传递 string 过来,后端解析成对应的。因为有 code所以我们都知道是哪个配置类。
// 然后,在 PayChannelEnum 里,枚举每个渠道对应的配置类。另外,我们就不单独给配置类搞 vo 了。参数校验,通过手动调用 Validator 去校验。
// 通过这样的方式VO 和 api 都收成,一个 update一个 create
@ApiModelProperty(value = "通道配置的json字符串")
@NotBlank(message = "通道配置不能为空")
private String config;
}

View File

@@ -16,4 +16,6 @@ public class PayChannelRespVO extends PayChannelBaseVO {
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ApiModelProperty(value = "配置", required = true)
private String config;
}

View File

@@ -15,4 +15,7 @@ public class PayChannelUpdateReqVO extends PayChannelBaseVO {
@NotNull(message = "商户编号不能为空")
private Long id;
@ApiModelProperty(value = "通道配置的json字符串")
@NotBlank(message = "通道配置不能为空")
private String config;
}

View File

@@ -1,65 +0,0 @@
package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import java.util.Date;
@ApiModel("支付微信渠道 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayWeChatChannelRespVO extends PayChannelBaseVO {
@ApiModelProperty(value = "商户编号", required = true)
private Long id;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
/**
* 微信配置类
*/
@Valid
private WeChatConfig weChatConfig;
/**
* 微信配置类
*/
@Data
@ApiModel("微信配置类")
public static class WeChatConfig {
@ApiModelProperty(value = "公众号或者小程序的 appid", required = true, example = "wx041349c6f39b261b")
private String appId;
@ApiModelProperty(value = "商户号", required = true, example = "1545083881")
private String mchId;
@ApiModelProperty(value = "API 版本", required = true, example = "v2")
private String apiVersion;
// ========== V2 版本的参数 ==========
@ApiModelProperty(value = "商户密钥", required = true, example = "0alL64UDQdaCwiKZ73ib7ypaIjMns06p")
private String mchKey;
/// todo @aquan 暂不支持 .p12上传 后期优化
/// apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径. 对应的字符串
/// private String keyContent;
// ========== V3 版本的参数 ==========
@ApiModelProperty(value = "apiclient_key.pem 证书对应的字符串", required = true, example = "-----BEGIN PRIVATE KEY-----")
private String privateKeyContent;
@ApiModelProperty(value = "apiclient_cert.pem 证书对应的字符串", required = true, example = "-----BEGIN CERTIFICATE-----")
private String privateCertContent;
}
}

View File

@@ -1,68 +0,0 @@
package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 支付渠道微信创建Request VO
* @author aquan
*/
@ApiModel("支付渠道微信创建Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayWechatChannelCreateReqVO extends PayChannelBaseVO {
/**
* 微信配置类
*/
@Valid
private WeChatConfig weChatConfig;
/**
* 微信配置类
*/
@Data
@ApiModel("微信配置类")
public static class WeChatConfig {
@NotBlank(message = "公众号或者小程序的 appid不能为空")
@ApiModelProperty(value = "公众号或者小程序的 appid", required = true, example = "wx041349c6f39b261b")
private String appId;
@NotBlank(message = "商户号不能为空")
@ApiModelProperty(value = "商户号", required = true, example = "1545083881")
private String mchId;
@NotNull(message = "API 版本不能为空")
@ApiModelProperty(value = "API 版本", required = true, example = "v2")
private String apiVersion;
// ========== V2 版本的参数 ==========
@ApiModelProperty(value = "商户密钥", required = true, example = "0alL64UDQdaCwiKZ73ib7ypaIjMns06p")
private String mchKey;
/// todo @aquan 暂不支持 .p12上传 后期优化
/// apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径. 对应的字符串
/// private String keyContent;
// ========== V3 版本的参数 ==========
@ApiModelProperty(value = "apiclient_key.pem 证书对应的字符串", required = true, example = "-----BEGIN PRIVATE KEY-----")
private String privateKeyContent;
@ApiModelProperty(value = "apiclient_cert.pem 证书对应的字符串", required = true, example = "-----BEGIN CERTIFICATE-----")
private String privateCertContent;
}
}

View File

@@ -1,68 +0,0 @@
package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@ApiModel("支付渠道 更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayWechatChannelUpdateReqVO extends PayChannelBaseVO {
@ApiModelProperty(value = "商户编号", required = true)
@NotNull(message = "商户编号不能为空")
private Long id;
/**
* 微信配置类
*/
@Valid
private PayWechatChannelCreateReqVO.WeChatConfig weChatConfig;
/**
* 微信配置类
*/
@Data
@ApiModel("微信配置类")
public static class WeChatConfig {
@NotBlank(message = "公众号或者小程序的 appid不能为空")
@ApiModelProperty(value = "公众号或者小程序的 appid", required = true, example = "wx041349c6f39b261b")
private String appId;
@NotBlank(message = "商户号不能为空")
@ApiModelProperty(value = "商户号", required = true, example = "1545083881")
private String mchId;
@NotNull(message = "API 版本不能为空")
@ApiModelProperty(value = "API 版本", required = true, example = "v2")
private String apiVersion;
// ========== V2 版本的参数 ==========
@ApiModelProperty(value = "商户密钥", required = true, example = "0alL64UDQdaCwiKZ73ib7ypaIjMns06p")
private String mchKey;
/// todo @aquan 暂不支持 .p12上传 后期优化
/// apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径. 对应的字符串
/// private String keyContent;
// ========== V3 版本的参数 ==========
@ApiModelProperty(value = "apiclient_key.pem 证书对应的字符串", required = true, example = "-----BEGIN PRIVATE KEY-----")
private String privateKeyContent;
@ApiModelProperty(value = "apiclient_cert.pem 证书对应的字符串", required = true, example = "-----BEGIN CERTIFICATE-----")
private String privateCertContent;
// TODO @aquan参数校验。可以使用 @AssertTruev2 和 v3 的
}
}

View File

@@ -23,15 +23,12 @@ public interface PayChannelConvert {
PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class);
@Mapping(target = "config",ignore = true)
PayChannelDO convert(PayWechatChannelCreateReqVO bean);
@Mapping(target = "config",ignore = true)
PayChannelDO convert(PayWechatChannelUpdateReqVO bean);
PayChannelDO convert(PayChannelCreateReqVO bean);
@Mapping(target = "config",ignore = true)
PayChannelDO convert(PayChannelUpdateReqVO bean);
@Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))")
PayChannelRespVO convert(PayChannelDO bean);
List<PayChannelRespVO> convertList(List<PayChannelDO> list);
@@ -39,13 +36,7 @@ public interface PayChannelConvert {
PageResult<PayChannelRespVO> convertPage(PageResult<PayChannelDO> page);
List<PayChannelExcelVO> convertList02(List<PayChannelDO> list);
WXPayClientConfig configConvert(PayWechatChannelCreateReqVO.WeChatConfig bean);
WXPayClientConfig configConvert(PayWechatChannelUpdateReqVO.WeChatConfig bean);
@Mapping(target = "weChatConfig",ignore = true)
PayWeChatChannelRespVO convert2(PayChannelDO bean);
PayWeChatChannelRespVO.WeChatConfig configConvert(WXPayClientConfig bean);
}

View File

@@ -31,14 +31,14 @@ public interface PayAppMapper extends BaseMapperX<PayAppDO> {
.orderByDesc("id"));
}
default List<PayAppDO> selectList(PayAppExportReqVO reqVO) {
default List<PayAppDO> selectList(PayAppExportReqVO reqVO, Collection<Long> merchantIds) {
return selectList(new QueryWrapperX<PayAppDO>()
.likeIfPresent("name", reqVO.getName())
.eqIfPresent("status", reqVO.getStatus())
.eqIfPresent("remark", reqVO.getRemark())
.eqIfPresent("pay_notify_url", reqVO.getPayNotifyUrl())
.eqIfPresent("refund_notify_url", reqVO.getRefundNotifyUrl())
.eqIfPresent("merchant_id", reqVO.getMerchantId())
.inIfPresent("merchant_id", merchantIds)
.betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc("id"));
}

View File

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChann
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
@@ -26,7 +27,7 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
.eqIfPresent("fee_rate", reqVO.getFeeRate())
.eqIfPresent("merchant_id", reqVO.getMerchantId())
.eqIfPresent("app_id", reqVO.getAppId())
.eqIfPresent("config", reqVO.getConfig())
// .eqIfPresent("config", reqVO.getConfig())
.betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc("id") );
}
@@ -39,9 +40,52 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
.eqIfPresent("fee_rate", reqVO.getFeeRate())
.eqIfPresent("merchant_id", reqVO.getMerchantId())
.eqIfPresent("app_id", reqVO.getAppId())
.eqIfPresent("config", reqVO.getConfig())
// .eqIfPresent("config", reqVO.getConfig())
.betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc("id") );
}
/**
* 根据条件获取通道数量
*
* @param merchantId 商户编号
* @param appid 应用编号
* @param code 通道编码
* @return 数量
*/
default Integer getChannelCountByConditions(Long merchantId, Long appid, String code) {
return this.selectCount(new QueryWrapper<PayChannelDO>().lambda()
.eq(PayChannelDO::getMerchantId, merchantId)
.eq(PayChannelDO::getAppId, appid)
.eq(PayChannelDO::getCode, code)
);
}
/**
* 根据条件获取通道
*
* @param merchantId 商户编号
* @param appid 应用编号
* @param code 通道编码
* @return 数量
*/
default PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code) {
return this.selectOne((new QueryWrapper<PayChannelDO>().lambda()
.eq(PayChannelDO::getMerchantId, merchantId)
.eq(PayChannelDO::getAppId, appid)
.eq(PayChannelDO::getCode, code)
));
}
/**
* 根据支付应用ID集合获得支付渠道列表
*
* @param appIds 应用编号集合
* @return 支付渠道列表
*/
default List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds){
return this.selectList(new QueryWrapper<PayChannelDO>().lambda()
.in(PayChannelDO::getAppId, appIds));
}
}

View File

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerch
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.*;
@@ -39,4 +40,14 @@ public interface PayMerchantMapper extends BaseMapperX<PayMerchantDO> {
.orderByDesc("id"));
}
/**
* 根据商户名称模糊查询商户集合
*
* @param merchantName 商户名称
* @return 商户集合
*/
default List<PayMerchantDO> getMerchantListByName(String merchantName) {
return this.selectList(new QueryWrapper<PayMerchantDO>()
.lambda().likeRight(PayMerchantDO::getName, merchantName));
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.adminserver.modules.pay.service.app.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
@@ -7,6 +8,7 @@ import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqV
import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
import cn.iocoder.yudao.adminserver.modules.pay.convert.app.PayAppConvert;
import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper;
import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper;
import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService;
import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
@@ -18,10 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.APP_NOT_EXISTS;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -43,7 +42,7 @@ public class PayAppServiceImpl implements PayAppService {
* 商户 service 组件
*/
@Resource
private PayMerchantService merchantService;
private PayMerchantMapper merchantMapper;
@Override
public Long createApp(PayAppCreateReqVO createReqVO) {
@@ -89,13 +88,20 @@ public class PayAppServiceImpl implements PayAppService {
@Override
public PageResult<PayAppDO> getAppPage(PayAppPageReqVO pageReqVO) {
// TODO @aquan会有一个场景merchantName 匹配不到商户编号的时候,应该返回没数据的
return appMapper.selectPage(pageReqVO, this.getMerchantCondition(pageReqVO.getMerchantName()));
Set<Long> merchantIdList = this.getMerchantCondition(pageReqVO.getMerchantName());
if (StrUtil.isNotBlank(pageReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) {
return new PageResult<>();
}
return appMapper.selectPage(pageReqVO, merchantIdList);
}
@Override
public List<PayAppDO> getAppList(PayAppExportReqVO exportReqVO) {
return appMapper.selectList(exportReqVO);
Set<Long> merchantIdList = this.getMerchantCondition(exportReqVO.getMerchantName());
if (StrUtil.isNotBlank(exportReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) {
return new ArrayList<>();
}
return appMapper.selectList(exportReqVO, merchantIdList);
}
/**
@@ -108,7 +114,7 @@ public class PayAppServiceImpl implements PayAppService {
if (StrUtil.isBlank(merchantName)) {
return Collections.emptySet();
}
return convertSet(merchantService.getMerchantListByName(merchantName), PayMerchantDO::getId);
return convertSet(merchantMapper.getMerchantListByName(merchantName), PayMerchantDO::getId);
}
/**
@@ -131,6 +137,7 @@ public class PayAppServiceImpl implements PayAppService {
/**
* 检查商户是否存在
*
* @param id 商户编号
*/
@VisibleForTesting

View File

@@ -12,7 +12,7 @@ import java.util.List;
/**
* 支付渠道 Service 接口
*
* @author 芋艿 // TODO @aquan作者不要我
* @author aquan
*/
public interface PayChannelService {
@@ -76,31 +76,12 @@ public interface PayChannelService {
List<PayChannelDO> getChannelList(PayChannelExportReqVO exportReqVO);
/**
* 根据支付应用ID集合获取所有的支付渠道
* 根据支付应用ID集合获支付渠道列表
*
* @param payIds 支付应用编号集合
* @return 支付渠道
* @param appIds 应用编号集合
* @return 支付渠道列表
*/
// TODO @aquan暂时不用提供这种哈。之前提供的原因是数据字典比较特殊。
List<PayChannelDO> getSimpleChannels(Collection<Long> payIds);
/**
* 解析pem文件获取公钥私钥字符串
*
* @param file pem公私钥文件
* @return 解析后的字符串
*/
// TODO @aquan可以前端读取么
String parsingPemFile(MultipartFile file);
/**
* 创建微信的渠道配置
*
* @param reqVO 创建信息
* @return 创建结果
*/
// TODO @aquanpojo 如果要做参数校验,需要添加 @Valid
Long createWechatChannel(PayWechatChannelCreateReqVO reqVO);
List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds);
/**
* 根据条件获取通道数量
@@ -122,10 +103,5 @@ public interface PayChannelService {
*/
PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code);
/**
* 更新微信支付渠道
*
* @param updateReqVO 更新信息
*/
void updateWechatChannel(PayWechatChannelUpdateReqVO updateReqVO);
}

View File

@@ -1,23 +1,35 @@
package cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert;
import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper;
import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -25,7 +37,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
/**
* 支付渠道 Service 实现类
*
* @author 芋艿 // TODO aquan作者写自己哈
* @author aquan
*/
@Service
@Slf4j
@@ -36,11 +48,18 @@ public class PayChannelServiceImpl implements PayChannelService {
private PayChannelMapper channelMapper;
@Override
public Long createChannel(PayChannelCreateReqVO createReqVO) {
// 插入
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(createReqVO);
public Long createChannel(PayChannelCreateReqVO reqVO) {
// 判断是否有重复的有责无法新增
Integer channelCount = this.getChannelCountByConditions(reqVO.getMerchantId(), reqVO.getAppId(), reqVO.getCode());
if (channelCount > 0) {
throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR);
}
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO);
settingConfigAndCheckParam(channel, reqVO.getConfig());
channelMapper.insert(channel);
// 返回
return channel.getId();
}
@@ -49,8 +68,9 @@ public class PayChannelServiceImpl implements PayChannelService {
// 校验存在
this.validateChannelExists(updateReqVO.getId());
// 更新
PayChannelDO updateObj = PayChannelConvert.INSTANCE.convert(updateReqVO);
channelMapper.updateById(updateObj);
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO);
settingConfigAndCheckParam(channel, updateReqVO.getConfig());
channelMapper.updateById(channel);
}
@Override
@@ -88,53 +108,16 @@ public class PayChannelServiceImpl implements PayChannelService {
}
/**
* 根据支付应用ID集合获取所有的支付渠道
* 根据支付应用ID集合获支付渠道列表
*
* @param payIds 支付应用编号集合
* @return 支付渠道
* @param appIds 应用编号集合
* @return 支付渠道列表
*/
@Override
public List<PayChannelDO> getSimpleChannels(Collection<Long> payIds) {
return channelMapper.selectList(new QueryWrapper<PayChannelDO>().lambda().in(PayChannelDO::getAppId, payIds));
public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) {
return channelMapper.getChannelListByAppIds(appIds);
}
/**
* 解析pem文件获取公钥私钥字符串
*
* @param file pem公私钥文件
* @return 解析后的字符串
*/
@Override
public String parsingPemFile(MultipartFile file) {
try {
return IoUtil.readUtf8(file.getInputStream());
} catch (IOException e) {
log.error("[parsingPemToString]读取pem[{}]文件错误", file.getOriginalFilename());
throw exception(CHANNEL_KEY_READ_ERROR);
}
}
/**
* 创建微信的渠道配置
*
* @param reqVO 创建信息
* @return 创建结果
*/
@Override
public Long createWechatChannel(PayWechatChannelCreateReqVO reqVO) {
// 判断是否有重复的有责无法新增
Integer channelCount = this.getChannelCountByConditions(reqVO.getMerchantId(), reqVO.getAppId(), reqVO.getCode());
if (channelCount > 0) {
throw exception(EXIST_SAME_CHANNEL_ERROR);
}
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO);
WXPayClientConfig config = PayChannelConvert.INSTANCE.configConvert(reqVO.getWeChatConfig());
channel.setConfig(config);
channelMapper.insert(channel);
return channel.getId();
}
/**
* 根据条件获取通道数量
@@ -146,14 +129,9 @@ public class PayChannelServiceImpl implements PayChannelService {
*/
@Override
public Integer getChannelCountByConditions(Long merchantId, Long appid, String code) {
return this.channelMapper.selectCount(new QueryWrapper<PayChannelDO>().lambda()
.eq(PayChannelDO::getMerchantId, merchantId)
.eq(PayChannelDO::getAppId, appid)
.eq(PayChannelDO::getCode, code)
);
return this.channelMapper.getChannelCountByConditions(merchantId, appid, code);
}
// TODO @aquanservice 不出现 mybatis plus 哈
/**
* 根据条件获取通道
*
@@ -164,25 +142,76 @@ public class PayChannelServiceImpl implements PayChannelService {
*/
@Override
public PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code) {
return this.channelMapper.selectOne((new QueryWrapper<PayChannelDO>().lambda()
.eq(PayChannelDO::getMerchantId, merchantId)
.eq(PayChannelDO::getAppId, appid)
.eq(PayChannelDO::getCode, code)
));
return this.channelMapper.getChannelByConditions(merchantId, appid, code);
}
/**
* 更新微信支付渠道
* 检测微信秘钥参数
*
* @param updateReqVO 更新信息
* @param config 信秘钥参数
*/
@Override
public void updateWechatChannel(PayWechatChannelUpdateReqVO updateReqVO) {
// 校验存在
this.validateChannelExists(updateReqVO.getId());
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO);
WXPayClientConfig config = PayChannelConvert.INSTANCE.configConvert(updateReqVO.getWeChatConfig());
channel.setConfig(config);
this.channelMapper.updateById(channel);
private void wechatParamCheck(WXPayClientConfig config) {
// 针对于 V2 或者 V3 版本的参数校验
if (WXPayClientConfig.API_VERSION_V2.equals(config.getApiVersion())) {
Assert.notNull(config.getMchKey(), CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL.getMsg());
}
if (WXPayClientConfig.API_VERSION_V3.equals(config.getApiVersion())) {
Assert.notNull(config.getPrivateKeyContent(), CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL.getMsg());
Assert.notNull(config.getPrivateCertContent(), CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL.getMsg());
}
}
/**
* 设置渠道配置以及参数校验
*
* @param channel 渠道
* @param configStr 配置
*/
private void settingConfigAndCheckParam(PayChannelDO channel, String configStr) {
// 得到这个渠道是微信的还是支付宝的
String channelType = PayChannelEnum.verifyWechatOrAliPay(channel.getCode());
Assert.notNull(channelType, CHANNEL_NOT_EXISTS.getMsg());
// 进行验证
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
// 微信的验证
if (PayChannelEnum.WECHAT.equals(channelType)) {
WXPayClientConfig config = JSON.parseObject(configStr, WXPayClientConfig.class);
// 判断是V2 版本还是 V3 版本
Class clazz = config.getApiVersion().equals(WXPayClientConfig.API_VERSION_V2)
? WXPayClientConfig.V2.class : WXPayClientConfig.V3.class;
// 手动调用validate进行验证
Set<ConstraintViolation<WXPayClientConfig>> validate = validator.validate(config,clazz);
// 断言没有异常
Assert.isTrue(validate.isEmpty(), validate.stream().map(ConstraintViolation::getMessage)
.collect(Collectors.joining(",")));
channel.setConfig(config);
}
// 支付宝验证
if (PayChannelEnum.ALIPAY.equals(channelType)) {
AlipayPayClientConfig config = JSON.parseObject(configStr, AlipayPayClientConfig.class);
// 判断是V2 版本还是 V3 版本
Class clazz = config.getMode().equals(AlipayPayClientConfig.MODE_PUBLIC_KEY)
? AlipayPayClientConfig.ModePublicKey.class : AlipayPayClientConfig.ModeCertificate.class;
// 手动调用validate进行验证
Set<ConstraintViolation<AlipayPayClientConfig>> validate = validator.validate(config,clazz);
// 断言没有异常
Assert.isTrue(validate.isEmpty(), validate.stream().map(ConstraintViolation::getMessage)
.collect(Collectors.joining(",")));
channel.setConfig(config);
}
}
}

View File

@@ -100,15 +100,6 @@ public interface PayMerchantService {
*/
List<PayMerchantDO> getMerchantListByNameLimit(String merchantName);
/**
* 获得指定编号的商户列表
*
* @param merchantIds 商户编号数组
* @return 商户列表
*/
// TODO @aquan和 getMerchantList 重复了
List<PayMerchantDO> getSimpleMerchants(Collection<Long> merchantIds);
/**
* 获得指定编号的商户 Map
*
@@ -116,11 +107,7 @@ public interface PayMerchantService {
* @return 商户 Map
*/
default Map<Long, PayMerchantDO> getMerchantMap(Collection<Long> merchantIds) {
// TODO @aquan可以不用判空交给 getMerchantList 解决
if (CollUtil.isEmpty(merchantIds)) {
return Collections.emptyMap();
}
List<PayMerchantDO> list = getSimpleMerchants(merchantIds);
List<PayMerchantDO> list = this.getMerchantList(merchantIds);
return CollectionUtils.convertMap(list, PayMerchantDO::getId);
}

View File

@@ -113,9 +113,7 @@ public class PayMerchantServiceImpl implements PayMerchantService {
*/
@Override
public List<PayMerchantDO> getMerchantListByName(String merchantName) {
// TODO @aquanService 层,不要出现 mybatis plus 的代码,要放到 mapper 里提供。技术与业务分离,原则上
return this.merchantMapper.selectList(new QueryWrapper<PayMerchantDO>()
.lambda().likeRight(PayMerchantDO::getName, merchantName));
return this.merchantMapper.getMerchantListByName(merchantName);
}
/**
@@ -150,16 +148,6 @@ public class PayMerchantServiceImpl implements PayMerchantService {
}
}
/**
* 获得指定编号的商户列表
*
* @param merchantIds 商户编号数组
* @return 商户列表
*/
@Override
public List<PayMerchantDO> getSimpleMerchants(Collection<Long> merchantIds) {
return merchantMapper.selectBatchIds(merchantIds);
}
// TODO @芋艿:后续增加下合适的算法
/**