Jelajahi Sumber

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

aquan 3 tahun lalu
induk
melakukan
6069a387ea
37 mengubah file dengan 1619 tambahan dan 1017 penghapusan
  1. 16 0
      sql/pay-dict.sql
  2. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java
  3. 2 2
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java
  4. 27 88
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java
  5. 8 3
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java
  6. 2 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java
  7. 3 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java
  8. 0 65
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWeChatChannelRespVO.java
  9. 0 68
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWechatChannelCreateReqVO.java
  10. 0 68
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWechatChannelUpdateReqVO.java
  11. 2 11
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java
  12. 2 2
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java
  13. 46 2
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java
  14. 11 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java
  15. 16 9
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java
  16. 6 30
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java
  17. 100 71
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java
  18. 1 14
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java
  19. 1 13
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java
  20. 0 193
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/app/service/PayAppServiceTest.java
  21. 0 202
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/channel/PayChannelServiceTest.java
  22. 249 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java
  23. 404 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java
  24. 2 6
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java
  25. 2 0
      yudao-admin-server/src/test/resources/sql/clean.sql
  26. 34 0
      yudao-admin-server/src/test/resources/sql/create_tables.sql
  27. 8 25
      yudao-admin-ui/src/api/pay/channel.js
  28. 5 0
      yudao-admin-ui/src/utils/constants.js
  29. 6 1
      yudao-admin-ui/src/utils/dict.js
  30. 354 0
      yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue
  31. 88 54
      yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue
  32. 116 60
      yudao-admin-ui/src/views/pay/app/index.vue
  33. 19 13
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
  34. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
  35. 23 3
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
  36. 33 11
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java
  37. 30 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java

+ 16 - 0
sql/pay-dict.sql

@@ -22,3 +22,19 @@ INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `cre
 
 INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'v2', 'v2', 'pay_channel_wechat_version', 0, 'v2版本', '1', '2021-11-08 17:00:58', '1', '2021-11-08 17:00:58', b'0');
 INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'v3', 'v3', 'pay_channel_wechat_version', 0, 'v3版本', '1', '2021-11-08 17:01:07', '1', '2021-11-08 17:01:07', b'0');
+
+-- 支付渠道支付宝算法类型
+INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('支付渠道支付宝算法类型', 'pay_channel_alipay_sign_type', 0, '支付渠道支付宝算法类型', '1', '2021-11-18 15:39:09', '1', '2021-11-18 15:39:09', b'0');
+INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'RSA2', 'RSA2', 'pay_channel_alipay_sign_type', 0, 'RSA2', '1', '2021-11-18 15:39:29', '1', '2021-11-18 15:39:29', b'0');
+
+
+-- 支付渠道支付宝公钥类型
+INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('支付渠道支付宝公钥类型', 'pay_channel_alipay_mode', 0, '支付渠道支付宝公钥类型', '1', '2021-11-18 15:44:28', '1', '2021-11-18 15:44:28', b'0');
+INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '公钥模式', '1', 'pay_channel_alipay_mode', 0, '公钥模式:privateKey + alipayPublicKey', '1', '2021-11-18 15:45:23', '1', '2021-11-18 15:45:23', b'0');
+INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '证书模式', '2', 'pay_channel_alipay_mode', 0, '证书模式:appCertContent + alipayPublicCertContent + rootCertContent', '1', '2021-11-18 15:45:40', '1', '2021-11-18 15:45:40', b'0');
+
+
+-- 支付宝网关地址
+INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES ('支付宝网关地址', 'pay_channel_alipay_server_type', 0, '支付宝网关地址', '1', '2021-11-18 16:58:55', '1', '2021-11-18 17:01:34', b'0');
+INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '线上', 'https://openapi.alipay.com/gateway.do', 'pay_channel_alipay_server_type', 0, '网关地址 - 线上', '1', '2021-11-18 16:59:32', '1', '2021-11-21 17:37:29', b'0');
+INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, '沙箱', 'https://openapi.alipaydev.com/gateway.do', 'pay_channel_alipay_server_type', 0, '网关地址 - 沙箱', '1', '2021-11-18 16:59:48', '1', '2021-11-21 17:37:39', b'0');

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java

@@ -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);

+ 2 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java

@@ -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 = "开始创建时间")

+ 27 - 88
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java

@@ -1,40 +1,35 @@
 package cn.iocoder.yudao.adminserver.modules.pay.controller.channel;
 
+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 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 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 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 javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
 import java.io.IOException;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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;
 
-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;
-
+/**
+ * 支付渠道 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不可为空");
-        }
-    }
-
 }

+ 8 - 3
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java

@@ -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;
+
 
 }

+ 2 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java

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

+ 3 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java

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

+ 0 - 65
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWeChatChannelRespVO.java

@@ -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;
-    }
-
-}

+ 0 - 68
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWechatChannelCreateReqVO.java

@@ -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;
-    }
-
-}

+ 0 - 68
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayWechatChannelUpdateReqVO.java

@@ -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:参数校验。可以使用 @AssertTrue,v2 和 v3 的
-    }
-}

+ 2 - 11
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java

@@ -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);
 }

+ 2 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java

@@ -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"));
     }

+ 46 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java

@@ -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));
+    }
 }

+ 11 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java

@@ -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));
+    }
 }

+ 16 - 9
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java

@@ -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

+ 6 - 30
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java

@@ -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 支付渠道
-     */
-    // TODO @aquan:暂时不用提供这种哈。之前提供的原因,是数据字典比较特殊。
-    List<PayChannelDO> getSimpleChannels(Collection<Long> payIds);
-
-    /**
-     * 解析pem文件获取公钥私钥字符串
-     *
-     * @param file pem公私钥文件
-     * @return 解析后的字符串
-     */
-    // TODO @aquan:可以前端读取么?
-    String parsingPemFile(MultipartFile file);
-
-    /**
-     * 创建微信的渠道配置
-     *
-     * @param reqVO 创建信息
-     * @return 创建结果
+     * @param appIds 应用编号集合
+     * @return 支付渠道列表
      */
-    // TODO @aquan:pojo 如果要做参数校验,需要添加 @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);
+
 }

+ 100 - 71
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java

@@ -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集合获取所有的支付渠道
-     *
-     * @param payIds 支付应用编号集合
-     * @return 支付渠道
-     */
-    @Override
-    public List<PayChannelDO> getSimpleChannels(Collection<Long> payIds) {
-        return channelMapper.selectList(new QueryWrapper<PayChannelDO>().lambda().in(PayChannelDO::getAppId, payIds));
-    }
-
-    /**
-     * 解析pem文件获取公钥私钥字符串
+     * 根据支付应用ID集合获得支付渠道列表
      *
-     * @param file pem公私钥文件
-     * @return 解析后的字符串
+     * @param appIds 应用编号集合
+     * @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);
-        }
+    public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) {
+        return channelMapper.getChannelListByAppIds(appIds);
     }
 
-    /**
-     * 创建微信的渠道配置
-     *
-     * @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 @aquan:service 不出现 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);
+        }
+
     }
 }

+ 1 - 14
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java

@@ -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);
     }
 

+ 1 - 13
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java

@@ -113,9 +113,7 @@ public class PayMerchantServiceImpl implements PayMerchantService {
      */
     @Override
     public List<PayMerchantDO> getMerchantListByName(String merchantName) {
-        // TODO @aquan:Service 层,不要出现 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 @芋艿:后续增加下合适的算法
     /**

+ 0 - 193
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/app/service/PayAppServiceTest.java

@@ -1,193 +0,0 @@
-package cn.iocoder.yudao.adminserver.modules.pay.app.service;
-
-import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper;
-import cn.iocoder.yudao.adminserver.modules.pay.service.app.impl.PayAppServiceImpl;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import org.junit.jupiter.api.Test;
-import org.springframework.context.annotation.Import;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.APP_NOT_EXISTS;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
-* {@link PayAppServiceImpl} 的单元测试类
-*
-* @author 芋艿
-*/
-@Import(PayAppServiceImpl.class)
-public class PayAppServiceTest extends BaseDbUnitTest {
-
-    @Resource
-    private PayAppServiceImpl appService;
-
-    @Resource
-    private PayAppMapper appMapper;
-
-    @Test
-    public void testCreateApp_success() {
-        // 准备参数
-        PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class);
-
-        // 调用
-        Long appId = appService.createApp(reqVO);
-        // 断言
-        assertNotNull(appId);
-        // 校验记录的属性是否正确
-        PayAppDO app = appMapper.selectById(appId);
-        assertPojoEquals(reqVO, app);
-    }
-
-    @Test
-    public void testUpdateApp_success() {
-        // mock 数据
-        PayAppDO dbApp = randomPojo(PayAppDO.class);
-        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> {
-            o.setId(dbApp.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        appService.updateApp(reqVO);
-        // 校验是否更新正确
-        PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的
-        assertPojoEquals(reqVO, app);
-    }
-
-    @Test
-    public void testUpdateApp_notExists() {
-        // 准备参数
-        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteApp_success() {
-        // mock 数据
-        PayAppDO dbApp = randomPojo(PayAppDO.class);
-        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbApp.getId();
-
-        // 调用
-        appService.deleteApp(id);
-       // 校验数据不存在了
-       assertNull(appMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteApp_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> appService.deleteApp(id), APP_NOT_EXISTS);
-    }
-
-    @Test // TODO 请修改 null 为需要的值
-    public void testGetAppPage() {
-       // mock 数据
-       PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
-           o.setName(null);
-           o.setStatus(null);
-           o.setRemark(null);
-           o.setPayNotifyUrl(null);
-           o.setRefundNotifyUrl(null);
-           o.setMerchantId(null);
-           o.setCreateTime(null);
-       });
-       appMapper.insert(dbApp);
-       // 测试 name 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setName(null)));
-       // 测试 status 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setStatus(null)));
-       // 测试 remark 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRemark(null)));
-       // 测试 payNotifyUrl 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setPayNotifyUrl(null)));
-       // 测试 refundNotifyUrl 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRefundNotifyUrl(null)));
-       // 测试 merchantId 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setMerchantId(null)));
-       // 测试 createTime 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setCreateTime(null)));
-       // 准备参数
-       PayAppPageReqVO reqVO = new PayAppPageReqVO();
-       reqVO.setName(null);
-       reqVO.setStatus(null);
-       reqVO.setRemark(null);
-       reqVO.setPayNotifyUrl(null);
-       reqVO.setRefundNotifyUrl(null);
-       reqVO.setBeginCreateTime(null);
-       reqVO.setEndCreateTime(null);
-
-       // 调用
-       PageResult<PayAppDO> pageResult = appService.getAppPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbApp, pageResult.getList().get(0));
-    }
-
-    @Test // TODO aquan:请修改 null 为需要的值
-    public void testGetAppList() {
-       // mock 数据
-       PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
-           o.setName(null);
-           o.setStatus(null);
-           o.setRemark(null);
-           o.setPayNotifyUrl(null);
-           o.setRefundNotifyUrl(null);
-           o.setMerchantId(null);
-           o.setCreateTime(null);
-       });
-       appMapper.insert(dbApp);
-       // 测试 name 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setName(null)));
-       // 测试 status 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setStatus(null)));
-       // 测试 remark 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRemark(null)));
-       // 测试 payNotifyUrl 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setPayNotifyUrl(null)));
-       // 测试 refundNotifyUrl 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRefundNotifyUrl(null)));
-       // 测试 merchantId 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setMerchantId(null)));
-       // 测试 createTime 不匹配
-       appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setCreateTime(null)));
-       // 准备参数
-       PayAppExportReqVO reqVO = new PayAppExportReqVO();
-       reqVO.setName(null);
-       reqVO.setStatus(null);
-       reqVO.setRemark(null);
-       reqVO.setPayNotifyUrl(null);
-       reqVO.setRefundNotifyUrl(null);
-       reqVO.setMerchantId(null);
-       reqVO.setBeginCreateTime(null);
-       reqVO.setEndCreateTime(null);
-
-       // 调用
-       List<PayAppDO> list = appService.getAppList(reqVO);
-       // 断言
-       assertEquals(1, list.size());
-       assertPojoEquals(dbApp, list.get(0));
-    }
-
-}

+ 0 - 202
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/channel/PayChannelServiceTest.java

@@ -1,202 +0,0 @@
-package cn.iocoder.yudao.adminserver.modules.pay.channel;
-
-import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO;
-import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper;
-import cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl.PayChannelServiceImpl;
-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.object.ObjectUtils;
-import org.junit.jupiter.api.Test;
-import org.springframework.context.annotation.Import;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
-* {@link PayChannelServiceImpl} 的单元测试类
-*
-* @author 芋艿
-*/
-@Import(PayChannelServiceImpl.class)
-public class PayChannelServiceTest extends BaseDbUnitTest {
-
-    @Resource
-    private PayChannelServiceImpl channelService;
-
-    @Resource
-    private PayChannelMapper channelMapper;
-
-    @Test
-    public void testCreateChannel_success() {
-        // 准备参数
-        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class);
-
-        // 调用
-        Long channelId = channelService.createChannel(reqVO);
-        // 断言
-        assertNotNull(channelId);
-        // 校验记录的属性是否正确
-        PayChannelDO channel = channelMapper.selectById(channelId);
-        assertPojoEquals(reqVO, channel);
-    }
-
-    @Test
-    public void testUpdateChannel_success() {
-        // mock 数据
-        PayChannelDO dbChannel = randomPojo(PayChannelDO.class);
-        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
-            o.setId(dbChannel.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        channelService.updateChannel(reqVO);
-        // 校验是否更新正确
-        PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
-        assertPojoEquals(reqVO, channel);
-    }
-
-    @Test
-    public void testUpdateChannel_notExists() {
-        // 准备参数
-        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteChannel_success() {
-        // mock 数据
-        PayChannelDO dbChannel = randomPojo(PayChannelDO.class);
-        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbChannel.getId();
-
-        // 调用
-        channelService.deleteChannel(id);
-       // 校验数据不存在了
-       assertNull(channelMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteChannel_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS);
-    }
-
-    @Test // TODO 请修改 null 为需要的值
-    public void testGetChannelPage() {
-       // mock 数据
-       PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
-           o.setCode(null);
-           o.setStatus(null);
-           o.setRemark(null);
-           o.setFeeRate(null);
-           o.setMerchantId(null);
-           o.setAppId(null);
-           o.setConfig(null);
-           o.setCreateTime(null);
-       });
-       channelMapper.insert(dbChannel);
-       // 测试 code 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setCode(null)));
-       // 测试 status 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setStatus(null)));
-       // 测试 remark 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setRemark(null)));
-       // 测试 feeRate 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setFeeRate(null)));
-       // 测试 merchantId 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setMerchantId(null)));
-       // 测试 appId 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setAppId(null)));
-       // 测试 config 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setConfig(null)));
-       // 测试 createTime 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setCreateTime(null)));
-       // 准备参数
-       PayChannelPageReqVO reqVO = new PayChannelPageReqVO();
-       reqVO.setCode(null);
-       reqVO.setStatus(null);
-       reqVO.setRemark(null);
-       reqVO.setFeeRate(null);
-       reqVO.setMerchantId(null);
-       reqVO.setAppId(null);
-       reqVO.setConfig(null);
-       reqVO.setBeginCreateTime(null);
-       reqVO.setEndCreateTime(null);
-
-       // 调用
-       PageResult<PayChannelDO> pageResult = channelService.getChannelPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbChannel, pageResult.getList().get(0));
-    }
-
-    @Test // TODO aquan:请修改 null 为需要的值
-    public void testGetChannelList() {
-       // mock 数据
-       PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
-           o.setCode(null);
-           o.setStatus(null);
-           o.setRemark(null);
-           o.setFeeRate(null);
-           o.setMerchantId(null);
-           o.setAppId(null);
-           o.setConfig(null);
-           o.setCreateTime(null);
-       });
-       channelMapper.insert(dbChannel);
-       // 测试 code 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setCode(null)));
-       // 测试 status 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setStatus(null)));
-       // 测试 remark 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setRemark(null)));
-       // 测试 feeRate 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setFeeRate(null)));
-       // 测试 merchantId 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setMerchantId(null)));
-       // 测试 appId 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setAppId(null)));
-       // 测试 config 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setConfig(null)));
-       // 测试 createTime 不匹配
-       channelMapper.insert(ObjectUtils.clone(dbChannel, o -> o.setCreateTime(null)));
-       // 准备参数
-       PayChannelExportReqVO reqVO = new PayChannelExportReqVO();
-       reqVO.setCode(null);
-       reqVO.setStatus(null);
-       reqVO.setRemark(null);
-       reqVO.setFeeRate(null);
-       reqVO.setMerchantId(null);
-       reqVO.setAppId(null);
-       reqVO.setConfig(null);
-       reqVO.setBeginCreateTime(null);
-       reqVO.setEndCreateTime(null);
-
-       // 调用
-       List<PayChannelDO> list = channelService.getChannelList(reqVO);
-       // 断言
-       assertEquals(1, list.size());
-       assertPojoEquals(dbChannel, list.get(0));
-    }
-
-}

+ 249 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java

@@ -0,0 +1,249 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.app;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
+
+
+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.impl.PayAppServiceImpl;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link PayAppServiceImpl} 的单元测试类
+ *
+ * @author 芋艿
+ */
+@Import(PayAppServiceImpl.class)
+public class PayAppServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayAppServiceImpl appService;
+
+    @Resource
+    private PayAppMapper appMapper;
+
+    @MockBean(name = "payMerchantMapper")
+    private PayMerchantMapper payMerchantMapper;
+
+    @Test
+    public void testCreateApp_success() {
+        // 准备参数
+        PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+
+        // 调用
+        Long appId = appService.createApp(reqVO);
+        // 断言
+        assertNotNull(appId);
+        // 校验记录的属性是否正确
+        PayAppDO app = appMapper.selectById(appId);
+        assertPojoEquals(reqVO, app);
+    }
+
+    @Test
+    public void testUpdateApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
+                o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> {
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setId(dbApp.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        appService.updateApp(reqVO);
+        // 校验是否更新正确
+        PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, app);
+    }
+
+    @Test
+    public void testUpdateApp_notExists() {
+        // 准备参数
+        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+
+        // 调用
+        appService.deleteApp(id);
+        // 校验数据不存在了
+        assertNull(appMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteApp_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.deleteApp(id), APP_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGetAppPage() {
+        Long merchantId = 1L;
+        Long mismatchMerchantId = 2L;
+
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
+            o.setName("灿灿姐的杂货铺");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("敏敏姐的小卖铺");
+            o.setPayNotifyUrl("https://www.hc.com");
+            o.setRefundNotifyUrl("https://www.xm.com");
+            o.setMerchantId(merchantId);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+            o.setId(merchantId);
+            o.setNo("M1008611");
+            o.setName("灿哥的杂货铺");
+            o.setShortName("灿灿子");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿哥的杂货铺");
+            o.setCreateTime(buildTime(2021,11,3));
+        });
+
+        Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName()))
+                .thenReturn(Collections.singletonList(dbMerchant));
+
+        appMapper.insert(dbApp);
+        // 测试 name 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setName("敏敏姐的杂货铺")));
+        // 测试 status 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 remark 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRemark("灿灿姐的小卖部")));
+        // 测试 payNotifyUrl 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setPayNotifyUrl("xm.com")));
+        // 测试 refundNotifyUrl 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRefundNotifyUrl("hc.com")));
+        // 测试 merchantId 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setMerchantId(mismatchMerchantId)));
+        // 测试 createTime 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setCreateTime(buildTime(2021,12,21))));
+        // 准备参数
+        PayAppPageReqVO reqVO = new PayAppPageReqVO();
+        reqVO.setName("灿灿姐的杂货铺");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("敏敏姐的小卖铺");
+        reqVO.setPayNotifyUrl("https://www.hc.com");
+        reqVO.setRefundNotifyUrl("https://www.xm.com");
+        reqVO.setMerchantName(dbMerchant.getName());
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        PageResult<PayAppDO> pageResult = appService.getAppPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbApp, pageResult.getList().get(0));
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGetAppList() {
+        Long merchantId = 1L;
+        Long mismatchMerchantId = 2L;
+
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
+            o.setName("灿灿姐的杂货铺");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("敏敏姐的小卖铺");
+            o.setPayNotifyUrl("https://www.hc.com");
+            o.setRefundNotifyUrl("https://www.xm.com");
+            o.setMerchantId(merchantId);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+            o.setId(merchantId);
+            o.setNo("M1008611");
+            o.setName("灿哥的杂货铺");
+            o.setShortName("灿灿子");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿哥的杂货铺");
+            o.setCreateTime(buildTime(2021,11,3));
+        });
+
+        Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName()))
+                .thenReturn(Collections.singletonList(dbMerchant));
+
+        appMapper.insert(dbApp);
+        // 测试 name 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setName("敏敏姐的杂货铺")));
+        // 测试 status 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 remark 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRemark("灿灿姐的小卖部")));
+        // 测试 payNotifyUrl 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setPayNotifyUrl("xm.com")));
+        // 测试 refundNotifyUrl 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setRefundNotifyUrl("hc.com")));
+        // 测试 merchantId 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setMerchantId(mismatchMerchantId)));
+        // 测试 createTime 不匹配
+        appMapper.insert(ObjectUtils.clone(dbApp, o -> o.setCreateTime(buildTime(2021,12,21))));
+        // 准备参数
+        PayAppExportReqVO reqVO = new PayAppExportReqVO();
+        reqVO.setName("灿灿姐的杂货铺");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("敏敏姐的小卖铺");
+        reqVO.setPayNotifyUrl("https://www.hc.com");
+        reqVO.setRefundNotifyUrl("https://www.xm.com");
+        reqVO.setMerchantName(dbMerchant.getName());
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        List<PayAppDO> list = appService.getAppList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbApp, list.get(0));
+    }
+
+}

+ 404 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java

@@ -0,0 +1,404 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.channel;
+
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl.PayChannelServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.alibaba.fastjson.JSON;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import javax.validation.Validator;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link PayChannelServiceImpl} 的单元测试类
+ *
+ * @author 芋艿
+ */
+@Import(PayChannelServiceImpl.class)
+public class PayChannelServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayChannelServiceImpl channelService;
+
+    @Resource
+    private PayChannelMapper channelMapper;
+
+
+    @Test
+    public void testCreateWechatVersion2Channel_success() {
+        // 准备参数
+
+        WXPayClientConfig v2Config = getV2Config();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(v2Config));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(v2Config, channel.getConfig());
+
+    }
+
+    @Test
+    public void testCreateWechatVersion3Channel_success() {
+        // 准备参数
+
+        WXPayClientConfig v3Config = getV3Config();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(v3Config));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(v3Config, channel.getConfig());
+    }
+
+    @Test
+    public void testCreateAliPayPublicKeyChannel_success() {
+        // 准备参数
+
+        AlipayPayClientConfig payClientConfig = getPublicKeyConfig();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientConfig));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(payClientConfig, channel.getConfig());
+
+    }
+
+    @Test
+    public void testCreateAliPayCertificateChannel_success() {
+        // 准备参数
+
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientConfig));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(payClientConfig, channel.getConfig());
+    }
+
+    @Test
+    public void testUpdateChannel_success() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(payClientConfig);
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
+            o.setCode(dbChannel.getCode());
+            o.setStatus(dbChannel.getStatus());
+            o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
+            o.setId(dbChannel.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        channelService.updateChannel(reqVO);
+        // 校验是否更新正确
+        PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, channel, "config");
+        assertPojoEquals(payClientPublicKeyConfig, channel.getConfig());
+    }
+
+    @Test
+    public void testUpdateChannel_notExists() {
+        // 准备参数
+        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
+        });
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteChannel_success() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(payClientConfig);
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbChannel.getId();
+
+        // 调用
+        channelService.deleteChannel(id);
+        // 校验数据不存在了
+        assertNull(channelMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteChannel_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS);
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGetChannelPage() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿灿子的支付渠道");
+            o.setFeeRate(0.03);
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+        channelMapper.insert(dbChannel);
+        // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的
+        dbChannel.setConfig(null);
+        // 测试 code 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+        }));
+        // 测试 status 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        }));
+        // 测试 remark 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o ->{
+            o.setConfig(payClientConfig);
+            o.setRemark("敏敏子的渠道");
+        }));
+        // 测试 feeRate 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setFeeRate(1.23);
+        }));
+        // 测试 merchantId 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setMerchantId(2L);
+        }));
+        // 测试 appId 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setAppId(2L);
+        }));
+        // 测试 createTime 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021, 10, 20));
+        }));
+        // 准备参数
+        PayChannelPageReqVO reqVO = new PayChannelPageReqVO();
+        reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("灿灿子的支付渠道");
+        reqVO.setFeeRate(0.03);
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setConfig(JSON.toJSONString(payClientConfig));
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        PageResult<PayChannelDO> pageResult = channelService.getChannelPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbChannel, pageResult.getList().get(0), "config");
+        assertPojoEquals(payClientConfig, pageResult.getList().get(0).getConfig());
+
+    }
+
+    @Test
+    public void testGetChannelList() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿灿子的支付渠道");
+            o.setFeeRate(0.03);
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+        channelMapper.insert(dbChannel);
+        // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的
+        dbChannel.setConfig(null);
+        // 测试 code 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+        }));
+        // 测试 status 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        }));
+        // 测试 remark 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o ->{
+            o.setConfig(payClientConfig);
+            o.setRemark("敏敏子的渠道");
+        }));
+        // 测试 feeRate 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setFeeRate(1.23);
+        }));
+        // 测试 merchantId 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setMerchantId(2L);
+        }));
+        // 测试 appId 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setAppId(2L);
+        }));
+        // 测试 createTime 不匹配
+        channelMapper.insert(ObjectUtils.clone(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021, 10, 20));
+        }));
+        // 准备参数
+        PayChannelExportReqVO reqVO = new PayChannelExportReqVO();
+        reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("灿灿子的支付渠道");
+        reqVO.setFeeRate(0.03);
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setConfig(JSON.toJSONString(payClientConfig));
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        List<PayChannelDO> list = channelService.getChannelList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbChannel, list.get(0), "config");
+        assertPojoEquals(payClientConfig, list.get(0).getConfig());
+    }
+
+
+    public WXPayClientConfig getV2Config() {
+        return new WXPayClientConfig()
+                .setAppId("APP00001")
+                .setMchId("MCH00001")
+                .setApiVersion(WXPayClientConfig.API_VERSION_V2)
+                .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6")
+                .setApiV3Key("")
+                .setPrivateCertContent("")
+                .setPrivateKeyContent("");
+    }
+
+    public WXPayClientConfig getV3Config() {
+        return new WXPayClientConfig()
+                .setAppId("APP00001")
+                .setMchId("MCH00001")
+                .setApiVersion(WXPayClientConfig.API_VERSION_V3)
+                .setMchKey("")
+                .setApiV3Key("sdadasdsadadsa")
+                .setPrivateKeyContent("dsa445das415d15asd16ad156as")
+                .setPrivateCertContent("dsadasd45asd4s5a");
+
+    }
+
+    public AlipayPayClientConfig getPublicKeyConfig() {
+        return new AlipayPayClientConfig()
+                .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD)
+                .setAppId("APP00001")
+                .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
+                .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY)
+                .setPrivateKey("13131321312")
+                .setAlipayPublicKey("13321321321")
+                .setAppCertContent("")
+                .setAlipayPublicCertContent("")
+                .setRootCertContent("");
+    }
+
+    public AlipayPayClientConfig getCertificateConfig() {
+        return new AlipayPayClientConfig()
+                .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD)
+                .setAppId("APP00001")
+                .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
+                .setMode(AlipayPayClientConfig.MODE_CERTIFICATE)
+                .setPrivateKey("")
+                .setAlipayPublicKey("")
+                .setAppCertContent("13321321321sda")
+                .setAlipayPublicCertContent("13321321321aqeqw")
+                .setRootCertContent("13321321321dsad");
+    }
+
+
+}

+ 2 - 6
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/merchant/service/PayMerchantServiceTest.java → yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java

@@ -1,9 +1,6 @@
-package cn.iocoder.yudao.adminserver.modules.pay.merchant.service;
+package cn.iocoder.yudao.adminserver.modules.pay.service.merchant;
 
 import cn.hutool.core.util.RandomUtil;
-import cn.hutool.http.HttpUtil;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
 import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO;
 import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO;
@@ -54,8 +51,7 @@ public class PayMerchantServiceTest extends BaseDbUnitTest {
         assertNotNull(merchantId);
         // 校验记录的属性是否正确
         PayMerchantDO merchant = merchantMapper.selectById(merchantId);
-        // TODO @aquan:需要判断 no 非空
-        assertPojoEquals(reqVO, merchant,"no");
+        assertPojoEquals(reqVO, merchant);
     }
 
     @Test

+ 2 - 0
yudao-admin-server/src/test/resources/sql/clean.sql

@@ -27,3 +27,5 @@ DELETE FROM "sys_social_user";
 
 -- pay 开头的 DB
 DELETE FROM pay_merchant;
+DELETE FROM pay_app;
+DELETE FROM pay_channel

+ 34 - 0
yudao-admin-server/src/test/resources/sql/create_tables.sql

@@ -464,3 +464,37 @@ CREATE TABLE IF NOT EXISTS "pay_merchant" (
     "deleted" bit(1) NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
 )  COMMENT '支付商户信息';
+
+CREATE TABLE IF NOT EXISTS "pay_app"  (
+     "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+     "name" varchar(64) NOT NULL,
+     "status" tinyint NOT NULL,
+     "remark" varchar(255) DEFAULT NULL,
+     `pay_notify_url` varchar(1024) NOT NULL,
+     `refund_notify_url`varchar(1024) NOT NULL,
+     `merchant_id`bigint(20)    NOT NULL,
+     "creator" varchar(64) DEFAULT '',
+     "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+     "updater" varchar(64) DEFAULT '',
+     "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+     "deleted" bit(1) NOT NULL DEFAULT FALSE,
+     PRIMARY KEY ("id")
+)  COMMENT = '支付应用信息';
+
+CREATE TABLE "pay_channel"  (
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "code" varchar(32)  NOT NULL ,
+    "status" tinyint(4) NOT NULL ,
+    "remark" varchar(255)  DEFAULT NULL ,
+    "fee_rate" double NOT NULL DEFAULT 0 ,
+    "merchant_id" bigint(20) NOT NULL ,
+    "app_id" bigint(20) NOT NULL ,
+    "config" varchar(10240)  NOT NULL ,
+    "creator" varchar(64)  NULL DEFAULT '' ,
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+    "updater" varchar(64)  NULL DEFAULT '' ,
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit(1) NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+)COMMENT = '支付渠道';
+

+ 8 - 25
yudao-admin-ui/src/api/pay/channel.js

@@ -28,12 +28,12 @@ export function deleteChannel(id) {
 }
 
 // 获得支付渠道
-export function getChannel(id) {
-  return request({
-    url: '/pay/channel/get?id=' + id,
-    method: 'get'
-  })
-}
+// export function getChannel(id) {
+//   return request({
+//     url: '/pay/channel/get?id=' + id,
+//     method: 'get'
+//   })
+// }
 
 
 
@@ -56,19 +56,10 @@ export function exportChannelExcel(query) {
   })
 }
 
-// 创建微信支付渠道
-export function createWechatChannel(data) {
-  return request({
-    url: '/pay/channel/create-wechat',
-    method: 'post',
-    data: data
-  })
-}
-
 // 获得支付渠道
-export function getWechatChannel(merchantId,appId,code) {
+export function getChannel(merchantId,appId,code) {
   return request({
-    url: '/pay/channel/get-wechat',
+    url: '/pay/channel/get-channel',
     params:{
       merchantId:merchantId,
       appId:appId,
@@ -78,11 +69,3 @@ export function getWechatChannel(merchantId,appId,code) {
   })
 }
 
-// 更新支付渠道
-export function updateWechatChannel(data) {
-  return request({
-    url: '/pay/channel/update-wechat',
-    method: 'put',
-    data: data
-  })
-}

+ 5 - 0
yudao-admin-ui/src/utils/constants.js

@@ -121,3 +121,8 @@ export const PayChannelEnum = {
     "name": "支付宝扫码支付"
   },
 }
+
+export const PayType = {
+  WECHAT: "WECHAT",
+  ALIPAY: "ALIPAY"
+}

+ 6 - 1
yudao-admin-ui/src/utils/dict.js

@@ -39,7 +39,12 @@ export const DICT_TYPE = {
   PAY_CHANNEL_STATUS: 'pay_channel_status',
   // 微信渠道版本
   PAY_CHANNEL_WECHAT_VERSION:'pay_channel_wechat_version',
-
+  // 支付渠道支付宝算法类型
+  PAY_CHANNEL_ALIPAY_SIGN_TYPE:'pay_channel_alipay_sign_type',
+  // 支付宝公钥类型
+  PAY_CHANNEL_ALIPAY_MODE:'pay_channel_alipay_mode',
+  // 支付宝网关地址
+  PAY_CHANNEL_ALIPAY_SERVER_TYPE:'pay_channel_alipay_server_type',
 }
 
 /**

+ 354 - 0
yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue

@@ -0,0 +1,354 @@
+<template>
+  <div>
+    <el-dialog :visible.sync="transferParam.aliPayOpen" @closed="close" append-to-body width="800px">
+      <el-form ref="aliPayForm" :model="form" :rules="rules" size="medium" label-width="100px"
+               v-loading="transferParam.loading">
+        <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
+          <el-input v-model="form.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
+            <template slot="append">%</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="开放平台APPID" prop="aliPayConfig.appId">
+          <el-input v-model="form.aliPayConfig.appId" placeholder="请输入开放平台APPID" clearable :style="{width: '100%'}">
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="渠道状态" prop="status">
+          <el-radio-group v-model="form.status" size="medium">
+            <el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="网关地址" prop="aliPayConfig.serverUrl">
+          <el-radio-group v-model="form.aliPayConfig.serverUrl" size="medium">
+            <el-radio v-for="dict in aliPayServerDatas" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="算法类型" prop="aliPayConfig.signType">
+          <el-radio-group v-model="form.aliPayConfig.signType" size="medium">
+            <el-radio v-for="dict in aliPaySignTypeDatas" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="公钥类型" prop="aliPayConfig.mode">
+          <el-radio-group v-model="form.aliPayConfig.mode" size="medium">
+            <el-radio v-for="dict in aliPayModeDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <div v-if="form.aliPayConfig.mode === 1">
+          <el-form-item label-width="180px" label="商户私钥" prop="aliPayConfig.privateKey">
+            <el-input type="textarea" :autosize="{minRows: 8, maxRows: 8}" v-model="form.aliPayConfig.privateKey"
+                      placeholder="请输入商户私钥" clearable :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="支付宝公钥字符串" prop="aliPayConfig.alipayPublicKey">
+            <el-input
+              type="textarea"
+              :autosize="{minRows: 8, maxRows: 8}"
+              v-model="form.aliPayConfig.alipayPublicKey"
+              placeholder="请输入支付宝公钥字符串" clearable
+              :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+        </div>
+        <div v-if="form.aliPayConfig.mode === 2">
+          <el-form-item label-width="180px" label="商户公钥应用证书" prop="aliPayConfig.appCertContent">
+            <el-input v-model="form.aliPayConfig.appCertContent" type="textarea"
+                      placeholder="请上传商户公钥应用证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              action=""
+              ref="privateKeyContentFile"
+              :limit="1"
+              :accept="fileAccept"
+              :http-request="appCertUpload"
+              :before-upload="fileBeforeUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label-width="180px" label="支付宝公钥证书" prop="aliPayConfig.alipayPublicCertContent">
+            <el-input v-model="form.aliPayConfig.alipayPublicCertContent" type="textarea"
+                      placeholder="请上传支付宝公钥证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              ref="privateCertContentFile"
+              action=""
+              :limit="1"
+              :accept="fileAccept"
+              :before-upload="fileBeforeUpload"
+              :http-request="alipayPublicCertUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label-width="180px" label="根证书" prop="aliPayConfig.rootCertContent">
+            <el-input
+              v-model="form.aliPayConfig.rootCertContent"
+              type="textarea"
+              placeholder="请上传根证书"
+              readonly :autosize="{minRows: 8, maxRows: 8}"
+              :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              ref="privateCertContentFile"
+              :limit="1"
+              :accept="fileAccept"
+              action=""
+              :before-upload="fileBeforeUpload"
+              :http-request="rootCertUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+        </div>
+        <el-form-item label-width="180px" label="备注" prop="remark">
+          <el-input v-model="form.remark" :style="{width: '100%'}"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
+import {createChannel, getChannel, updateChannel} from "@/api/pay/channel";
+
+const defaultForm = {
+  code: '',
+  status: null,
+  remark: '',
+  feeRate: null,
+  appId: '',
+  merchantId: null,
+  aliPayConfig: {
+    appId: '',
+    serverUrl: null,
+    signType: '',
+    mode: null,
+    privateKey: '',
+    alipayPublicKey: '',
+    appCertContent: '',
+    alipayPublicCertContent: '',
+    rootCertContent: ''
+  }
+};
+
+export default {
+  name: "aliPayChannelForm",
+  components: {},
+  props: {
+    // 传输的参数
+    transferParam: {
+      // 加载动画
+      "loading": false,
+      // 是否修改
+      "edit": false,
+      // 是否显示
+      "aliPayOpen": false,
+      // 应用ID
+      "appId": null,
+      // 渠道编码
+      "payCode": null,
+      // 商户对象
+      "payMerchant": {
+        // 编号
+        "id": null,
+        // 名称
+        "name": null
+      },
+    }
+  },
+  data() {
+    return {
+      form: JSON.parse(JSON.stringify(defaultForm)),
+      rules: {
+        feeRate: [{
+          required: true,
+          message: '请输入渠道费率',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.appId': [{
+          required: true,
+          message: '请输入开放平台上创建的应用的 ID',
+          trigger: 'blur'
+        }],
+        status: [{
+          required: true,
+          message: '渠道状态不能为空',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.serverUrl': [{
+          required: true,
+          message: '请传入网关地址',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.signType': [{
+          required: true,
+          message: '请传入签名算法类型',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.mode': [{
+          required: true,
+          message: '公钥类型不能为空',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.privateKey': [{
+          required: true,
+          message: '请输入商户私钥',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.alipayPublicKey': [{
+          required: true,
+          message: '请输入支付宝公钥字符串',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.appCertContent': [{
+          required: true,
+          message: '请上传商户公钥应用证书',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.alipayPublicCertContent': [{
+          required: true,
+          message: '请上传支付宝公钥证书',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.rootCertContent': [{
+          required: true,
+          message: '请上传指定根证书',
+          trigger: 'blur'
+        }],
+      },
+      fileAccept: ".crt",
+      // 渠道状态 数据字典
+      statusDictDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_STATUS),
+      // 支付宝加密方式
+      aliPaySignTypeDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_SIGN_TYPE),
+      // 版本状态 数据字典
+      aliPayModeDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_MODE),
+      // 支付宝网关地址
+      aliPayServerDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_SERVER_TYPE),
+    }
+  },
+  watch: {
+    transferParam: {
+      deep: true,  // 深度监听
+      handler(newVal) {
+        if (newVal.aliPayOpen) {
+          this.form.code = newVal.payCode;
+          this.form.appId = newVal.appId;
+          this.form.merchantId = newVal.payMerchant.id;
+          // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
+          if (newVal.edit === true && newVal.loading) {
+            this.init();
+          }
+        }
+      }
+    }
+  },
+
+  methods: {
+    init() {
+      getChannel(this.transferParam.payMerchant.id, this.transferParam.appId, this.transferParam.payCode)
+        .then(response => {
+          this.form.id = response.data.id;
+          this.form.feeRate = response.data.feeRate;
+          this.form.status = response.data.status;
+          this.form.remark = response.data.remark;
+
+          let config = JSON.parse(response.data.config);
+          this.form.aliPayConfig.appId = config.appId;
+          this.form.aliPayConfig.serverUrl = config.serverUrl;
+          this.form.aliPayConfig.signType = config.signType;
+          this.form.aliPayConfig.mode = config.mode;
+          this.form.aliPayConfig.privateKey = config.privateKey;
+          this.form.aliPayConfig.alipayPublicKey = config.alipayPublicKey;
+          this.form.aliPayConfig.appCertContent = config.appCertContent;
+          this.form.aliPayConfig.alipayPublicCertContent = config.alipayPublicCertContent;
+          this.form.aliPayConfig.rootCertContent = config.rootCertContent;
+          this.transferParam.loading = false;
+        })
+    },
+    close() {
+      this.transferParam.aliPayOpen = false;
+      this.form = JSON.parse(JSON.stringify(defaultForm));
+    },
+    handleConfirm() {
+      this.$refs['aliPayForm'].validate(valid => {
+        if (!valid) {
+          return
+        }
+        let data = this.form;
+        data.config = JSON.stringify(this.form.aliPayConfig);
+        if (this.transferParam.edit) {
+          updateChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("修改成功");
+              this.close();
+            }
+
+          })
+        } else {
+
+          createChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("新增成功");
+              this.$parent.refreshTable();
+              this.close();
+            }
+          });
+        }
+      });
+    },
+    fileBeforeUpload(file) {
+      let format = '.' + file.name.split(".")[1];
+      if (format !== this.fileAccept) {
+        this.$message.error('请上传指定格式"' + this.fileAccept + '"文件');
+        return false;
+      }
+      let isRightSize = file.size / 1024 / 1024 < 2
+      if (!isRightSize) {
+        this.$message.error('文件大小超过 2MB')
+      }
+      return isRightSize
+    },
+    appCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.appCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    alipayPublicCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.alipayPublicCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    rootCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.rootCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+
+  }
+}
+
+</script>
+<style scoped>
+
+</style>

+ 88 - 54
yudao-admin-ui/src/views/pay/app/components/wechatJsApiForm.vue → yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <el-dialog :visible.sync="transferParam.open" @close="close" append-to-body>
+    <el-dialog :visible.sync="transferParam.wechatOpen" @close="close" append-to-body width="800px">
       <el-form ref="wechatJsApiForm" :model="form" :rules="rules" size="medium" label-width="100px"
                v-loading="transferParam.loading">
         <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
@@ -22,7 +22,7 @@
             </el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label-width="180px" label="API  版本" prop="weChatConfig.apiVersion">
+        <el-form-item label-width="180px" label="API 版本" prop="weChatConfig.apiVersion">
           <el-radio-group v-model="form.weChatConfig.apiVersion" size="medium">
             <el-radio v-for="dict in versionDictDatas" :key="dict.value" :label="dict.value">
               {{ dict.label }}
@@ -32,9 +32,13 @@
         <el-form-item label-width="180px" label="商户秘钥" prop="weChatConfig.mchKey"
                       v-if="form.weChatConfig.apiVersion === 'v2'">
           <el-input v-model="form.weChatConfig.mchKey" placeholder="请输入商户秘钥" clearable
-                    :style="{width: '100%'}"></el-input>
+                    :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
         </el-form-item>
         <div v-if="form.weChatConfig.apiVersion === 'v3'">
+          <el-form-item label-width="180px" label="API V3秘钥" prop="weChatConfig.apiV3Key">
+            <el-input v-model="form.weChatConfig.apiV3Key" placeholder="请输入API V3秘钥" clearable
+                      :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
+          </el-form-item>
           <el-form-item label-width="180px" label="apiclient_key.perm证书" prop="weChatConfig.privateKeyContent">
             <el-input v-model="form.weChatConfig.privateKeyContent" type="textarea"
                       placeholder="请上传apiclient_key.perm证书"
@@ -45,9 +49,9 @@
                        :limit="1"
                        :accept="fileAccept"
                        :headers="header"
-                       :action="pemUploadAction"
+                       action=""
                        :before-upload="pemFileBeforeUpload"
-                       :on-success="privateKeyUploadSuccess"
+                       :http-request="privateKeyUpload"
             >
               <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
             </el-upload>
@@ -62,9 +66,9 @@
                        :limit="1"
                        :accept="fileAccept"
                        :headers="header"
-                       :action="pemUploadAction"
+                       action=""
                        :before-upload="pemFileBeforeUpload"
-                       :on-success="privateCertUploadSuccess"
+                       :http-request="privateCertUpload"
             >
               <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
             </el-upload>
@@ -83,10 +87,28 @@
 </template>
 <script>
 import {DICT_TYPE, getDictDatas} from "@/utils/dict";
-import {createWechatChannel, getWechatChannel, updateWechatChannel} from "@/api/pay/channel";
+import {createChannel, getChannel, updateChannel} from "@/api/pay/channel";
+
+const defaultForm = {
+  code: '',
+  status: null,
+  remark: '',
+  feeRate: null,
+  appId: '',
+  merchantId: null,
+  weChatConfig: {
+    appId: '',
+    mchId: '',
+    apiVersion: '',
+    mchKey: '',
+    privateKeyContent: '',
+    privateCertContent: '',
+    apiV3Key:'',
+  }
+}
 
 export default {
-  name: "wechatJsApiForm",
+  name: "wechatChannelForm",
   components: {},
   props: {
     // 传输的参数
@@ -96,7 +118,7 @@ export default {
       // 是否修改
       "edit": false,
       // 是否显示
-      "open": false,
+      "wechatOpen": false,
       // 应用ID
       "appId": null,
       // 渠道编码
@@ -112,22 +134,7 @@ export default {
   },
   data() {
     return {
-      form: {
-        code: undefined,
-        status: undefined,
-        remark: undefined,
-        feeRate: undefined,
-        appId: undefined,
-        merchantId: undefined,
-        weChatConfig: {
-          appId: undefined,
-          mchId: undefined,
-          apiVersion: undefined,
-          mchKey: undefined,
-          privateKeyContent: undefined,
-          privateCertContent: undefined,
-        }
-      },
+      form: JSON.parse(JSON.stringify(defaultForm)),
       rules: {
         feeRate: [{
           required: true,
@@ -147,12 +154,12 @@ export default {
         status: [{
           required: true,
           message: '渠道状态不能为空',
-          trigger: 'change'
+          trigger: 'blur'
         }],
         'weChatConfig.apiVersion': [{
           required: true,
           message: 'API版本不能为空',
-          trigger: 'change'
+          trigger: 'blur'
         }],
         'weChatConfig.mchKey': [{
           required: true,
@@ -169,12 +176,16 @@ export default {
           message: '请上传apiclient_cert.perm证书',
           trigger: 'blur'
         }],
+        'weChatConfig.apiV3Key': [{
+          required: true,
+          message: '请上传apiV3秘钥值',
+          trigger: 'blur'
+        }],
       },
       // 文件上传的header
       header: {
         "Authorization": null
       },
-      pemUploadAction: 'http://127.0.0.1:48080/api/pay/channel/parsing-pem',
       fileAccept: ".pem",
       // 渠道状态 数据字典
       statusDictDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_STATUS),
@@ -185,51 +196,66 @@ export default {
     transferParam: {
       deep: true,  // 深度监听
       handler(newVal) {
-        this.form.code = newVal.payCode;
-        this.form.appId = newVal.appId;
-        this.form.merchantId = newVal.payMerchant.id;
-        // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
-        if (newVal.edit === true && newVal.loading) {
-          this.init();
+        if (newVal.wechatOpen) {
+          this.form.code = newVal.payCode;
+          this.form.appId = newVal.appId;
+          this.form.merchantId = newVal.payMerchant.id;
+          // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
+          if (newVal.edit && newVal.loading) {
+            this.init();
+          }
         }
       }
     }
   },
-  created() {
-    this.header.Authorization = "Bearer " + this.$store.getters.token;
-  },
   methods: {
     init() {
-      getWechatChannel(this.transferParam.payMerchant.id, this.transferParam.appId, this.transferParam.payCode)
+      getChannel(this.transferParam.payMerchant.id, this.transferParam.appId, this.transferParam.payCode)
         .then(response => {
-          this.form = response.data;
+          this.form.id = response.data.id;
+          this.form.feeRate = response.data.feeRate;
+          this.form.appId = response.data.appId;
+          this.form.status = response.data.status;
+          this.form.remark = response.data.remark;
+
+          let config = JSON.parse(response.data.config);
+          this.form.weChatConfig.appId = config.appId;
+          this.form.weChatConfig.apiVersion = config.apiVersion;
+          this.form.weChatConfig.mchId = config.mchId;
+          this.form.weChatConfig.mchKey = config.mchKey;
+          this.form.weChatConfig.privateKeyContent = config.privateKeyContent;
+          this.form.weChatConfig.privateCertContent = config.privateCertContent;
+          this.form.weChatConfig.apiV3Key = config.apiV3Key;
           this.transferParam.loading = false;
         })
     },
     close() {
-      this.transferParam.open = false;
-      this.$refs['wechatJsApiForm'].resetFields();
+      this.transferParam.wechatOpen = false;
+      this.form = JSON.parse(JSON.stringify(defaultForm));
     },
     handleConfirm() {
       this.$refs['wechatJsApiForm'].validate(valid => {
         if (!valid) {
           return
         }
+        let data = this.form;
+        data.config = JSON.stringify(this.form.weChatConfig);
         if (this.transferParam.edit) {
-          updateWechatChannel(this.form).then(response => {
-            if (response.code === 0 ) {
+          updateChannel(data).then(response => {
+            if (response.code === 0) {
               this.msgSuccess("修改成功");
               this.close();
             }
 
           })
         } else {
-          createWechatChannel(this.form).then(response => {
-           if (response.code === 0) {
-             this.msgSuccess("新增成功");
-             this.$parent.refreshTable();
-             this.close();
-           }
+
+          createChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("新增成功");
+              this.$parent.refreshTable();
+              this.close();
+            }
           });
         }
       });
@@ -246,11 +272,19 @@ export default {
       }
       return isRightSize
     },
-    privateKeyUploadSuccess(response) {
-      this.form.weChatConfig.privateKeyContent = response.data;
+    privateKeyUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.weChatConfig.privateKeyContent = e.target.result
+      }
+      readFile.readAsText(event.file);
     },
-    privateCertUploadSuccess(response) {
-      this.form.weChatConfig.privateCertContent = response.data;
+    privateCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.weChatConfig.privateCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
     }
   }
 }

+ 116 - 60
yudao-admin-ui/src/views/pay/app/index.vue

@@ -57,68 +57,104 @@
       <el-table-column label="支付宝配置" align="center">
         <el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       v-if="scope.row.payChannel.alipayApp === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       v-if="scope.row.payChannel.alipayApp === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayApp === sysCommonStatusEnum.ENABLE">
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayApp === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
         <el-table-column :label="payChannelEnum.ALIPAY_PC.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       v-if="scope.row.payChannel.alipayPc === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       v-if="scope.row.payChannel.alipayPc === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_PC.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayPc === sysCommonStatusEnum.ENABLE">
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_PC.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayPc === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
         <el-table-column :label="payChannelEnum.ALIPAY_WAP.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       v-if="scope.row.payChannel.alipayWap === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       v-if="scope.row.payChannel.alipayWap === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_WAP.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayWap === sysCommonStatusEnum.ENABLE">
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_WAP.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayWap === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
         <el-table-column :label="payChannelEnum.ALIPAY_QR.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       v-if="scope.row.payChannel.alipayQr === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       v-if="scope.row.payChannel.alipayQr === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_QR.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayQr === sysCommonStatusEnum.ENABLE">
+
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_QR.code,payType.ALIPAY)"
+              v-if="scope.row.payChannel.alipayQr === sysCommonStatusEnum.DISABLE">
+
+            </el-button>
           </template>
         </el-table-column>
       </el-table-column>
       <el-table-column label="微信配置" align="center">
         <el-table-column :label="payChannelEnum.WX_LITE.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       @click="handleUpdateChannel(scope.row,payChannelEnum.WX_LITE.code)"
-                       v-if="scope.row.payChannel.wxLite === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_LITE.code)"
-                       v-if="scope.row.payChannel.wxLite === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.WX_LITE.code,payType.WECHAT)"
+              v-if="scope.row.payChannel.wxLite === sysCommonStatusEnum.ENABLE">
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.WX_LITE.code,payType.WECHAT)"
+              v-if="scope.row.payChannel.wxLite === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
         <el-table-column :label="payChannelEnum.WX_PUB.name" align="center">
           <template slot-scope="scope">
             <el-button
               type="success" icon="el-icon-check" circle
-              @click="handleUpdateChannel(scope.row,payChannelEnum.WX_PUB.code)"
+              @click="handleUpdateChannel(scope.row,payChannelEnum.WX_PUB.code,payType.WECHAT)"
               v-if="scope.row.payChannel.wxPub === sysCommonStatusEnum.ENABLE">
             </el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_PUB.code)"
-                       v-if="scope.row.payChannel.wxPub === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.WX_PUB.code,payType.WECHAT)"
+              v-if="scope.row.payChannel.wxPub === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
         <el-table-column :label="payChannelEnum.WX_APP.name" align="center">
           <template slot-scope="scope">
-            <el-button type="success" icon="el-icon-check" circle
-                       @click="handleUpdateChannel(scope.row,payChannelEnum.WX_APP.code)"
-                       v-if="scope.row.payChannel.wxApp === sysCommonStatusEnum.ENABLE"></el-button>
-            <el-button type="danger" icon="el-icon-close" circle
-                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_APP.code)"
-                       v-if="scope.row.payChannel.wxApp === sysCommonStatusEnum.DISABLE"></el-button>
+            <el-button
+              type="success" icon="el-icon-check" circle
+              @click="handleUpdateChannel(scope.row,payChannelEnum.WX_APP.code,payType.WECHAT)"
+              v-if="scope.row.payChannel.wxApp === sysCommonStatusEnum.ENABLE">
+            </el-button>
+            <el-button
+              type="danger" icon="el-icon-close" circle
+              @click="handleCreateChannel(scope.row,payChannelEnum.WX_APP.code,payType.WECHAT)"
+              v-if="scope.row.payChannel.wxApp === sysCommonStatusEnum.DISABLE">
+            </el-button>
           </template>
         </el-table-column>
       </el-table-column>
@@ -186,21 +222,24 @@
       </div>
     </el-dialog>
 
-    <wechat-js-api-form :transferParam="wechatChannelParam"></wechat-js-api-form>
+    <wechat-channel-form :transferParam="channelParam"></wechat-channel-form>
+    <ali-pay-channel-form :transferParam="channelParam"></ali-pay-channel-form>
   </div>
 </template>
 
 <script>
 import {createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage, exportAppExcel} from "@/api/pay/app";
 import {DICT_TYPE, getDictDatas} from "@/utils/dict";
-import {PayChannelEnum, SysCommonStatusEnum} from "@/utils/constants";
+import {PayType, PayChannelEnum, SysCommonStatusEnum} from "@/utils/constants";
 import {getMerchantListByName} from "@/api/pay/merchant";
-import wechatJsApiForm from "@/views/pay/app/components/wechatJsApiForm";
+import wechatChannelForm from "@/views/pay/app/components/wechatChannelForm";
+import aliPayChannelForm from "@/views/pay/app/components/aliPayChannelForm";
 
 export default {
   name: "App",
   components: {
-    "wechatJsApiForm": wechatJsApiForm
+    "wechatChannelForm": wechatChannelForm,
+    "aliPayChannelForm": aliPayChannelForm
   },
   data() {
     return {
@@ -243,16 +282,20 @@ export default {
       sysCommonStatusEnum: SysCommonStatusEnum,
       // 支付渠道枚举
       payChannelEnum: PayChannelEnum,
+      // 支付类型
+      payType: PayType,
       // 商户列表
       merchantList: [],
       // 是否显示支付窗口
       payOpen: false,
       // 微信组件传参参数
-      wechatChannelParam: {
+      channelParam: {
         // 是否修改
-        "edit":false,
-        // 是否显示
-        "open":false,
+        "edit": false,
+        // 微信是否显示
+        "wechatOpen": false,
+        // 支付宝是否显示
+        "aliPayOpen": false,
         // 应用ID
         "appId": null,
         // 渠道编码
@@ -279,7 +322,6 @@ export default {
       this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
       // 执行查询
       getAppPage(params).then(response => {
-        console.log(response.data);
         this.list = response.data.list;
         this.total = response.data.total;
         this.loading = false;
@@ -355,9 +397,9 @@ export default {
         // 修改的提交
         if (this.form.id != null) {
           updateApp(this.form).then(response => {
-             this.msgSuccess("修改成功");
-             this.open = false;
-             this.getList();
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
           });
           return;
         }
@@ -407,33 +449,47 @@ export default {
      */
     handleGetMerchantListByName(name) {
       getMerchantListByName(name).then(response => {
-        console.log(response)
         this.merchantList = response.data;
       });
     },
     /**
      * 修改支付渠道信息
      */
-    handleUpdateChannel(row, payCode) {
-      this.wechatChannelParam.edit = true;
-      this.wechatChannelParam.loading = true;
-      this.wechatChannelParam.appId = row.id;
-      this.wechatChannelParam.payCode = payCode;
-      this.wechatChannelParam.payMerchant = row.payMerchant;
-      this.wechatChannelParam.open = true;
+    handleUpdateChannel(row, payCode, type) {
+      this.settingChannelParam(row, payCode, type)
+      this.channelParam.edit = true;
+      this.channelParam.loading = true;
+
     },
     /**
      * 新增支付渠道信息
      */
-    handleCreateChannel(row, payCode) {
-      this.wechatChannelParam.edit = false;
-      this.wechatChannelParam.loading = false;
-      this.wechatChannelParam.appId = row.id;
-      this.wechatChannelParam.payCode = payCode;
-      this.wechatChannelParam.payMerchant = row.payMerchant;
-      this.wechatChannelParam.open = true;
+    handleCreateChannel(row, payCode, type) {
+      this.settingChannelParam(row, payCode, type)
+      this.channelParam.edit = false;
+      this.channelParam.loading = false;
+
+
+    },
+    /**
+     * 设置支付渠道信息
+     */
+    settingChannelParam(row, payCode, type) {
+      if (type === PayType.WECHAT) {
+        this.channelParam.wechatOpen = true;
+        this.channelParam.aliPayOpen = false;
+      }
+      if (type === PayType.ALIPAY) {
+        this.channelParam.aliPayOpen = true;
+        this.channelParam.wechatOpen = false;
+      }
+      this.channelParam.edit = false;
+      this.channelParam.loading = false;
+      this.channelParam.appId = row.id;
+      this.channelParam.payCode = payCode;
+      this.channelParam.payMerchant = row.payMerchant;
     },
-    refreshTable(){
+    refreshTable() {
       this.getList();
     }
   }

+ 19 - 13
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java

@@ -9,21 +9,35 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  */
 public interface PayErrorCodeCoreConstants {
 
-    // ========== APP 模块 1-007-000-000 ==========
+    /**
+     * ========== APP 模块 1-007-000-000 ==========
+     */
     ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
     ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
 
-    // ========== CHANNEL 模块 1-007-001-000 ==========
+    /**
+     * ========== CHANNEL 模块 1-007-001-000 ==========
+     */
     ErrorCode PAY_CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
     ErrorCode PAY_CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
     ErrorCode PAY_CHANNEL_CLIENT_NOT_FOUND = new ErrorCode(1007001002, "支付渠道的客户端不存在");
-
-    // ========== ORDER 模块 1-007-002-000 ==========
+    ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
+    ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007001004, "支付渠道秘钥文件读取失败");
+    ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
+    ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空");
+    ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本apiclient_key.pem不可为空");
+    ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本中apiclient_cert.pem不可为空");
+    /**
+     * ========== ORDER 模块 1-007-002-000 ==========
+     */
     ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
     ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
     ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
     ErrorCode PAY_ORDER_ERROR_USER = new ErrorCode(1007002003, "支付订单用户不正确");
-    // ========== ORDER 模块(拓展单) 1-007-003-000 ==========
+
+    /**
+     * ========== ORDER 模块(拓展单) 1-007-003-000 ==========
+     */
     ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
     ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
     ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007003002, "支付订单不处于已支付");
@@ -46,12 +60,4 @@ public interface PayErrorCodeCoreConstants {
     ErrorCode APP_NOT_EXISTS = new ErrorCode(1007005000, "支付应用信息不存在");
 
 
-    /**
-     * ========== 支付渠道 1-007-001-000 ==========
-     */
-    ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
-    ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007001004, "支付渠道秘钥文件读取失败");
-    // TODO @aquan:下面这个错误码,缺了 CHANNEL 前缀。另外,错误码的分段,上面有啦,合并下进去哈
-    ErrorCode EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
-
 }

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java

@@ -65,6 +65,8 @@ public class PayClientFactoryImpl implements PayClientFactory {
             case WX_APP: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
+            case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
+            case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
         }
         // 创建失败,错误日志 + 抛出异常
         log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", config);

+ 23 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java

@@ -3,7 +3,11 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import lombok.Data;
 
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
 // TODO 芋艿:参数校验
+
 /**
  * 支付宝的 PayClientConfig 实现类
  * 属性主要来自 {@link com.alipay.api.AlipayConfig} 的必要属性
@@ -25,11 +29,11 @@ public class AlipayPayClientConfig implements PayClientConfig {
     /**
      * 公钥类型 - 公钥模式
      */
-    private static final Integer MODE_PUBLIC_KEY = 1;
+    public static final Integer MODE_PUBLIC_KEY = 1;
     /**
      * 公钥类型 - 证书模式
      */
-    private static final Integer MODE_CERTIFICATE = 2;
+    public static final Integer MODE_CERTIFICATE = 2;
 
     /**
      * 签名算法类型 - RSA
@@ -41,18 +45,21 @@ public class AlipayPayClientConfig implements PayClientConfig {
      * 1. {@link #SERVER_URL_PROD}
      * 2. {@link #SERVER_URL_SANDBOX}
      */
+    @NotBlank(message = "网关地址不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
     private String serverUrl;
 
     /**
      * 开放平台上创建的应用的 ID
      */
+    @NotBlank(message = "开放平台上创建的应用的 ID不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
     private String appId;
 
     /**
      * 签名算法类型,推荐:RSA2
-     *
+     * <p>
      * {@link #SIGN_TYPE_DEFAULT}
      */
+    @NotBlank(message = "签名算法类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
     private String signType;
 
     /**
@@ -60,30 +67,43 @@ public class AlipayPayClientConfig implements PayClientConfig {
      * 1. {@link #MODE_PUBLIC_KEY} 情况,privateKey + alipayPublicKey
      * 2. {@link #MODE_CERTIFICATE} 情况,appCertContent + alipayPublicCertContent + rootCertContent
      */
+    @NotNull(message = "公钥类型不能为空", groups = {ModePublicKey.class, ModeCertificate.class})
     private Integer mode;
 
     // ========== 公钥模式 ==========
     /**
      * 商户私钥
      */
+    @NotBlank(message = "商户私钥不能为空", groups = {ModePublicKey.class})
     private String privateKey;
+
     /**
      * 支付宝公钥字符串
      */
+    @NotBlank(message = "支付宝公钥字符串不能为空", groups = {ModePublicKey.class})
     private String alipayPublicKey;
 
     // ========== 证书模式 ==========
     /**
      * 指定商户公钥应用证书内容字符串
      */
+    @NotBlank(message = "指定商户公钥应用证书内容不能为空", groups = {ModeCertificate.class})
     private String appCertContent;
     /**
      * 指定支付宝公钥证书内容字符串
      */
+    @NotBlank(message = "指定支付宝公钥证书内容不能为空", groups = {ModeCertificate.class})
     private String alipayPublicCertContent;
     /**
      * 指定根证书内容字符串
      */
+    @NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class})
     private String rootCertContent;
 
+    public interface ModePublicKey {
+    }
+
+    public interface ModeCertificate {
+    }
+
 }

+ 33 - 11
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java

@@ -4,10 +4,12 @@ import cn.hutool.core.io.IoUtil;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import lombok.Data;
 
+import javax.validation.constraints.NotBlank;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 
 // TODO 芋艿:参数校验
+
 /**
  * 微信支付的 PayClientConfig 实现类
  * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性
@@ -20,13 +22,13 @@ public class WXPayClientConfig implements PayClientConfig {
     // TODO 芋艿:V2 or V3 客户端
     /**
      * API 版本 - V2
-     *
+     * <p>
      * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1
      */
     public static final String API_VERSION_V2 = "v2";
     /**
      * API 版本 - V3
-     *
+     * <p>
      * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
      */
     public static final String API_VERSION_V3 = "v3";
@@ -34,14 +36,17 @@ public class WXPayClientConfig implements PayClientConfig {
     /**
      * 公众号或者小程序的 appid
      */
+    @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class})
     private String appId;
     /**
      * 商户号
      */
+    @NotBlank(message = "商户号 不能为空", groups = {V2.class, V3.class})
     private String mchId;
     /**
      * API 版本
      */
+    @NotBlank(message = "API 版本 不能为空", groups = {V2.class, V3.class})
     private String apiVersion;
 
     // ========== V2 版本的参数 ==========
@@ -49,33 +54,37 @@ public class WXPayClientConfig implements PayClientConfig {
     /**
      * 商户密钥
      */
+    @NotBlank(message = "商户密钥 不能为空", groups = {V2.class})
     private String mchKey;
-//    /**
-//     * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径.
-//     * 对应的字符串
-//     *
-//     * 注意,可通过 {@link #main(String[])} 读取
-//     */
-//    private String keyContent;
+    /**
+     * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径.
+     * 对应的字符串
+     *
+     * 注意,可通过 {@link #main(String[])} 读取
+     */
+    /// private String keyContent;
 
     // ========== V3 版本的参数 ==========
     /**
      * apiclient_key.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
      * 对应的字符串
-     *
+     * <p>
      * 注意,可通过 {@link #main(String[])} 读取
      */
+    @NotBlank(message = "apiclient_key 不能为空", groups = {V3.class})
     private String privateKeyContent;
     /**
      * apiclient_cert.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
      * 对应的字符串
-     *
+     * <p>
      * 注意,可通过 {@link #main(String[])} 读取
      */
+    @NotBlank(message = "apiclient_cert 不能为空", groups = {V3.class})
     private String privateCertContent;
     /**
      * apiV3 秘钥值
      */
+    @NotBlank(message = "apiV3 秘钥值 不能为空", groups = {V3.class})
     private String apiV3Key;
 
     public static void main(String[] args) throws FileNotFoundException {
@@ -85,4 +94,17 @@ public class WXPayClientConfig implements PayClientConfig {
         System.out.println(IoUtil.readUtf8(new FileInputStream(path)));
     }
 
+
+    /**
+     * 分组校验 v2版本
+     */
+    public interface V2 {
+    }
+
+    /**
+     * 分组校验 v3版本
+     */
+    public interface V3 {
+    }
+
 }

+ 30 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java

@@ -15,8 +15,6 @@ import lombok.Getter;
 public enum PayChannelEnum {
 
     WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号的网页
-    // TODO @芋艿 这个地方你写的是 wx_lit 是不是少写了一个e? 还是我这里多加了一个e
-    // TODO @aquan:这里就是 lite 哈,轻量
     WX_LITE("wx_lite","微信小程序支付"),
     WX_APP("wx_app", "微信 App 支付"),
 
@@ -36,8 +34,38 @@ public enum PayChannelEnum {
      */
     private String name;
 
+    /**
+     * 微信支付
+     */
+    public static final String WECHAT = "WECHAT";
+
+    /**
+     * 支付宝支付
+     */
+    public static final String ALIPAY = "ALIPAY";
+
     public static PayChannelEnum getByCode(String code) {
         return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());
     }
 
+    /**
+     * 判断当前渠道是那种支付方式
+     * @param code
+     * @return
+     */
+    public static String verifyWechatOrAliPay(String code){
+        switch (PayChannelEnum.getByCode(code)){
+            case WX_PUB:
+            case WX_LITE:
+            case WX_APP:
+                return WECHAT;
+            case ALIPAY_PC:
+            case ALIPAY_WAP:
+            case ALIPAY_APP:
+            case ALIPAY_QR:
+                return ALIPAY;
+        }
+        return null;
+    }
+
 }