Commit 66ed61c6416e36adcc7227202130b86737bc9dda

Authored by YunaiV
1 parent 7caf8e90

mall + pay:

1. 微信支付配置,补全 apiclient_cert.p12 证书
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
1 package cn.iocoder.yudao.framework.pay.core.client.impl.weixin; 1 package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
2 2
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.codec.Base64;
4 import cn.hutool.core.date.LocalDateTimeUtil; 5 import cn.hutool.core.date.LocalDateTimeUtil;
5 import cn.hutool.core.date.TemporalAccessorUtil; 6 import cn.hutool.core.date.TemporalAccessorUtil;
6 import cn.hutool.core.lang.Assert; 7 import cn.hutool.core.lang.Assert;
@@ -34,6 +35,7 @@ import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN; @@ -34,6 +35,7 @@ import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; 36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
36 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; 37 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
  38 +import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2;
37 39
38 /** 40 /**
39 * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款) 41 * 微信支付抽象类,实现微信统一的接口、以及部分实现(退款)
@@ -59,12 +61,17 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -59,12 +61,17 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
59 WxPayConfig payConfig = new WxPayConfig(); 61 WxPayConfig payConfig = new WxPayConfig();
60 BeanUtil.copyProperties(config, payConfig, "keyContent"); 62 BeanUtil.copyProperties(config, payConfig, "keyContent");
61 payConfig.setTradeType(tradeType); 63 payConfig.setTradeType(tradeType);
  64 +// if (WxPayClientConfig.API_VERSION_V2.equals(config.getApiVersion())) {
  65 +// payConfig.setSignType(WxPayConstants.SignType.MD5);
  66 +// }
  67 + // weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
  68 + if (Base64.isBase64(config.getKeyContent())) {
  69 + payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
  70 + }
62 if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) { 71 if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
63 - // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决  
64 payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath()); 72 payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
65 } 73 }
66 if (StrUtil.isNotEmpty(config.getPrivateCertContent())) { 74 if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
67 - // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决  
68 payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath()); 75 payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
69 } 76 }
70 77
@@ -77,7 +84,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -77,7 +84,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
77 protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { 84 protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
78 try { 85 try {
79 switch (config.getApiVersion()) { 86 switch (config.getApiVersion()) {
80 - case WxPayClientConfig.API_VERSION_V2: 87 + case API_VERSION_V2:
81 return doUnifiedOrderV2(reqDTO); 88 return doUnifiedOrderV2(reqDTO);
82 case WxPayClientConfig.API_VERSION_V3: 89 case WxPayClientConfig.API_VERSION_V3:
83 return doUnifiedOrderV3(reqDTO); 90 return doUnifiedOrderV3(reqDTO);
@@ -112,7 +119,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -112,7 +119,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
112 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 119 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
113 try { 120 try {
114 switch (config.getApiVersion()) { 121 switch (config.getApiVersion()) {
115 - case WxPayClientConfig.API_VERSION_V2: 122 + case API_VERSION_V2:
116 return doUnifiedRefundV2(reqDTO); 123 return doUnifiedRefundV2(reqDTO);
117 case WxPayClientConfig.API_VERSION_V3: 124 case WxPayClientConfig.API_VERSION_V3:
118 return doUnifiedRefundV3(reqDTO); 125 return doUnifiedRefundV3(reqDTO);
@@ -160,7 +167,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -160,7 +167,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
160 try { 167 try {
161 // 微信支付 v2 回调结果处理 168 // 微信支付 v2 回调结果处理
162 switch (config.getApiVersion()) { 169 switch (config.getApiVersion()) {
163 - case WxPayClientConfig.API_VERSION_V2: 170 + case API_VERSION_V2:
164 return parseOrderNotifyV2(body); 171 return parseOrderNotifyV2(body);
165 case WxPayClientConfig.API_VERSION_V3: 172 case WxPayClientConfig.API_VERSION_V3:
166 return parseOrderNotifyV3(body); 173 return parseOrderNotifyV3(body);
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java
@@ -37,16 +37,17 @@ public class WxPayClientConfig implements PayClientConfig { @@ -37,16 +37,17 @@ public class WxPayClientConfig implements PayClientConfig {
37 * 37 *
38 * 只有公众号或小程序需要该字段 38 * 只有公众号或小程序需要该字段
39 */ 39 */
  40 + @NotBlank(message = "APPID 不能为空", groups = {V2.class, V3.class})
40 private String appId; 41 private String appId;
41 /** 42 /**
42 * 商户号 43 * 商户号
43 */ 44 */
44 - @NotBlank(message = "商户号 不能为空", groups = {V2.class, V3.class}) 45 + @NotBlank(message = "商户号不能为空", groups = {V2.class, V3.class})
45 private String mchId; 46 private String mchId;
46 /** 47 /**
47 * API 版本 48 * API 版本
48 */ 49 */
49 - @NotBlank(message = "API 版本 不能为空", groups = {V2.class, V3.class}) 50 + @NotBlank(message = "API 版本不能为空", groups = {V2.class, V3.class})
50 private String apiVersion; 51 private String apiVersion;
51 52
52 // ========== V2 版本的参数 ========== 53 // ========== V2 版本的参数 ==========
@@ -54,36 +55,31 @@ public class WxPayClientConfig implements PayClientConfig { @@ -54,36 +55,31 @@ public class WxPayClientConfig implements PayClientConfig {
54 /** 55 /**
55 * 商户密钥 56 * 商户密钥
56 */ 57 */
57 - @NotBlank(message = "商户密钥 不能为空", groups = V2.class) 58 + @NotBlank(message = "商户密钥不能为空", groups = V2.class)
58 private String mchKey; 59 private String mchKey;
59 /** 60 /**
60 - * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径.  
61 - * 对应的字符串 61 + * apiclient_cert.p12 证书文件的对应字符串【base64 格式】
62 * 62 *
63 - * 注意,可通过 {@link #main(String[])} 读取 63 + * 为什么采用 base64 格式?因为 p12 读取后是二进制,需要转换成 base64 格式才好传输和存储
64 */ 64 */
65 - /// private String keyContent; 65 + @NotBlank(message = "apiclient_cert.p12 不能为空", groups = V2.class)
  66 + private String keyContent;
66 67
67 // ========== V3 版本的参数 ========== 68 // ========== V3 版本的参数 ==========
68 /** 69 /**
69 - * apiclient_key.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.  
70 - * 对应的字符串  
71 - * 注意,可通过 {@link #main(String[])} 读取 70 + * apiclient_key.pem 证书文件的对应字符串
72 */ 71 */
73 @NotBlank(message = "apiclient_key 不能为空", groups = V3.class) 72 @NotBlank(message = "apiclient_key 不能为空", groups = V3.class)
74 private String privateKeyContent; 73 private String privateKeyContent;
75 /** 74 /**
76 - * apiclient_cert.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.  
77 - * 对应的字符串  
78 - * <p>  
79 - * 注意,可通过 {@link #main(String[])} 读取 75 + * apiclient_cert.pem 证书文件的对应的字符串
80 */ 76 */
81 @NotBlank(message = "apiclient_cert 不能为空", groups = V3.class) 77 @NotBlank(message = "apiclient_cert 不能为空", groups = V3.class)
82 private String privateCertContent; 78 private String privateCertContent;
83 /** 79 /**
84 * apiV3 密钥值 80 * apiV3 密钥值
85 */ 81 */
86 - @NotBlank(message = "apiV3 密钥值 不能为空", groups = V3.class) 82 + @NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class)
87 private String apiV3Key; 83 private String apiV3Key;
88 84
89 /** 85 /**
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClientIntegrationTest.java
@@ -44,7 +44,7 @@ public class WxBarPayClientIntegrationTest { @@ -44,7 +44,7 @@ public class WxBarPayClientIntegrationTest {
44 .totalFee(1) // 单位分 44 .totalFee(1) // 单位分
45 .timeExpire(formatDateV2(LocalDateTimeUtils.addTime(Duration.ofMinutes(2)))) 45 .timeExpire(formatDateV2(LocalDateTimeUtils.addTime(Duration.ofMinutes(2))))
46 .spbillCreateIp("127.0.0.1") 46 .spbillCreateIp("127.0.0.1")
47 - .authCode("131276541518138032") 47 + .authCode("134298744426278497")
48 .build(); 48 .build();
49 System.out.println("========= request =========="); 49 System.out.println("========= request ==========");
50 System.out.println(JsonUtils.toJsonPrettyString(request)); 50 System.out.println(JsonUtils.toJsonPrettyString(request));
@@ -63,7 +63,7 @@ public class WxBarPayClientIntegrationTest { @@ -63,7 +63,7 @@ public class WxBarPayClientIntegrationTest {
63 63
64 // 执行发起退款 64 // 执行发起退款
65 WxPayRefundRequest request = new WxPayRefundRequest() 65 WxPayRefundRequest request = new WxPayRefundRequest()
66 - .setOutTradeNo("1689504162805") 66 + .setOutTradeNo("1689545667276")
67 .setOutRefundNo(String.valueOf(System.currentTimeMillis())) 67 .setOutRefundNo(String.valueOf(System.currentTimeMillis()))
68 .setRefundFee(1) 68 .setRefundFee(1)
69 .setRefundDesc("就是想退了") 69 .setRefundDesc("就是想退了")
@@ -103,8 +103,6 @@ public class WxBarPayClientIntegrationTest { @@ -103,8 +103,6 @@ public class WxBarPayClientIntegrationTest {
103 config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim"); 103 config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim");
104 config.setSignType(WxPayConstants.SignType.MD5); 104 config.setSignType(WxPayConstants.SignType.MD5);
105 config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12"); 105 config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12");
106 - config.setPrivateCertPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem");  
107 - config.setPrivateKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_key.pem");  
108 return config; 106 return config;
109 } 107 }
110 108
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClientIntegrationTest.java
@@ -58,7 +58,7 @@ public class WxNativePayClientIntegrationTest { @@ -58,7 +58,7 @@ public class WxNativePayClientIntegrationTest {
58 58
59 // 执行发起退款 59 // 执行发起退款
60 WxPayRefundV3Request request = new WxPayRefundV3Request() 60 WxPayRefundV3Request request = new WxPayRefundV3Request()
61 - .setOutTradeNo("1689506153043") 61 + .setOutTradeNo("1689545729695")
62 .setOutRefundNo(String.valueOf(System.currentTimeMillis())) 62 .setOutRefundNo(String.valueOf(System.currentTimeMillis()))
63 .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY")) 63 .setAmount(new WxPayRefundV3Request.Amount().setTotal(1).setRefund(1).setCurrency("CNY"))
64 .setReason("就是想退了"); 64 .setReason("就是想退了");
@@ -73,10 +73,8 @@ public class WxNativePayClientIntegrationTest { @@ -73,10 +73,8 @@ public class WxNativePayClientIntegrationTest {
73 WxPayConfig config = new WxPayConfig(); 73 WxPayConfig config = new WxPayConfig();
74 config.setAppId("wx62056c0d5e8db250"); 74 config.setAppId("wx62056c0d5e8db250");
75 config.setMchId("1545083881"); 75 config.setMchId("1545083881");
76 - config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim");  
77 config.setApiV3Key("459arNsYHl1mgkiO6H9ZH5KkhFXSxaA4"); 76 config.setApiV3Key("459arNsYHl1mgkiO6H9ZH5KkhFXSxaA4");
78 // config.setCertSerialNo(serialNo); 77 // config.setCertSerialNo(serialNo);
79 - config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12");  
80 config.setPrivateCertPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem"); 78 config.setPrivateCertPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem");
81 config.setPrivateKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_key.pem"); 79 config.setPrivateKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_key.pem");
82 return config; 80 return config;
yudao-ui-admin/src/views/pay/app/components/wechatChannelForm.vue
@@ -8,8 +8,8 @@ @@ -8,8 +8,8 @@
8 <template slot="append">%</template> 8 <template slot="append">%</template>
9 </el-input> 9 </el-input>
10 </el-form-item> 10 </el-form-item>
11 - <el-form-item label-width="180px" label="公众号APPID" prop="weChatConfig.appId">  
12 - <el-input v-model="form.weChatConfig.appId" placeholder="请输入公众号APPID" clearable :style="{width: '100%'}"> 11 + <el-form-item label-width="180px" label="公众号 APPID" prop="weChatConfig.appId">
  12 + <el-input v-model="form.weChatConfig.appId" placeholder="请输入公众号 APPID" clearable :style="{width: '100%'}">
13 </el-input> 13 </el-input>
14 </el-form-item> 14 </el-form-item>
15 <el-form-item label-width="180px" label="商户号" prop="weChatConfig.mchId"> 15 <el-form-item label-width="180px" label="商户号" prop="weChatConfig.mchId">
@@ -29,29 +29,41 @@ @@ -29,29 +29,41 @@
29 </el-radio> 29 </el-radio>
30 </el-radio-group> 30 </el-radio-group>
31 </el-form-item> 31 </el-form-item>
32 - <el-form-item label-width="180px" label="商户密钥" prop="weChatConfig.mchKey"  
33 - v-if="form.weChatConfig.apiVersion === 'v2'">  
34 - <el-input v-model="form.weChatConfig.mchKey" placeholder="请输入商户密钥" clearable  
35 - :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>  
36 - </el-form-item> 32 + <div v-if="form.weChatConfig.apiVersion === 'v2'">
  33 + <el-form-item label-width="180px" label="商户密钥" prop="weChatConfig.mchKey">
  34 + <el-input v-model="form.weChatConfig.mchKey" placeholder="请输入商户密钥" clearable
  35 + :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
  36 + </el-form-item>
  37 + <el-form-item label-width="180px" label="apiclient_cert.p12 证书" prop="weChatConfig.keyContent">
  38 + <el-input v-model="form.weChatConfig.keyContent" type="textarea"
  39 + placeholder="请上传 apiclient_cert.p12 证书"
  40 + readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
  41 + </el-form-item>
  42 + <el-form-item label-width="180px" label="">
  43 + <el-upload :limit="1" accept=".p12" action=""
  44 + :before-upload="p12FileBeforeUpload"
  45 + :http-request="keyContentUpload">
  46 + <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
  47 + </el-upload>
  48 + </el-form-item>
  49 + </div>
37 <div v-if="form.weChatConfig.apiVersion === 'v3'"> 50 <div v-if="form.weChatConfig.apiVersion === 'v3'">
38 - <el-form-item label-width="180px" label="API V3密钥" prop="weChatConfig.apiV3Key">  
39 - <el-input v-model="form.weChatConfig.apiV3Key" placeholder="请输入API V3密钥" clearable 51 + <el-form-item label-width="180px" label="API V3 密钥" prop="weChatConfig.apiV3Key">
  52 + <el-input v-model="form.weChatConfig.apiV3Key" placeholder="请输入 API V3 密钥" clearable
40 :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input> 53 :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
41 </el-form-item> 54 </el-form-item>
42 - <el-form-item label-width="180px" label="apiclient_key.perm证书" prop="weChatConfig.privateKeyContent"> 55 + <el-form-item label-width="180px" label="apiclient_key.perm 证书" prop="weChatConfig.privateKeyContent">
43 <el-input v-model="form.weChatConfig.privateKeyContent" type="textarea" 56 <el-input v-model="form.weChatConfig.privateKeyContent" type="textarea"
44 - placeholder="请上传apiclient_key.perm证书" 57 + placeholder="请上传 apiclient_key.perm 证书"
45 readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input> 58 readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
46 </el-form-item> 59 </el-form-item>
47 <el-form-item label-width="180px" label="" prop="privateKeyContentFile"> 60 <el-form-item label-width="180px" label="" prop="privateKeyContentFile">
48 <el-upload ref="privateKeyContentFile" 61 <el-upload ref="privateKeyContentFile"
49 :limit="1" 62 :limit="1"
50 - :accept="fileAccept"  
51 - :headers="header" 63 + accept=".pem"
52 action="" 64 action=""
53 :before-upload="pemFileBeforeUpload" 65 :before-upload="pemFileBeforeUpload"
54 - :http-request="privateKeyUpload" 66 + :http-request="privateKeyContentUpload"
55 > 67 >
56 <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button> 68 <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
57 </el-upload> 69 </el-upload>
@@ -64,18 +76,17 @@ @@ -64,18 +76,17 @@
64 <el-form-item label-width="180px" label="" prop="privateCertContentFile"> 76 <el-form-item label-width="180px" label="" prop="privateCertContentFile">
65 <el-upload ref="privateCertContentFile" 77 <el-upload ref="privateCertContentFile"
66 :limit="1" 78 :limit="1"
67 - :accept="fileAccept"  
68 - :headers="header" 79 + accept=".pem"
69 action="" 80 action=""
70 :before-upload="pemFileBeforeUpload" 81 :before-upload="pemFileBeforeUpload"
71 - :http-request="privateCertUpload" 82 + :http-request="privateCertContentUpload"
72 > 83 >
73 <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button> 84 <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
74 </el-upload> 85 </el-upload>
75 </el-form-item> 86 </el-form-item>
76 </div> 87 </div>
77 <el-form-item label-width="180px" label="备注" prop="remark"> 88 <el-form-item label-width="180px" label="备注" prop="remark">
78 - <el-input v-model="form.remark" :style="{width: '100%'}"></el-input> 89 + <el-input v-model="form.remark" :style="{width: '100%'}" />
79 </el-form-item> 90 </el-form-item>
80 </el-form> 91 </el-form>
81 <div slot="footer" class="dialog-footer"> 92 <div slot="footer" class="dialog-footer">
@@ -100,6 +111,7 @@ const defaultForm = { @@ -100,6 +111,7 @@ const defaultForm = {
100 mchId: '', 111 mchId: '',
101 apiVersion: '', 112 apiVersion: '',
102 mchKey: '', 113 mchKey: '',
  114 + keyContent: '',
103 privateKeyContent: '', 115 privateKeyContent: '',
104 privateCertContent: '', 116 privateCertContent: '',
105 apiV3Key:'', 117 apiV3Key:'',
@@ -159,27 +171,27 @@ export default { @@ -159,27 +171,27 @@ export default {
159 message: '请输入商户密钥', 171 message: '请输入商户密钥',
160 trigger: 'blur' 172 trigger: 'blur'
161 }], 173 }],
  174 + 'weChatConfig.keyContent': [{
  175 + required: true,
  176 + message: '请上传 apiclient_cert.p12 证书',
  177 + trigger: 'blur'
  178 + }],
162 'weChatConfig.privateKeyContent': [{ 179 'weChatConfig.privateKeyContent': [{
163 required: true, 180 required: true,
164 - message: '请上传apiclient_key.perm证书', 181 + message: '请上传 apiclient_key.perm 证书',
165 trigger: 'blur' 182 trigger: 'blur'
166 }], 183 }],
167 'weChatConfig.privateCertContent': [{ 184 'weChatConfig.privateCertContent': [{
168 required: true, 185 required: true,
169 - message: '请上传apiclient_cert.perm证书', 186 + message: '请上传 apiclient_cert.perm证 书',
170 trigger: 'blur' 187 trigger: 'blur'
171 }], 188 }],
172 'weChatConfig.apiV3Key': [{ 189 'weChatConfig.apiV3Key': [{
173 required: true, 190 required: true,
174 - message: '请上传apiV3密钥值', 191 + message: '请上传 api V3 密钥值',
175 trigger: 'blur' 192 trigger: 'blur'
176 }], 193 }],
177 }, 194 },
178 - // 文件上传的header  
179 - header: {  
180 - "Authorization": null  
181 - },  
182 - fileAccept: ".pem",  
183 // 渠道状态 数据字典 195 // 渠道状态 数据字典
184 statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS), 196 statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS),
185 versionDictDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_WECHAT_VERSION), 197 versionDictDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_WECHAT_VERSION),
@@ -218,6 +230,7 @@ export default { @@ -218,6 +230,7 @@ export default {
218 this.form.weChatConfig.apiVersion = config.apiVersion; 230 this.form.weChatConfig.apiVersion = config.apiVersion;
219 this.form.weChatConfig.mchId = config.mchId; 231 this.form.weChatConfig.mchId = config.mchId;
220 this.form.weChatConfig.mchKey = config.mchKey; 232 this.form.weChatConfig.mchKey = config.mchKey;
  233 + this.form.weChatConfig.keyContent = config.keyContent;
221 this.form.weChatConfig.privateKeyContent = config.privateKeyContent; 234 this.form.weChatConfig.privateKeyContent = config.privateKeyContent;
222 this.form.weChatConfig.privateCertContent = config.privateCertContent; 235 this.form.weChatConfig.privateCertContent = config.privateCertContent;
223 this.form.weChatConfig.apiV3Key = config.apiV3Key; 236 this.form.weChatConfig.apiV3Key = config.apiV3Key;
@@ -241,7 +254,6 @@ export default { @@ -241,7 +254,6 @@ export default {
241 this.$modal.msgSuccess("修改成功"); 254 this.$modal.msgSuccess("修改成功");
242 this.close(); 255 this.close();
243 } 256 }
244 -  
245 }) 257 })
246 } else { 258 } else {
247 259
@@ -255,10 +267,14 @@ export default { @@ -255,10 +267,14 @@ export default {
255 } 267 }
256 }); 268 });
257 }, 269 },
258 - pemFileBeforeUpload(file) { 270 + /**
  271 + * apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem 上传前的校验
  272 + */
  273 + fileBeforeUpload(file, fileAccept) {
259 let format = '.' + file.name.split(".")[1]; 274 let format = '.' + file.name.split(".")[1];
260 - if (format !== this.fileAccept) {  
261 - this.$message.error('请上传指定格式"' + this.fileAccept + '"文件'); 275 + if (format !== fileAccept) {
  276 + debugger
  277 + this.$message.error('请上传指定格式"' + fileAccept + '"文件');
262 return false; 278 return false;
263 } 279 }
264 let isRightSize = file.size / 1024 / 1024 < 2 280 let isRightSize = file.size / 1024 / 1024 < 2
@@ -267,19 +283,41 @@ export default { @@ -267,19 +283,41 @@ export default {
267 } 283 }
268 return isRightSize 284 return isRightSize
269 }, 285 },
270 - privateKeyUpload(event) { 286 + p12FileBeforeUpload(file) {
  287 + this.fileBeforeUpload(file, '.p12')
  288 + },
  289 + pemFileBeforeUpload(file) {
  290 + this.fileBeforeUpload(file, '.pem')
  291 + },
  292 + /**
  293 + * 读取 apiclient_key.pem 到 privateKeyContent 字段
  294 + */
  295 + privateKeyContentUpload(event) {
271 const readFile = new FileReader() 296 const readFile = new FileReader()
272 readFile.onload = (e) => { 297 readFile.onload = (e) => {
273 this.form.weChatConfig.privateKeyContent = e.target.result 298 this.form.weChatConfig.privateKeyContent = e.target.result
274 } 299 }
275 readFile.readAsText(event.file); 300 readFile.readAsText(event.file);
276 }, 301 },
277 - privateCertUpload(event) { 302 + /**
  303 + * 读取 apiclient_cert.pem 到 privateCertContent 字段
  304 + */
  305 + privateCertContentUpload(event) {
278 const readFile = new FileReader() 306 const readFile = new FileReader()
279 readFile.onload = (e) => { 307 readFile.onload = (e) => {
280 this.form.weChatConfig.privateCertContent = e.target.result 308 this.form.weChatConfig.privateCertContent = e.target.result
281 } 309 }
282 readFile.readAsText(event.file); 310 readFile.readAsText(event.file);
  311 + },
  312 + /**
  313 + * 读取 apiclient_cert.p12 到 keyContent 字段
  314 + */
  315 + keyContentUpload(event) {
  316 + const readFile = new FileReader()
  317 + readFile.onload = (e) => {
  318 + this.form.weChatConfig.keyContent = e.target.result.split(',')[1]
  319 + }
  320 + readFile.readAsDataURL(event.file); // 读成 base64
283 } 321 }
284 } 322 }
285 } 323 }
yudao-ui-admin/src/views/pay/app/index.vue
@@ -143,6 +143,30 @@ @@ -143,6 +143,30 @@
143 </el-button> 143 </el-button>
144 </template> 144 </template>
145 </el-table-column> 145 </el-table-column>
  146 + <el-table-column :label="payChannelEnum.WX_NATIVE.name" align="center">
  147 + <template v-slot="scope">
  148 + <el-button type="success" icon="el-icon-check" circle
  149 + v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_NATIVE.code)"
  150 + @click="handleUpdateChannel(scope.row,payChannelEnum.WX_NATIVE.code,payType.WECHAT)">
  151 + </el-button>
  152 + <el-button v-else
  153 + type="danger" icon="el-icon-close" circle
  154 + @click="handleCreateChannel(scope.row,payChannelEnum.WX_NATIVE.code,payType.WECHAT)">
  155 + </el-button>
  156 + </template>
  157 + </el-table-column>
  158 + <el-table-column :label="payChannelEnum.WX_BAR.name" align="center">
  159 + <template v-slot="scope">
  160 + <el-button type="success" icon="el-icon-check" circle
  161 + v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_BAR.code)"
  162 + @click="handleUpdateChannel(scope.row,payChannelEnum.WX_BAR.code,payType.WECHAT)">
  163 + </el-button>
  164 + <el-button v-else
  165 + type="danger" icon="el-icon-close" circle
  166 + @click="handleCreateChannel(scope.row,payChannelEnum.WX_BAR.code,payType.WECHAT)">
  167 + </el-button>
  168 + </template>
  169 + </el-table-column>
146 </el-table-column> 170 </el-table-column>
147 <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> 171 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
148 <template v-slot="scope"> 172 <template v-slot="scope">