Commit ab19228ca63cbe87659ce723d8597cb0553c5c4c

Authored by YunaiV
1 parent 6dc65234

完成支付的下单和提交订单的逻辑

Showing 24 changed files with 251 additions and 94 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayChannelCodeEnum.java deleted
1   -package cn.iocoder.yudao.adminserver.modules.pay.enums;
2   -
3   -import lombok.AllArgsConstructor;
4   -import lombok.Getter;
5   -
6   -/**
7   - * 支付渠道的编码的枚举
8   - *
9   - * @author 芋道源码
10   - */
11   -@Getter
12   -@AllArgsConstructor
13   -public enum PayChannelCodeEnum {
14   -
15   - wx_pub("wx_pub", "微信 JSAPI 支付");
16   -
17   - /**
18   - * 编码
19   - *
20   - * 参考 https://www.pingxx.com/api/支付渠道属性值.html
21   - */
22   - private String code;
23   - /**
24   - * 名字
25   - */
26   - private String name;
27   -
28   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java
... ... @@ -3,16 +3,15 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
3 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
4 4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
5 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum;
6 8 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
7 9 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
8 10 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
9   -import com.baomidou.mybatisplus.annotation.TableField;
10 11 import com.baomidou.mybatisplus.annotation.TableName;
11   -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
12 12 import lombok.*;
13 13  
14 14 import java.util.Date;
15   -import java.util.Map;
16 15  
17 16 /**
18 17 * 支付订单 DO
... ... @@ -61,7 +60,7 @@ public class PayOrderDO extends BaseDO {
61 60  
62 61 /**
63 62 * 商户订单编号
64   - * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 TODO 芋艿:需要在测试下
  63 + * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
65 64 */
66 65 private String merchantOrderId;
67 66 /**
... ... @@ -72,6 +71,16 @@ public class PayOrderDO extends BaseDO {
72 71 * 商品描述信息
73 72 */
74 73 private String body;
  74 + /**
  75 + * 异步通知地址
  76 + */
  77 + private String notifyUrl;
  78 + /**
  79 + * 通知商户支付结果的回调状态
  80 + *
  81 + * 枚举 {@link PayOrderNotifyStatusEnum}
  82 + */
  83 + private Integer notifyStatus;
75 84 // /**
76 85 // * 商户拓展参数
77 86 // */
... ... @@ -100,11 +109,6 @@ public class PayOrderDO extends BaseDO {
100 109 */
101 110 private Integer status;
102 111 /**
103   - * 通知商户支付结果的回调状态
104   - * TODO 芋艿:0 未发送 1 已发送
105   - */
106   - private Integer notifyStatus;
107   - /**
108 112 * 用户 IP
109 113 */
110 114 private String userIp;
... ... @@ -122,23 +126,12 @@ public class PayOrderDO extends BaseDO {
122 126 * 关联 {@link PayOrderDO#getId()}
123 127 */
124 128 private Long successExtensionId;
125   - /**
126   - * 支付渠道的额外参数
127   - *
128   - * 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
129   - */
130   - @TableField(typeHandler = JacksonTypeHandler.class)
131   - private Map<String, String> channelExtras;
132   - /**
133   - * 异步通知地址
134   - */
135   - private String notifyUrl;
136 129  
137 130 // ========== 退款相关字段 ==========
138 131 /**
139 132 * 退款状态
140 133 *
141   - * TODO 芋艿:0 - 未退款;1 - 部分退款; 2 - 全额退款
  134 + * 枚举 {@link PayOrderRefundStatusEnum}
142 135 */
143 136 private Integer refundStatus;
144 137 /**
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java
... ... @@ -3,9 +3,13 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
3 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
4 4 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
5 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
6 7 import com.baomidou.mybatisplus.annotation.TableName;
  8 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
7 9 import lombok.*;
8 10  
  11 +import java.util.Map;
  12 +
9 13 /**
10 14 * 支付订单拓展 DO
11 15 *
... ... @@ -49,7 +53,7 @@ public class PayOrderExtensionDO extends BaseDO {
49 53 /**
50 54 * 渠道编码
51 55 */
52   - private Integer channelCode;
  56 + private String channelCode;
53 57 /**
54 58 * 用户 IP
55 59 */
... ... @@ -62,6 +66,13 @@ public class PayOrderExtensionDO extends BaseDO {
62 66 */
63 67 private Integer status;
64 68 /**
  69 + * 支付渠道的额外参数
  70 + *
  71 + * 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
  72 + */
  73 + @TableField(typeHandler = JacksonTypeHandler.class)
  74 + private Map<String, String> channelExtras;
  75 + /**
65 76 * 支付渠道异步通知的内容
66 77 *
67 78 * 在支持成功后,会记录回调的数据
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java
... ... @@ -71,6 +71,15 @@ public class PayRefundDO extends BaseDO {
71 71 // * 商户拓展参数
72 72 // */
73 73 // private String merchantExtra;
  74 + /**
  75 + * 异步通知地址
  76 + */
  77 + private String notifyUrl;
  78 + /**
  79 + * 通知商户退款结果的回调状态
  80 + * TODO 芋艿:0 未发送 1 已发送
  81 + */
  82 + private Integer notifyStatus;
74 83  
75 84 // ========== 退款相关字段 ==========
76 85 /**
... ... @@ -80,11 +89,6 @@ public class PayRefundDO extends BaseDO {
80 89 */
81 90 private Integer status;
82 91 /**
83   - * 通知商户退款结果的回调状态
84   - * TODO 芋艿:0 未发送 1 已发送
85   - */
86   - private Integer notifyStatus;
87   - /**
88 92 * 用户 IP
89 93 */
90 94 private String userIp;
... ... @@ -110,10 +114,6 @@ public class PayRefundDO extends BaseDO {
110 114 * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
111 115 */
112 116 private String channelExtra;
113   - /**
114   - * 异步通知地址
115   - */
116   - private String notifyUrl;
117 117  
118 118 // ========== 渠道相关字段 ==========
119 119 /**
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderNotifyStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +/**
  8 + * 支付订单的通知状态枚举
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum PayOrderNotifyStatusEnum implements IntArrayValuable {
  15 +
  16 + NO(0, "未通知"),
  17 + SUCCESS(10, "通知成功"),
  18 + FAILURE(20, "通知失败")
  19 + ;
  20 +
  21 + private final Integer status;
  22 + private final String name;
  23 +
  24 + @Override
  25 + public int[] array() {
  26 + return new int[0];
  27 + }
  28 +
  29 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +/**
  8 + * 支付订单的退款状态枚举
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Getter
  13 +@AllArgsConstructor
  14 +public enum PayOrderRefundStatusEnum implements IntArrayValuable {
  15 +
  16 + NO(0, "未退款"),
  17 + SOME(10, "部分退款"),
  18 + ALL(20, "全部退款")
  19 + ;
  20 +
  21 + private final Integer status;
  22 + private final String name;
  23 +
  24 + @Override
  25 + public int[] array() {
  26 + return new int[0];
  27 + }
  28 +
  29 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/impl/PayChannelCoreServiceImpl.java
... ... @@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 12 import org.springframework.scheduling.annotation.Scheduled;
13 13 import org.springframework.stereotype.Service;
14 14  
  15 +import javax.annotation.PostConstruct;
15 16 import javax.annotation.Resource;
16 17 import javax.validation.Valid;
17 18  
... ... @@ -50,6 +51,7 @@ public class PayChannelCoreServiceImpl implements PayChannelCoreService {
50 51 private PayClientFactory payClientFactory;
51 52  
52 53 @Override
  54 + @PostConstruct
53 55 public void initPayClients() {
54 56 // 获取支付渠道,如果有更新
55 57 List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java
... ... @@ -28,6 +28,6 @@ public interface PayOrderCoreService {
28 28 * @param reqDTO 提交请求
29 29 * @return 提交结果
30 30 */
31   - PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO);
  31 + PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
32 32  
33 33 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayOrderSubmitReqDTO.java
1 1 package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
2 2  
  3 +import com.baomidou.mybatisplus.annotation.TableField;
  4 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
3 5 import lombok.Data;
4 6 import lombok.experimental.Accessors;
5 7  
6 8 import javax.validation.constraints.NotEmpty;
7 9 import javax.validation.constraints.NotNull;
8 10 import java.io.Serializable;
  11 +import java.util.Map;
9 12  
10 13 /**
11 14 * 支付单提交 Request DTO
... ... @@ -38,4 +41,10 @@ public class PayOrderSubmitReqDTO implements Serializable {
38 41 @NotEmpty(message = "用户 IP 不能为空")
39 42 private String userIp;
40 43  
  44 + /**
  45 + * 支付渠道的额外参数
  46 + */
  47 + @TableField(typeHandler = JacksonTypeHandler.class)
  48 + private Map<String, String> channelExtras;
  49 +
41 50 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
9 9 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
10 10 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
11 11 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper;
  12 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
12 13 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
13 14 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
14 15 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
... ... @@ -20,12 +21,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
20 21 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
21 22 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
22 23 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
  24 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
23 25 import lombok.extern.slf4j.Slf4j;
24 26 import org.springframework.stereotype.Service;
  27 +import org.springframework.validation.annotation.Validated;
25 28  
26 29 import javax.annotation.Resource;
27   -import javax.validation.Valid;
28 30 import java.util.Date;
  31 +import java.util.Objects;
29 32  
30 33 import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
31 34 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -36,7 +39,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
36 39 * @author 芋道源码
37 40 */
38 41 @Service
39   -@Valid
  42 +@Validated
40 43 @Slf4j
41 44 public class PayOrderCoreServiceImpl implements PayOrderCoreService {
42 45  
... ... @@ -68,10 +71,16 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
68 71 }
69 72  
70 73 // 创建支付交易单
71   - // TODO 芋艿:需要看看,还有啥要补全的字段
72 74 order = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
73   - .setStatus(PayOrderStatusEnum.WAITING.getStatus())
74   - .setNotifyUrl(app.getPayNotifyUrl());
  75 + .setMerchantId(app.getMerchantId()).setAppId(app.getId());
  76 + // 商户相关字段
  77 + order.setNotifyUrl(app.getPayNotifyUrl())
  78 + .setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus());
  79 + // 订单相关字段
  80 + order.setStatus(PayOrderStatusEnum.WAITING.getStatus());
  81 + // 退款相关字段
  82 + order.setRefundStatus(PayOrderNotifyStatusEnum.NO.getStatus())
  83 + .setRefundTimes(0).setRefundAmount(0L);
75 84 payOrderCoreMapper.insert(order);
76 85 // 最终返回
77 86 return order.getId();
... ... @@ -80,9 +89,9 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
80 89 @Override
81 90 public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
82 91 // 校验 App
83   - PayAppDO app = payAppCoreService.validPayApp(reqDTO.getId());
  92 + PayAppDO app = payAppCoreService.validPayApp(reqDTO.getAppId());
84 93 // 校验支付渠道是否有效
85   - PayChannelDO channel = payChannelCoreService.validPayChannel(reqDTO.getId(), reqDTO.getChannelCode());
  94 + PayChannelDO channel = payChannelCoreService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
86 95 // 校验支付客户端是否正确初始化
87 96 PayClient client = payClientFactory.getPayClient(channel.getId());
88 97 if (client == null) {
... ... @@ -92,7 +101,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
92 101  
93 102 // 获得 PayOrderDO ,并校验其是否存在
94 103 PayOrderDO order = payOrderCoreMapper.selectById(reqDTO.getId());
95   - if (order == null || order.getAppId().equals(reqDTO.getAppId())) { // 是否存在
  104 + if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
96 105 throw exception(PAY_ORDER_NOT_FOUND);
97 106 }
98 107 if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
... ... @@ -102,18 +111,25 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
102 111 // 插入 PayOrderExtensionDO
103 112 PayOrderExtensionDO orderExtension = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
104 113 .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
  114 + .setChannelId(channel.getId()).setChannelCode(channel.getCode())
105 115 .setStatus(PayOrderStatusEnum.WAITING.getStatus());
106 116 payOrderExtensionCoreMapper.insert(orderExtension);
107 117  
108 118 // 调用三方接口
109   - // TODO 暂时传入 extra = null
110   - CommonResult<?> invokeResult = client.unifiedOrder(PayOrderCoreConvert.INSTANCE.convert2(reqDTO));
111   - invokeResult.checkError();
  119 + PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderCoreConvert.INSTANCE.convert2(reqDTO);
  120 + // 商户相关字段
  121 + unifiedOrderReqDTO.setMerchantOrderId(order.getMerchantOrderId())
  122 + .setSubject(order.getSubject()).setBody(order.getBody())
  123 + .setNotifyUrl(app.getPayNotifyUrl());
  124 + // 订单相关字段
  125 + unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
  126 + CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
  127 + unifiedOrderResult.checkError();
112 128  
113 129 // TODO 轮询三方接口,是否已经支付的任务
114 130 // 返回成功
115 131 return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
116   - .setInvokeResponse(JsonUtils.toJsonString(invokeResult));
  132 + .setInvokeResponse(JsonUtils.toJsonString(unifiedOrderResult));
117 133 }
118 134  
119 135 private String generateOrderExtensionNo() {
... ...
yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreServiceTest.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.BaseDbIntegrationTest;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl.PayAppCoreServiceImpl;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl.PayChannelCoreServiceImpl;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.PayOrderCoreServiceImpl;
  9 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  10 +import cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration;
  11 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
  12 +import org.junit.jupiter.api.Test;
  13 +import org.springframework.context.annotation.Import;
  14 +
  15 +import javax.annotation.Resource;
  16 +import java.time.Duration;
  17 +
  18 +@Import({PayOrderCoreServiceImpl.class, PayAppCoreServiceImpl.class,
  19 + PayChannelCoreServiceImpl.class, YudaoPayAutoConfiguration.class})
  20 +public class PayOrderCoreServiceTest extends BaseDbIntegrationTest {
  21 +
  22 + @Resource
  23 + private PayOrderCoreService payOrderCoreService;
  24 +
  25 + @Test
  26 + public void testCreatePayOrder() {
  27 + // 构造请求
  28 + PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
  29 + reqDTO.setAppId(6L);
  30 + reqDTO.setUserIp("127.0.0.1");
  31 + reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
  32 + reqDTO.setSubject("标题");
  33 + reqDTO.setBody("内容");
  34 + reqDTO.setAmount(100);
  35 + reqDTO.setExpireTime(DateUtils.addTime(Duration.ofDays(1)));
  36 + // 发起请求
  37 + payOrderCoreService.createPayOrder(reqDTO);
  38 + }
  39 +
  40 + @Test
  41 + public void testSubmitPayOrder() {
  42 + // 构造请求
  43 + PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
  44 + reqDTO.setId(10L);
  45 + reqDTO.setAppId(6L);
  46 + reqDTO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
  47 + reqDTO.setUserIp("127.0.0.1");
  48 + // 发起请求
  49 + payOrderCoreService.submitPayOrder(reqDTO);
  50 + }
  51 +
  52 +}
... ...
yudao-core-service/src/test-integration/java/cn/iocoder/yudao/coreservice/modules/pay/service/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service;
... ...
yudao-core-service/src/test-integration/resources/application-integration-test.yaml
... ... @@ -60,7 +60,7 @@ mybatis-plus:
60 60 logic-delete-value: 1 # 逻辑已删除值(默认为 1)
61 61 logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
62 62 mapper-locations: classpath*:mapper/*.xml
63   - type-aliases-package: ${yudao.info.base-package}.modules.*.dal.dataobject, ${yudao.core-service.base-package}.modules.*.dal.dataobject
  63 + type-aliases-package: ${yudao.core-service.base-package}.modules.*.dal.dataobject
64 64  
65 65 --- #################### 定时任务相关配置 ####################
66 66  
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/ObjectUtils.java
... ... @@ -29,4 +29,13 @@ public class ObjectUtils {
29 29 return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
30 30 }
31 31  
  32 + public static <T> T defaultIfNull(T... array) {
  33 + for (T item : array) {
  34 + if (item != null) {
  35 + return item;
  36 + }
  37 + }
  38 + return null;
  39 + }
  40 +
32 41 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 3 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
5 4  
6 5 /**
... ... @@ -17,7 +16,12 @@ public interface PayClient {
17 16 */
18 17 Long getId();
19 18  
20   - // TODO 缺少注释
21   - CommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
  19 + /**
  20 + * 调用支付渠道,统一下单
  21 + *
  22 + * @param reqDTO 下单信息
  23 + * @return 各支付渠道的返回结果
  24 + */
  25 + PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
22 26  
23 27 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
... ... @@ -54,7 +54,7 @@ public class PayOrderUnifiedReqDTO {
54 54 */
55 55 @NotNull(message = "支付金额不能为空")
56 56 @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
57   - private Integer amount;
  57 + private Long amount;
58 58  
59 59 /**
60 60 * 支付过期时间
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl;
2 2  
  3 +import cn.hutool.extra.validation.ValidationUtil;
  4 +import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
3 5 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
4 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
5   -import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
  7 +import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
  8 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
6 9 import lombok.extern.slf4j.Slf4j;
7 10  
  11 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
  12 +
8 13 /**
9 14 * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码
10 15 *
... ... @@ -30,7 +35,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
30 35 */
31 36 protected Config config;
32 37  
33   - protected Double calculateAmount(Integer amount) {
  38 + protected Double calculateAmount(Long amount) {
34 39 return amount / 100.0;
35 40 }
36 41  
... ... @@ -70,4 +75,23 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
70 75 return channelId;
71 76 }
72 77  
  78 + @Override
  79 + public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  80 + ValidationUtil.validate(reqDTO);
  81 + // 执行短信发送
  82 + PayCommonResult<?> result;
  83 + try {
  84 + result = doUnifiedOrder(reqDTO);
  85 + } catch (Throwable ex) {
  86 + // 打印异常日志
  87 + log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
  88 + // 封装返回
  89 + return PayCommonResult.error(ex);
  90 + }
  91 + return result;
  92 + }
  93 +
  94 + protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
  95 + throws Throwable;
  96 +
73 97 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
2 2  
3 3 import cn.hutool.core.bean.BeanUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6 5 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
7 6 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
... ... @@ -42,7 +41,7 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
42 41 }
43 42  
44 43 @Override
45   - public CommonResult<AlipayTradePrecreateResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  44 + public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
46 45 // 构建 AlipayTradePrecreateModel 请求
47 46 AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
48 47 model.setOutTradeNo(reqDTO.getMerchantOrderId());
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
2 2  
3 3 import cn.hutool.core.bean.BeanUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 4 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6 5 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
7 6 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
... ... @@ -37,7 +36,7 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
37 36 }
38 37  
39 38 @Override
40   - public CommonResult<AlipayTradeWapPayResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  39 + public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
41 40 // 构建 AlipayTradeWapPayModel 请求
42 41 AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
43 42 model.setOutTradeNo(reqDTO.getMerchantOrderId());
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXCodeMapping.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4 5 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
5 6 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
... ... @@ -44,6 +45,11 @@ public class WXCodeMapping extends AbstractPayCodeMapping {
44 45 return PAY_OPENID_ERROR;
45 46 }
46 47 }
  48 + if (Objects.equals(apiCode, "CustomErrorCode")) {
  49 + if (StrUtil.contains(apiMsg, "必填字段")) {
  50 + return PAY_PARAM_MISSING;
  51 + }
  52 + }
47 53 return null;
48 54 }
49 55  
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
... ... @@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.date.DateUtil;
5 5 import cn.hutool.core.util.StrUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 6 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8 8 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
9 9 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
10 10 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
... ... @@ -21,7 +21,6 @@ import com.github.binarywang.wxpay.service.WxPayService;
21 21 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
22 22 import lombok.extern.slf4j.Slf4j;
23 23  
24   -import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
25 24 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
26 25 import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
27 26 import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
... ... @@ -62,7 +61,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
62 61 }
63 62  
64 63 @Override
65   - public CommonResult<WxPayMpOrderResult> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  64 + public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
66 65 WxPayMpOrderResult response;
67 66 try {
68 67 switch (config.getApiVersion()) {
... ... @@ -80,8 +79,8 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
80 79 }
81 80 } catch (WxPayException e) {
82 81 log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
83   - return PayCommonResult.build(defaultIfNull(e.getErrCode(), e.getReturnCode()),
84   - defaultIfNull(e.getErrCodeDes(), e.getReturnMsg()),null, codeMapping);
  82 + return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
  83 + ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
85 84 }
86 85 return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
87 86 }
... ... @@ -92,9 +91,9 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
92 91 .outTradeNo(reqDTO.getMerchantOrderId())
93 92 // TODO 芋艿:貌似没 title?
94 93 .body(reqDTO.getBody())
95   - .totalFee(reqDTO.getAmount()) // 单位分
  94 + .totalFee(reqDTO.getAmount().intValue()) // 单位分
96 95 .timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
97   - .spbillCreateIp(reqDTO.getClientIp())
  96 + .spbillCreateIp(reqDTO.getUserIp())
98 97 .openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死
99 98 .notifyUrl(reqDTO.getNotifyUrl())
100 99 .build();
... ... @@ -108,10 +107,10 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
108 107 request.setOutTradeNo(reqDTO.getMerchantOrderId());
109 108 // TODO 芋艿:貌似没 title?
110 109 request.setDescription(reqDTO.getBody());
111   - request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
  110 + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
112 111 request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
113 112 request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿:先随便写死
114   - request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getClientIp()));
  113 + request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
115 114 request.setNotifyUrl(reqDTO.getNotifyUrl());
116 115 // 执行请求
117 116 return client.createOrderV3(TradeTypeEnum.JSAPI, request);
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayFrameworkErrorCodeConstants.java
... ... @@ -20,6 +20,7 @@ public interface PayFrameworkErrorCodeConstants {
20 20  
21 21 // ========== 其它相关 2002000900 开头 ==========
22 22 ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
  23 + ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说,支付少传了金额
23 24  
24 25 ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
25 26  
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test-integration/java/cn/iocoder/yudao/framework/core/client/impl/PayClientFactoryImplTest.java
... ... @@ -117,11 +117,11 @@ public class PayClientFactoryImplTest {
117 117  
118 118 private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
119 119 PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
120   - reqDTO.setAmount(123);
  120 + reqDTO.setAmount(123L);
121 121 reqDTO.setSubject("IPhone 13");
122 122 reqDTO.setBody("biubiubiu");
123 123 reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
124   - reqDTO.setClientIp("127.0.0.1");
  124 + reqDTO.setUserIp("127.0.0.1");
125 125 reqDTO.setNotifyUrl("http://127.0.0.1:8080");
126 126 return reqDTO;
127 127 }
... ...