Commit 1ed6656bbb103133dba4be65ee0875c4550d6550

Authored by YunaiV
1 parent 23d8da74

完成对微信公众号支付的封装

Showing 15 changed files with 423 additions and 67 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.util.io;
  2 +
  3 +import cn.hutool.core.io.FileUtil;
  4 +import cn.hutool.core.util.IdUtil;
  5 +import lombok.SneakyThrows;
  6 +
  7 +import java.io.File;
  8 +
  9 +/**
  10 + * 文件工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class FileUtils {
  15 +
  16 + /**
  17 + * 创建临时文件
  18 + * 该文件会在 JVM 退出时,进行删除
  19 + *
  20 + * @param data 文件内容
  21 + * @return 文件
  22 + */
  23 + @SneakyThrows
  24 + public static File createTempFile(String data) {
  25 + // 创建文件,通过 UUID 保证唯一
  26 + File file = File.createTempFile(IdUtil.simpleUUID(), null);
  27 + // 标记 JVM 退出时,自动删除
  28 + file.deleteOnExit();
  29 + // 写入内容
  30 + FileUtil.writeUtf8String(data, file);
  31 + return file;
  32 + }
  33 +
  34 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml
... ... @@ -11,7 +11,10 @@
11 11  
12 12 <artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
13 13 <name>${artifactId}</name>
14   - <description>支付拓展,基于 IJPay 简单封装,支持微信、支付宝等常见支付渠道</description>
  14 + <description>支付拓展,接入国内多个支付渠道
  15 + 1. 支付宝,基于官方 SDK 接入
  16 + 2. 微信支付,基于 weixin-java-pay 接入
  17 + </description>
15 18  
16 19 <dependencies>
17 20 <dependency>
... ... @@ -34,17 +37,36 @@
34 37 <artifactId>slf4j-api</artifactId>
35 38 </dependency>
36 39  
37   - <!-- 三方云服务相关 -->
38 40 <dependency>
39   - <groupId>com.github.javen205</groupId>
40   - <artifactId>IJPay-AliPay</artifactId>
41   - <version>2.7.8</version>
  41 + <groupId>com.fasterxml.jackson.core</groupId>
  42 + <artifactId>jackson-databind</artifactId>
42 43 </dependency>
43 44 <dependency>
44   - <groupId>com.github.javen205</groupId>
45   - <artifactId>IJPay-WxPay</artifactId>
46   - <version>2.7.8</version>
  45 + <groupId>com.fasterxml.jackson.core</groupId>
  46 + <artifactId>jackson-core</artifactId>
47 47 </dependency>
48 48  
  49 + <!-- 三方云服务相关 -->
  50 +<!-- <dependency>-->
  51 +<!-- <groupId>com.github.javen205</groupId>-->
  52 +<!-- <artifactId>IJPay-AliPay</artifactId>-->
  53 +<!-- <version>2.7.8</version>-->
  54 +<!-- </dependency>-->
  55 +<!-- <dependency>-->
  56 +<!-- <groupId>com.github.javen205</groupId>-->
  57 +<!-- <artifactId>IJPay-WxPay</artifactId>-->
  58 +<!-- <version>2.7.8</version>-->
  59 +<!-- </dependency>-->
  60 + <dependency>
  61 + <groupId>com.alipay.sdk</groupId>
  62 + <artifactId>alipay-sdk-java</artifactId>
  63 + <version>4.17.9.ALL</version>
  64 + </dependency>
  65 + <dependency>
  66 + <groupId>com.github.binarywang</groupId>
  67 + <artifactId>weixin-java-pay</artifactId>
  68 + <version>4.1.9.B</version>
  69 + </dependency>
  70 + <!-- TODO 芋艿:清理 -->
49 71 </dependencies>
50 72 </project>
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayCodeMapping.java renamed to yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/AbstractPayCodeMapping.java
... ... @@ -2,8 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client;
2 2  
3 3 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4 4 import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
5   -
6   -import java.util.function.Function;
  5 +import lombok.extern.slf4j.Slf4j;
7 6  
8 7 /**
9 8 * 将 API 的错误码,转换为通用的错误码
... ... @@ -13,5 +12,22 @@ import java.util.function.Function;
13 12 *
14 13 * @author 芋道源码
15 14 */
16   -public interface PayCodeMapping extends Function<String, ErrorCode> {
  15 +@Slf4j
  16 +public abstract class AbstractPayCodeMapping {
  17 +
  18 + public final ErrorCode apply(String apiCode, String apiMsg) {
  19 + if (apiCode == null) {
  20 + log.error("[apply][API 错误码为空,请排查]");
  21 + return PayFrameworkErrorCodeConstants.EXCEPTION;
  22 + }
  23 + ErrorCode errorCode = this.apply0(apiCode, apiMsg);
  24 + if (errorCode == null) {
  25 + log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
  26 + return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
  27 + }
  28 + return errorCode;
  29 + }
  30 +
  31 + protected abstract ErrorCode apply0(String apiCode, String apiMsg);
  32 +
17 33 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
... ... @@ -18,6 +18,6 @@ public interface PayClient {
18 18 Long getId();
19 19  
20 20 // TODO 缺少注释
21   - CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
  21 + CommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
22 22  
23 23 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayCommonResult.java
... ... @@ -35,16 +35,13 @@ public class PayCommonResult&lt;T&gt; extends CommonResult&lt;T&gt; {
35 35 private PayCommonResult() {
36 36 }
37 37  
38   - public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, PayCodeMapping codeMapping) {
  38 + public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
39 39 Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
40 40 PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
41 41 result.setData(data);
42 42 // 翻译错误码
43 43 if (codeMapping != null) {
44   - ErrorCode errorCode = codeMapping.apply(apiCode);
45   - if (errorCode == null) {
46   - errorCode = PayFrameworkErrorCodeConstants.EXCEPTION;
47   - }
  44 + ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
48 45 result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
49 46 }
50 47 return result;
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.dto;
2 2  
3 3 import lombok.Data;
4 4 import org.hibernate.validator.constraints.Length;
  5 +import org.hibernate.validator.constraints.URL;
5 6  
6 7 import javax.validation.constraints.DecimalMin;
7 8 import javax.validation.constraints.NotEmpty;
... ... @@ -39,6 +40,12 @@ public class PayOrderUnifiedReqDTO {
39 40 @NotEmpty(message = "商品描述信息不能为空")
40 41 @Length(max = 128, message = "商品描述信息长度不能超过128")
41 42 private String body;
  43 + /**
  44 + * 支付结果的回调地址
  45 + */
  46 + @NotEmpty(message = "支付结果的回调地址不能为空")
  47 + @URL(message = "支付结果的回调地址必须是 URL 格式")
  48 + private String notifyUrl;
42 49  
43 50 // ========== 订单相关字段 ==========
44 51  
... ... @@ -55,4 +62,7 @@ public class PayOrderUnifiedReqDTO {
55 62 @NotNull(message = "支付过期时间不能为空")
56 63 private Date expireTime;
57 64  
  65 + // ========== 拓展参数 ==========
  66 + // TODO 芋艿:待完善
  67 +
58 68 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl;
2 2  
3 3 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
4 4 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
5   -import cn.iocoder.yudao.framework.pay.core.client.PayCodeMapping;
  5 +import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
6 6 import lombok.extern.slf4j.Slf4j;
7 7  
8 8 /**
... ... @@ -24,7 +24,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
24 24 /**
25 25 * 错误码枚举类
26 26 */
27   - protected PayCodeMapping codeMapping;
  27 + protected AbstractPayCodeMapping codeMapping;
28 28 /**
29 29 * 支付配置
30 30 */
... ... @@ -34,7 +34,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
34 34 return amount / 100.0;
35 35 }
36 36  
37   - public AbstractPayClient(Long channelId, String channelCode, Config config, PayCodeMapping codeMapping) {
  37 + public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
38 38 this.channelId = channelId;
39 39 this.channelCode = channelCode;
40 40 this.codeMapping = codeMapping;
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayConfig.java renamed to yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
... ... @@ -11,7 +11,7 @@ import lombok.Data;
11 11 * @author 芋道源码
12 12 */
13 13 @Data
14   -public class AlipayPayConfig implements PayClientConfig {
  14 +public class AlipayPayClientConfig implements PayClientConfig {
15 15  
16 16 /**
17 17 * 网关地址 - 线上
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
2 2  
3 3 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4   -import cn.iocoder.yudao.framework.pay.core.client.PayCodeMapping;
  4 +import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
5 5  
6   -public class AlipayPayCodeMapping implements PayCodeMapping {
  6 +/**
  7 + * 支付宝的 PayCodeMapping 实现类
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
7 12  
8 13 @Override
9   - public ErrorCode apply(String s) {
  14 + protected ErrorCode apply0(String apiCode, String apiMsg) {
10 15 return null;
11 16 }
12 17  
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
... ... @@ -12,6 +12,9 @@ import com.alipay.api.domain.AlipayTradePrecreateModel;
12 12 import com.alipay.api.request.AlipayTradePrecreateRequest;
13 13 import com.alipay.api.response.AlipayTradePrecreateResponse;
14 14 import lombok.SneakyThrows;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
15 18  
16 19 /**
17 20 * 支付宝【扫码支付】的 PayClient 实现类
... ... @@ -19,11 +22,12 @@ import lombok.SneakyThrows;
19 22 *
20 23 * @author 芋道源码
21 24 */
22   -public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
  25 +@Slf4j
  26 +public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> {
23 27  
24 28 private DefaultAlipayClient client;
25 29  
26   - public AlipayQrPayClient(Long channelId, String channelCode, AlipayPayConfig config) {
  30 + public AlipayQrPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
27 31 super(channelId, channelCode, config, new AlipayPayCodeMapping());
28 32 }
29 33  
... ... @@ -32,17 +36,18 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
32 36 protected void doInit() {
33 37 AlipayConfig alipayConfig = new AlipayConfig();
34 38 BeanUtil.copyProperties(config, alipayConfig, false);
  39 + // 真实客户端
35 40 this.client = new DefaultAlipayClient(alipayConfig);
36 41 }
37 42  
38 43 @Override
39   - public CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  44 + public CommonResult<AlipayTradePrecreateResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
40 45 // 构建 AlipayTradePrecreateModel 请求
41 46 AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
42 47 model.setOutTradeNo(reqDTO.getMerchantOrderId());
43 48 model.setSubject(reqDTO.getSubject());
44 49 model.setBody(reqDTO.getBody());
45   - model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
  50 + model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
46 51 // TODO 芋艿:clientIp + expireTime
47 52 // 构建 AlipayTradePrecreateRequest
48 53 AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
... ... @@ -53,6 +58,7 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
53 58 try {
54 59 response = client.execute(request);
55 60 } catch (AlipayApiException e) {
  61 + log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
56 62 return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
57 63 }
58 64  
... ... @@ -64,10 +70,10 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
64 70 }
65 71  
66 72 public static void main(String[] args) {
67   - AlipayPayConfig config = new AlipayPayConfig();
  73 + AlipayPayClientConfig config = new AlipayPayClientConfig();
68 74 config.setAppId("2021000118634035");
69   - config.setServerUrl(AlipayPayConfig.SERVER_URL_SANDBOX);
70   - config.setSignType(AlipayPayConfig.SIGN_TYPE_DEFAULT);
  75 + config.setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX);
  76 + config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
71 77 config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=");
72 78 config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
73 79 AlipayQrPayClient client = new AlipayQrPayClient(1L, "biu", config);
... ... @@ -79,4 +85,5 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
79 85 reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
80 86 client.unifiedOrder(reqDTO);
81 87 }
  88 +
82 89 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
... ... @@ -19,11 +19,11 @@ import lombok.SneakyThrows;
19 19 *
20 20 * @author 芋道源码
21 21 */
22   -public class AlipayWapPayClient extends AbstractPayClient<AlipayPayConfig> {
  22 +public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> {
23 23  
24 24 private DefaultAlipayClient client;
25 25  
26   - public AlipayWapPayClient(Long channelId, String channelCode, AlipayPayConfig config) {
  26 + public AlipayWapPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
27 27 super(channelId, channelCode, config, new AlipayPayCodeMapping());
28 28 }
29 29  
... ... @@ -36,7 +36,7 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
36 36 }
37 37  
38 38 @Override
39   - public CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  39 + public CommonResult<AlipayTradeWapPayResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
40 40 // 构建 AlipayTradeWapPayModel 请求
41 41 AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
42 42 model.setOutTradeNo(reqDTO.getMerchantOrderId());
... ... @@ -65,10 +65,10 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayConfig&gt; {
65 65 }
66 66  
67 67 public static void main(String[] args) {
68   - AlipayPayConfig config = new AlipayPayConfig();
  68 + AlipayPayClientConfig config = new AlipayPayClientConfig();
69 69 config.setAppId("2021000118634035");
70   - config.setServerUrl(AlipayPayConfig.SERVER_URL_SANDBOX);
71   - config.setSignType(AlipayPayConfig.SIGN_TYPE_DEFAULT);
  70 + config.setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX);
  71 + config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
72 72 config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=");
73 73 config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
74 74 AlipayWapPayClient client = new AlipayWapPayClient(1L, "biubiubiu", config);
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXCodeMapping.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  4 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
  6 +
  7 +import java.util.Objects;
  8 +
  9 +import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
  10 +
  11 +/**
  12 + * 微信支付 PayCodeMapping 实现类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class WXCodeMapping extends AbstractPayCodeMapping {
  17 +
  18 + /**
  19 + * 错误码 - 成功
  20 + * 由于 weixin-java-pay 封装的 Result 未返回 code,所以自己定义下
  21 + */
  22 + public static final String CODE_SUCCESS = "SUCCESS";
  23 + /**
  24 + * 错误提示 - 成功
  25 + */
  26 + public static final String MESSAGE_SUCCESS = "成功";
  27 +
  28 + @Override
  29 + protected ErrorCode apply0(String apiCode, String apiMsg) {
  30 + if (Objects.equals(apiCode, CODE_SUCCESS)) {
  31 + return GlobalErrorCodeConstants.SUCCESS;
  32 + }
  33 + if (Objects.equals(apiCode, "FAIL")) {
  34 + if (Objects.equals(apiMsg, "AppID不存在,请检查后再试")) {
  35 + return PAY_CONFIG_APP_ID_ERROR;
  36 + }
  37 + if (Objects.equals(apiMsg, "签名错误,请检查后再试")
  38 + || Objects.equals(apiMsg, "签名错误")) {
  39 + return PAY_CONFIG_SIGN_ERROR;
  40 + }
  41 + }
  42 + if (Objects.equals(apiCode, "PARAM_ERROR")) {
  43 + if (Objects.equals(apiMsg, "无效的openid")) {
  44 + return PAY_OPENID_ERROR;
  45 + }
  46 + }
  47 + return null;
  48 + }
  49 +
  50 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPayClientConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
  2 +
  3 +import cn.hutool.core.io.IoUtil;
  4 +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
  5 +import lombok.Data;
  6 +
  7 +import java.io.FileInputStream;
  8 +import java.io.FileNotFoundException;
  9 +
  10 +// TODO 芋艿:参数校验
  11 +/**
  12 + * 微信支付的 PayClientConfig 实现类
  13 + * 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Data
  18 +public class WXPayClientConfig implements PayClientConfig {
  19 +
  20 + // TODO 芋艿:V2 or V3 客户端
  21 + /**
  22 + * API 版本 - V2
  23 + *
  24 + * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1
  25 + */
  26 + public static final String API_VERSION_V2 = "v2";
  27 + /**
  28 + * API 版本 - V3
  29 + *
  30 + * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
  31 + */
  32 + public static final String API_VERSION_V3 = "v3";
  33 +
  34 + /**
  35 + * 公众号或者小程序的 appid
  36 + */
  37 + private String appId;
  38 + /**
  39 + * 商户号
  40 + */
  41 + private String mchId;
  42 + /**
  43 + * API 版本
  44 + */
  45 + private String apiVersion;
  46 +
  47 + // ========== V2 版本的参数 ==========
  48 +
  49 + /**
  50 + * 商户密钥
  51 + */
  52 + private String mchKey;
  53 +// /**
  54 +// * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径.
  55 +// * 对应的字符串
  56 +// *
  57 +// * 注意,可通过 {@link #main(String[])} 读取
  58 +// */
  59 +// private String keyContent;
  60 +
  61 + // ========== V3 版本的参数 ==========
  62 + /**
  63 + * apiclient_key.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
  64 + * 对应的字符串
  65 + *
  66 + * 注意,可通过 {@link #main(String[])} 读取
  67 + */
  68 + private String privateKeyContent;
  69 + /**
  70 + * apiclient_cert.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
  71 + * 对应的字符串
  72 + *
  73 + * 注意,可通过 {@link #main(String[])} 读取
  74 + */
  75 + private String privateCertContent;
  76 + /**
  77 + * apiV3 秘钥值
  78 + */
  79 + private String apiV3Key;
  80 +
  81 + public static void main(String[] args) throws FileNotFoundException {
  82 + String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.p12";
  83 +// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem";
  84 +// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem";
  85 + System.out.println(IoUtil.readUtf8(new FileInputStream(path)));
  86 + }
  87 +
  88 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import cn.hutool.core.io.IoUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  8 +import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  9 +import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
  10 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  11 +import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
  12 +import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
  13 +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
  14 +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
  15 +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
  16 +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
  17 +import com.github.binarywang.wxpay.config.WxPayConfig;
  18 +import com.github.binarywang.wxpay.constant.WxPayConstants;
  19 +import com.github.binarywang.wxpay.exception.WxPayException;
  20 +import com.github.binarywang.wxpay.service.WxPayService;
  21 +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +
  24 +import java.io.FileInputStream;
  25 +import java.io.FileNotFoundException;
  26 +
  27 +import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
  28 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
  29 +import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
  30 +import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
  31 +
  32 +/**
  33 + * 微信支付(公众号)的 PayClient 实现类
  34 + *
  35 + * @author 芋道源码
  36 + */
  37 +@Slf4j
  38 +public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
  39 +
  40 + private WxPayService client;
  41 +
  42 + public WXPubPayClient(Long channelId, String channelCode, WXPayClientConfig config) {
  43 + super(channelId, channelCode, config, new WXCodeMapping());
  44 + }
  45 +
  46 + @Override
  47 + protected void doInit() {
  48 + WxPayConfig payConfig = new WxPayConfig();
  49 + BeanUtil.copyProperties(config, payConfig, "keyContent");
  50 + payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式
  51 +// if (StrUtil.isNotEmpty(config.getKeyContent())) {
  52 +// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
  53 +// }
  54 + if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
  55 + // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
  56 + payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
  57 + }
  58 + if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
  59 + // weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
  60 + payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
  61 + }
  62 + // 真实客户端
  63 + this.client = new WxPayServiceImpl();
  64 + client.setConfig(payConfig);
  65 + }
  66 +
  67 + @Override
  68 + public CommonResult<WxPayMpOrderResult> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  69 + WxPayMpOrderResult response;
  70 + try {
  71 + switch (config.getApiVersion()) {
  72 + case WXPayClientConfig.API_VERSION_V2:
  73 + response = this.unifiedOrderV2(reqDTO);
  74 + break;
  75 + case WXPayClientConfig.API_VERSION_V3:
  76 + WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
  77 + // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
  78 + response = new WxPayMpOrderResult();
  79 + BeanUtil.copyProperties(responseV3, response, true);
  80 + break;
  81 + default:
  82 + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
  83 + }
  84 + } catch (WxPayException e) {
  85 + log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
  86 + return PayCommonResult.build(defaultIfNull(e.getErrCode(), e.getReturnCode()),
  87 + defaultIfNull(e.getErrCodeDes(), e.getReturnMsg()),null, codeMapping);
  88 + }
  89 + return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
  90 + }
  91 +
  92 + private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
  93 + // 构建 WxPayUnifiedOrderRequest 对象
  94 + WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
  95 + .outTradeNo(reqDTO.getMerchantOrderId())
  96 + // TODO 芋艿:貌似没 title?
  97 + .body(reqDTO.getBody())
  98 + .totalFee(reqDTO.getAmount()) // 单位分
  99 + .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
  100 + .spbillCreateIp(reqDTO.getClientIp())
  101 + .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死
  102 + .notifyUrl(reqDTO.getNotifyUrl())
  103 + .build();
  104 + // 执行请求
  105 + return client.createOrder(request);
  106 + }
  107 +
  108 + private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
  109 + // 构建 WxPayUnifiedOrderRequest 对象
  110 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
  111 + request.setOutTradeNo(reqDTO.getMerchantOrderId());
  112 + // TODO 芋艿:貌似没 title?
  113 + request.setDescription(reqDTO.getBody());
  114 + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
  115 + request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
  116 + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿:先随便写死
  117 + request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getClientIp()));
  118 + request.setNotifyUrl(reqDTO.getNotifyUrl());
  119 + // 执行请求
  120 + return client.createOrderV3(TradeTypeEnum.JSAPI, request);
  121 + }
  122 +
  123 + public static void main(String[] args) throws FileNotFoundException {
  124 + WXPayClientConfig config = new WXPayClientConfig();
  125 + config.setAppId("wx041349c6f39b268b");
  126 + config.setMchId("1545083881");
  127 + config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p");
  128 + config.setApiVersion(WXPayClientConfig.API_VERSION_V3);
  129 +// config.setKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12")));
  130 + config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem")));
  131 + config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
  132 + config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase");
  133 +
  134 + WXPubPayClient client = new WXPubPayClient(1L, "biu", config);
  135 + client.init();
  136 +
  137 + PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
  138 + reqDTO.setAmount(123);
  139 + reqDTO.setSubject("IPhone 13");
  140 + reqDTO.setBody("biubiubiu");
  141 + reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
  142 + reqDTO.setClientIp("127.0.0.1");
  143 + reqDTO.setNotifyUrl("http://127.0.0.1:8080");
  144 + CommonResult<WxPayMpOrderResult> result = client.unifiedOrder(reqDTO);
  145 + System.out.println(result);
  146 + }
  147 +
  148 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayFrameworkErrorCodeConstants.java
... ... @@ -11,36 +11,15 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
11 11 */
12 12 public interface PayFrameworkErrorCodeConstants {
13 13  
14   - ErrorCode PAY_UNKNOWN = new ErrorCode(2001000000, "未知错误,需要解析");
15   -
16   -// // ========== 权限 / 限流等相关 2001000100 ==========
17   -//
18   -// ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2001000100, "没有发送短信的权限");
19   -// // 云片:可以配置 IP 白名单,只有在白名单中才可以发送短信
20   -// ErrorCode SMS_IP_DENY = new ErrorCode(2001000100, "IP 不允许发送短信");
21   -//
22   -// // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
23   -// ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2001000102, "指定手机的发送限流");
24   -// // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
25   -// ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2001000103, "每天的发送限流");
26   -//
27   -// ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
28   -//
29   -// // ========== 模板相关 2001000200 ==========
30   -// ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
31   -// ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
32   -//
33   -// // ========== 签名相关 2001000300 ==========
34   -// ErrorCode SMS_SIGN_INVALID = new ErrorCode(2001000300, "短信签名不可用");
35   -//
36   -// // ========== 账户相关 2001000400 ==========
37   -// ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2001000400, "账户余额不足");
38   -// ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2001000401, "apiKey 不存在");
39   -//
40   -// // ========== 其它相关 2001000900 开头 ==========
41   -// ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
42   -// ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
43   -// ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
  14 + ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
  15 +
  16 + // ========== 配置相关相关 2002000100 ==========
  17 + ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
  18 + ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
  19 +
  20 +
  21 + // ========== 其它相关 2002000900 开头 ==========
  22 + ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
44 23  
45 24 ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
46 25  
... ...