Commit b54f7e9256b7efb4bfb0d5c6f950ad59be5ed6d1

Authored by zhijiantianya@gmail.com
1 parent 721adfbf

by gateway:

1. 完善部分 order 单元测试
2. 增加支付订单的 no 生成逻辑
Showing 12 changed files with 397 additions and 92 deletions
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java
@@ -12,7 +12,6 @@ import org.springframework.context.annotation.Bean; @@ -12,7 +12,6 @@ import org.springframework.context.annotation.Bean;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @AutoConfiguration 14 @AutoConfiguration
15 -@EnableConfigurationProperties(PayProperties.class)  
16 public class YudaoPayAutoConfiguration { 15 public class YudaoPayAutoConfiguration {
17 16
18 @Bean 17 @Bean
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
@@ -14,4 +14,12 @@ public interface RedisKeyConstants { @@ -14,4 +14,12 @@ public interface RedisKeyConstants {
14 "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类 14 "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
15 RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构 15 RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
16 16
  17 + /**
  18 + * 支付序号的缓存
  19 + *
  20 + * KEY 格式:pay_no:{prefix}
  21 + * VALUE 数据格式:编号自增
  22 + */
  23 + String PAY_NO = "pay_no";
  24 +
17 } 25 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.dal.redis.no;
  2 +
  3 +import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil;import org.springframework.data.redis.core.StringRedisTemplate;
  4 +import org.springframework.stereotype.Repository;
  5 +
  6 +import javax.annotation.Resource;import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 支付序号的 Redis DAO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Repository
  14 +public class PayNoRedisDAO {
  15 +
  16 + @Resource
  17 + private StringRedisTemplate stringRedisTemplate;
  18 +
  19 + /**
  20 + * 生成序号
  21 + *
  22 + * @param prefix 前缀
  23 + * @return 序号
  24 + */
  25 + public String generate(String prefix) {
  26 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
  27 + Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
  28 + return noPrefix + no;
  29 + }
  30 +
  31 +}
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.framework.pay.config;
  2 +
  3 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  4 +import org.springframework.context.annotation.Configuration;
  5 +
  6 +@Configuration(proxyBeanMethods = false)
  7 +@EnableConfigurationProperties(PayProperties.class)
  8 +public class PayConfiguration {
  9 +}
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java renamed to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java
1 -package cn.iocoder.yudao.framework.pay.config; 1 +package cn.iocoder.yudao.module.pay.framework.pay.config;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import org.hibernate.validator.constraints.URL; 4 import org.hibernate.validator.constraints.URL;
@@ -12,6 +12,9 @@ import javax.validation.constraints.NotEmpty; @@ -12,6 +12,9 @@ import javax.validation.constraints.NotEmpty;
12 @Data 12 @Data
13 public class PayProperties { 13 public class PayProperties {
14 14
  15 + private static final String ORDER_NO_PREFIX = "P";
  16 + private static final String REFUND_NO_PREFIX = "R";
  17 +
15 /** 18 /**
16 * 支付回调地址 19 * 支付回调地址
17 * 20 *
@@ -34,4 +37,16 @@ public class PayProperties { @@ -34,4 +37,16 @@ public class PayProperties {
34 @URL(message = "支付回调地址的格式必须是 URL") 37 @URL(message = "支付回调地址的格式必须是 URL")
35 private String refundNotifyUrl; 38 private String refundNotifyUrl;
36 39
  40 + /**
  41 + * 支付订单 no 的前缀
  42 + */
  43 + @NotEmpty(message = "支付订单 no 的前缀不能为空")
  44 + private String orderNoPrefix = ORDER_NO_PREFIX;
  45 +
  46 + /**
  47 + * 退款订单 no 的前缀
  48 + */
  49 + @NotEmpty(message = "退款订单 no 的前缀不能为空")
  50 + private String refundNoPrefix = REFUND_NO_PREFIX;
  51 +
37 } 52 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java 0 → 100644
  1 +/**
  2 + * 占位,无实际作用
  3 + */
  4 +package cn.iocoder.yudao.module.pay.framework.pay.core;
0 \ No newline at end of file 5 \ No newline at end of file
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
1 package cn.iocoder.yudao.module.pay.service.order; 1 package cn.iocoder.yudao.module.pay.service.order;
2 2
3 -import cn.hutool.core.date.DateUtil;  
4 import cn.hutool.core.lang.Pair; 3 import cn.hutool.core.lang.Pair;
5 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
6 -import cn.hutool.core.util.RandomUtil;  
7 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.spring.SpringUtil; 6 import cn.hutool.extra.spring.SpringUtil;
9 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 8 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
11 -import cn.iocoder.yudao.framework.pay.config.PayProperties;  
12 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 9 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
13 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 10 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
14 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; 11 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@@ -27,8 +24,10 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; @@ -27,8 +24,10 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
27 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 24 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
28 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; 25 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
29 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; 26 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
  27 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
30 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 28 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
31 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 29 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
  30 +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
32 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 31 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
33 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 32 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
34 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 33 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@@ -40,7 +39,6 @@ import org.springframework.transaction.annotation.Transactional; @@ -40,7 +39,6 @@ import org.springframework.transaction.annotation.Transactional;
40 import org.springframework.validation.annotation.Validated; 39 import org.springframework.validation.annotation.Validated;
41 40
42 import javax.annotation.Resource; 41 import javax.annotation.Resource;
43 -import java.time.LocalDateTime;  
44 import java.util.List; 42 import java.util.List;
45 import java.util.Objects; 43 import java.util.Objects;
46 44
@@ -68,6 +66,8 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -68,6 +66,8 @@ public class PayOrderServiceImpl implements PayOrderService {
68 private PayOrderMapper orderMapper; 66 private PayOrderMapper orderMapper;
69 @Resource 67 @Resource
70 private PayOrderExtensionMapper orderExtensionMapper; 68 private PayOrderExtensionMapper orderExtensionMapper;
  69 + @Resource
  70 + private PayNoRedisDAO noRedisDAO;
71 71
72 @Resource 72 @Resource
73 private PayAppService appService; 73 private PayAppService appService;
@@ -136,8 +136,9 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -136,8 +136,9 @@ public class PayOrderServiceImpl implements PayOrderService {
136 PayClient client = payClientFactory.getPayClient(channel.getId()); 136 PayClient client = payClientFactory.getPayClient(channel.getId());
137 137
138 // 2. 插入 PayOrderExtensionDO 138 // 2. 插入 PayOrderExtensionDO
  139 + String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
139 PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp) 140 PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
140 - .setOrderId(order.getId()).setNo(generateOrderExtensionNo()) 141 + .setOrderId(order.getId()).setNo(no)
141 .setChannelId(channel.getId()).setChannelCode(channel.getCode()) 142 .setChannelId(channel.getId()).setChannelCode(channel.getCode())
142 .setStatus(PayOrderStatusEnum.WAITING.getStatus()); 143 .setStatus(PayOrderStatusEnum.WAITING.getStatus());
143 orderExtensionMapper.insert(orderExtension); 144 orderExtensionMapper.insert(orderExtension);
@@ -155,7 +156,7 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -155,7 +156,7 @@ public class PayOrderServiceImpl implements PayOrderService {
155 156
156 // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功 157 // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
157 if (unifiedOrderResp != null) { 158 if (unifiedOrderResp != null) {
158 - getSelf().notifyPayOrder(channel, unifiedOrderResp); 159 + getSelf().notifyOrder(channel, unifiedOrderResp);
159 // 如有渠道错误码,则抛出业务异常,提示用户 160 // 如有渠道错误码,则抛出业务异常,提示用户
160 if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) { 161 if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
161 throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), 162 throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
@@ -204,31 +205,12 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -204,31 +205,12 @@ public class PayOrderServiceImpl implements PayOrderService {
204 return payProperties.getOrderNotifyUrl() + "/" + channel.getId(); 205 return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
205 } 206 }
206 207
207 - private String generateOrderExtensionNo() {  
208 -// wx  
209 -// 2014  
210 -// 10  
211 -// 27  
212 -// 20  
213 -// 09  
214 -// 39  
215 -// 5522657  
216 -// a690389285100  
217 - // 目前的算法  
218 - // 时间序列,年月日时分秒 14 位  
219 - // 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整  
220 - return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列  
221 - RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒  
222 - ;  
223 - }  
224 -  
225 @Override 208 @Override
226 - @Transactional(rollbackFor = Exception.class)  
227 public void notifyOrder(Long channelId, PayOrderRespDTO notify) { 209 public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
228 // 校验支付渠道是否有效 210 // 校验支付渠道是否有效
229 PayChannelDO channel = channelService.validPayChannel(channelId); 211 PayChannelDO channel = channelService.validPayChannel(channelId);
230 // 更新支付订单为已支付 212 // 更新支付订单为已支付
231 - TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyPayOrder(channel, notify)); 213 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
232 } 214 }
233 215
234 /** 216 /**
@@ -238,7 +220,7 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -238,7 +220,7 @@ public class PayOrderServiceImpl implements PayOrderService {
238 * @param notify 通知 220 * @param notify 通知
239 */ 221 */
240 @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 222 @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效
241 - public void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) { 223 + public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) {
242 // 情况一:支付成功的回调 224 // 情况一:支付成功的回调
243 if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) { 225 if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
244 notifyOrderSuccess(channel, notify); 226 notifyOrderSuccess(channel, notify);
@@ -254,16 +236,16 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -254,16 +236,16 @@ public class PayOrderServiceImpl implements PayOrderService {
254 236
255 private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { 237 private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
256 // 1. 更新 PayOrderExtensionDO 支付成功 238 // 1. 更新 PayOrderExtensionDO 支付成功
257 - PayOrderExtensionDO orderExtension = updateOrderExtensionSuccess(notify); 239 + PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
258 // 2. 更新 PayOrderDO 支付成功 240 // 2. 更新 PayOrderDO 支付成功
259 - Pair<Boolean, PayOrderDO> order = updateOrderExtensionSuccess(channel, orderExtension, notify);  
260 - if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调 241 + Boolean paid = updateOrderSuccess(channel, orderExtension, notify);
  242 + if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
261 return; 243 return;
262 } 244 }
263 245
264 // 3. 插入支付通知记录 246 // 3. 插入支付通知记录
265 notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() 247 notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
266 - .type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getValue().getId()).build()); 248 + .type(PayNotifyTypeEnum.ORDER.getType()).dataId(orderExtension.getOrderId()).build());
267 } 249 }
268 250
269 /** 251 /**
@@ -272,7 +254,7 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -272,7 +254,7 @@ public class PayOrderServiceImpl implements PayOrderService {
272 * @param notify 通知 254 * @param notify 通知
273 * @return PayOrderExtensionDO 对象 255 * @return PayOrderExtensionDO 对象
274 */ 256 */
275 - private PayOrderExtensionDO updateOrderExtensionSuccess(PayOrderRespDTO notify) { 257 + private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) {
276 // 1. 查询 PayOrderExtensionDO 258 // 1. 查询 PayOrderExtensionDO
277 PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); 259 PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
278 if (orderExtension == null) { 260 if (orderExtension == null) {
@@ -302,11 +284,10 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -302,11 +284,10 @@ public class PayOrderServiceImpl implements PayOrderService {
302 * @param channel 支付渠道 284 * @param channel 支付渠道
303 * @param orderExtension 支付拓展单 285 * @param orderExtension 支付拓展单
304 * @param notify 通知回调 286 * @param notify 通知回调
305 - * @return key:是否之前已经成功回调  
306 - * value:PayOrderDO 对象 287 + * @return 是否之前已经成功回调
307 */ 288 */
308 - private Pair<Boolean, PayOrderDO> updateOrderExtensionSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,  
309 - PayOrderRespDTO notify) { 289 + private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
  290 + PayOrderRespDTO notify) {
310 // 1. 判断 PayOrderDO 是否处于待支付 291 // 1. 判断 PayOrderDO 是否处于待支付
311 PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); 292 PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
312 if (order == null) { 293 if (order == null) {
@@ -315,7 +296,7 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -315,7 +296,7 @@ public class PayOrderServiceImpl implements PayOrderService {
315 if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 296 if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新
316 && Objects.equals(order.getExtensionId(), orderExtension.getId())) { 297 && Objects.equals(order.getExtensionId(), orderExtension.getId())) {
317 log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新]", order.getId()); 298 log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新]", order.getId());
318 - return Pair.of(true, order); 299 + return true;
319 } 300 }
320 if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 301 if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
321 throw exception(ORDER_STATUS_IS_NOT_WAITING); 302 throw exception(ORDER_STATUS_IS_NOT_WAITING);
@@ -333,7 +314,7 @@ public class PayOrderServiceImpl implements PayOrderService { @@ -333,7 +314,7 @@ public class PayOrderServiceImpl implements PayOrderService {
333 throw exception(ORDER_STATUS_IS_NOT_WAITING); 314 throw exception(ORDER_STATUS_IS_NOT_WAITING);
334 } 315 }
335 log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId()); 316 log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId());
336 - return Pair.of(false, order); 317 + return false;
337 } 318 }
338 319
339 private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) { 320 private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) {
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.refund; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.refund;
3 import cn.hutool.core.date.DateUtil; 3 import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.util.RandomUtil; 4 import cn.hutool.core.util.RandomUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 -import cn.iocoder.yudao.framework.pay.config.PayProperties;  
7 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 6 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
8 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 7 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
9 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; 8 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@@ -24,6 +23,7 @@ import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; @@ -24,6 +23,7 @@ import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
24 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 23 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
25 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 24 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
26 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; 25 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
  26 +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
27 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 27 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
28 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 28 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
29 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 29 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
1 package cn.iocoder.yudao.module.pay.service.order; 1 package cn.iocoder.yudao.module.pay.service.order;
2 2
  3 +import cn.hutool.extra.spring.SpringUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.framework.pay.config.PayProperties;  
5 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 5 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
7 -import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; 7 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
8 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 8 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
9 -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 9 +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
  11 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
10 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; 12 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
11 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; 13 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
12 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO; 14 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
13 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO; 15 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
  16 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
14 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; 17 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
15 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; 18 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
16 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 19 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
17 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 20 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
18 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; 21 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
19 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; 22 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
  23 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
  24 +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
20 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 25 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
  26 +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
21 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 27 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
22 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 28 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
23 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 29 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
  30 +import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
  31 +import org.junit.jupiter.api.BeforeEach;
24 import org.junit.jupiter.api.Test; 32 import org.junit.jupiter.api.Test;
  33 +import org.mockito.ArgumentMatcher;
  34 +import org.mockito.MockedStatic;
25 import org.springframework.boot.test.mock.mockito.MockBean; 35 import org.springframework.boot.test.mock.mockito.MockBean;
26 import org.springframework.context.annotation.Import; 36 import org.springframework.context.annotation.Import;
27 37
@@ -30,6 +40,7 @@ import java.time.Duration; @@ -30,6 +40,7 @@ import java.time.Duration;
30 import java.util.List; 40 import java.util.List;
31 41
32 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; 42 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
  43 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
33 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 44 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
34 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 45 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
35 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 46 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -37,19 +48,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -37,19 +48,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
37 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; 48 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
38 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; 49 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
39 import static org.assertj.core.api.Assertions.assertThat; 50 import static org.assertj.core.api.Assertions.assertThat;
40 -import static org.junit.jupiter.api.Assertions.*;  
41 -import static org.mockito.ArgumentMatchers.any;  
42 -import static org.mockito.ArgumentMatchers.eq;  
43 -import static org.mockito.Mockito.mock;  
44 -import static org.mockito.Mockito.when; 51 +import static org.junit.jupiter.api.Assertions.assertEquals;
  52 +import static org.junit.jupiter.api.Assertions.assertNotNull;
  53 +import static org.mockito.ArgumentMatchers.*;
  54 +import static org.mockito.Mockito.*;
45 55
46 /** 56 /**
47 * {@link PayOrderServiceImpl} 的单元测试类 57 * {@link PayOrderServiceImpl} 的单元测试类
48 * 58 *
49 * @author 芋艿 59 * @author 芋艿
50 */ 60 */
51 -@Import({PayOrderServiceImpl.class})  
52 -public class PayOrderServiceTest extends BaseDbUnitTest { 61 +@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class})
  62 +public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
53 63
54 @Resource 64 @Resource
55 private PayOrderServiceImpl orderService; 65 private PayOrderServiceImpl orderService;
@@ -70,6 +80,11 @@ public class PayOrderServiceTest extends BaseDbUnitTest { @@ -70,6 +80,11 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
70 @MockBean 80 @MockBean
71 private PayNotifyService notifyService; 81 private PayNotifyService notifyService;
72 82
  83 + @BeforeEach
  84 + public void setUp() {
  85 + when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1");
  86 + }
  87 +
73 @Test 88 @Test
74 public void testGetOrderPage() { 89 public void testGetOrderPage() {
75 // mock 数据 90 // mock 数据
@@ -237,7 +252,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { @@ -237,7 +252,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
237 public void testSubmitOrder_channelNotFound() { 252 public void testSubmitOrder_channelNotFound() {
238 // mock 数据(order) 253 // mock 数据(order)
239 PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()) 254 PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
240 - .setAppId(1L)); 255 + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
241 orderMapper.insert(order); 256 orderMapper.insert(order);
242 // 准备参数 257 // 准备参数
243 PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()) 258 PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
@@ -257,54 +272,295 @@ public class PayOrderServiceTest extends BaseDbUnitTest { @@ -257,54 +272,295 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
257 272
258 @Test // 调用 unifiedOrder 接口,返回存在渠道错误 273 @Test // 调用 unifiedOrder 接口,返回存在渠道错误
259 public void testSubmitOrder_channelError() { 274 public void testSubmitOrder_channelError() {
260 - // mock 数据(order)  
261 - PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())  
262 - .setAppId(1L));  
263 - orderMapper.insert(order); 275 + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
  276 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  277 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
  278 + .thenReturn(payOrderServiceImpl);
  279 +
  280 + // mock 数据(order)
  281 + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
  282 + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
  283 + orderMapper.insert(order);
  284 + // 准备参数
  285 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
  286 + .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  287 + String userIp = randomString();
  288 + // mock 方法(app)
  289 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  290 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  291 + // mock 方法(channel)
  292 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  293 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  294 + when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
  295 + .thenReturn(channel);
  296 + // mock 方法(client)
  297 + PayClient client = mock(PayClient.class);
  298 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  299 + // mock 方法()
  300 + PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o ->
  301 + o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常"));
  302 + when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
  303 + assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
  304 + assertThat(payOrderUnifiedReqDTO)
  305 + .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
  306 + .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
  307 + reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
  308 + return true;
  309 + }))).thenReturn(unifiedOrderResp);
  310 +
  311 + // 调用,并断言异常
  312 + assertServiceException(() -> orderService.submitOrder(reqVO, userIp),
  313 + ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常");
  314 + // 断言,数据记录(PayOrderExtensionDO)
  315 + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
  316 + assertNotNull(orderExtension);
  317 + assertThat(orderExtension).extracting("no", "orderId").isNotNull();
  318 + assertThat(orderExtension)
  319 + .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
  320 + "channelErrorCode", "channelErrorMsg", "channelNotifyData")
  321 + .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
  322 + PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
  323 + null, null, null);
  324 + }
  325 + }
  326 +
  327 + @Test
  328 + public void testSubmitOrder_success() {
  329 + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
  330 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  331 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
  332 + .thenReturn(payOrderServiceImpl);
  333 +
  334 + // mock 数据(order)
  335 + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
  336 + .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
  337 + orderMapper.insert(order);
  338 + // 准备参数
  339 + PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
  340 + .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  341 + String userIp = randomString();
  342 + // mock 方法(app)
  343 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  344 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  345 + // mock 方法(channel)
  346 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  347 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  348 + when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
  349 + .thenReturn(channel);
  350 + // mock 方法(client)
  351 + PayClient client = mock(PayClient.class);
  352 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  353 + // mock 方法()
  354 + PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
  355 + .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou"));
  356 + when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
  357 + assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
  358 + assertThat(payOrderUnifiedReqDTO)
  359 + .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
  360 + .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
  361 + reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
  362 + return true;
  363 + }))).thenReturn(unifiedOrderResp);
  364 +
  365 + // 调用
  366 + PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp);
  367 + // 断言,数据记录(PayOrderExtensionDO)
  368 + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
  369 + assertNotNull(orderExtension);
  370 + assertThat(orderExtension).extracting("no", "orderId").isNotNull();
  371 + assertThat(orderExtension)
  372 + .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
  373 + "channelErrorCode", "channelErrorMsg", "channelNotifyData")
  374 + .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
  375 + PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
  376 + null, null, null);
  377 + // 断言,返回(PayOrderSubmitRespVO)
  378 + assertThat(result)
  379 + .extracting("status", "displayMode", "displayContent")
  380 + .containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou");
  381 + // 断言,调用
  382 + verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp));
  383 + }
  384 + }
  385 +
  386 + @Test
  387 + public void testNotifyOrder_channelId() {
  388 + PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
  389 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  390 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
  391 + .thenReturn(payOrderServiceImpl);
  392 + // 准备参数
  393 + Long channelId = 10L;
  394 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class);
  395 + // mock 方法(channel)
  396 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  397 + when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
  398 +
  399 + // 调用
  400 + orderService.notifyOrder(channelId, notify);
  401 + // 断言
  402 + verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify));
  403 + }
  404 + }
  405 +
  406 + @Test
  407 + public void testNotifyOrderSuccess_orderExtension_notFound() {
264 // 准备参数 408 // 准备参数
265 - PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())  
266 - .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));  
267 - String userIp = randomString();  
268 - // mock 方法(app)  
269 - PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));  
270 - when(appService.validPayApp(eq(1L))).thenReturn(app);  
271 - // mock 方法(channel)  
272 - PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)  
273 - .setCode(PayChannelEnum.ALIPAY_APP.getCode()));  
274 - when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))  
275 - .thenReturn(channel);  
276 - // mock 方法(client)  
277 - PayClient client = mock(PayClient.class);  
278 - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);  
279 - when(client.unifiedOrder(any(PayOrderUnifiedReqDTO.class))).thenThrow(new NullPointerException()); 409 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  410 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  411 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()));
  412 +
  413 + // 调用,并断言异常
  414 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  415 + ORDER_EXTENSION_NOT_FOUND);
  416 + }
  417 +
  418 + @Test
  419 + public void testNotifyOrderSuccess_orderExtension_closed() {
  420 + // mock 数据(PayOrderExtensionDO)
  421 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  422 + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
  423 + .setNo("P110"));
  424 + orderExtensionMapper.insert(orderExtension);
  425 + // 准备参数
  426 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  427 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  428 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
  429 + .setOutTradeNo("P110"));
280 430
281 // 调用,并断言异常 431 // 调用,并断言异常
282 - assertThrows(NullPointerException.class, () -> orderService.submitOrder(reqVO, userIp));  
283 - // 断言,数据记录(PayOrderExtensionDO)  
284 - PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);  
285 - assertNotNull(orderExtension);  
286 - assertThat(orderExtension).extracting("no", "orderId").isNotNull();  
287 - assertThat(orderExtension)  
288 - .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",  
289 - "channelErrorCode", "channelErrorMsg", "channelNotifyData")  
290 - .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,  
291 - PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),  
292 - null, null, null); 432 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  433 + ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
293 } 434 }
294 435
295 - @Test // 【成功】支付结果为等待中  
296 - public void testSubmitOrder_waitingResult() { 436 + @Test
  437 + public void testNotifyOrderSuccess_order_notFound() {
  438 + // mock 数据(PayOrderExtensionDO)
  439 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  440 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  441 + .setNo("P110"));
  442 + orderExtensionMapper.insert(orderExtension);
  443 + // 准备参数
  444 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  445 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  446 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
  447 + .setOutTradeNo("P110"));
297 448
  449 + // 调用,并断言异常
  450 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  451 + ORDER_NOT_FOUND);
  452 + // 断言 PayOrderExtensionDO :数据更新被回滚
  453 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
298 } 454 }
299 455
300 - @Test // 【成功】支付结果为已完成  
301 - public void testSubmitOrder_successResult() { 456 + @Test
  457 + public void testNotifyOrderSuccess_order_closed() {
  458 + testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus());
  459 + }
302 460
  461 + @Test
  462 + public void testNotifyOrderSuccess_order_refund() {
  463 + testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus());
303 } 464 }
304 465
305 - @Test // 【成功】支付结果为已关闭  
306 - public void testSubmitOrder_closedResult() { 466 + private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) {
  467 + // mock 数据(PayOrderDO)
  468 + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
  469 + orderMapper.insert(order);
  470 + // mock 数据(PayOrderExtensionDO)
  471 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  472 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  473 + .setNo("P110")
  474 + .setOrderId(order.getId()));
  475 + orderExtensionMapper.insert(orderExtension);
  476 + // 准备参数
  477 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  478 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  479 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
  480 + .setOutTradeNo("P110"));
  481 +
  482 + // 调用,并断言异常
  483 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  484 + ORDER_STATUS_IS_NOT_WAITING);
  485 + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
  486 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
  487 + }
307 488
  489 + @Test
  490 + public void testNotifyOrderSuccess_order_paid() {
  491 + // mock 数据(PayOrderDO)
  492 + PayOrderDO order = randomPojo(PayOrderDO.class,
  493 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
  494 + orderMapper.insert(order);
  495 + // mock 数据(PayOrderExtensionDO)
  496 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  497 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  498 + .setNo("P110")
  499 + .setOrderId(order.getId()));
  500 + orderExtensionMapper.insert(orderExtension);
  501 + // 重要:需要将 order 的 extensionId 更新下
  502 + order.setExtensionId(orderExtension.getId());
  503 + orderMapper.updateById(order);
  504 + // 准备参数
  505 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  506 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  507 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
  508 + .setOutTradeNo("P110"));
  509 +
  510 + // 调用,并断言异常
  511 + orderService.notifyOrder(channel, notify);
  512 + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
  513 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
  514 + // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
  515 + assertPojoEquals(order, orderMapper.selectOne(null));
  516 + // 断言,调用
  517 + verify(notifyService, never()).createPayNotifyTask(any(PayNotifyTaskCreateReqDTO.class));
  518 + }
  519 +
  520 + @Test
  521 + public void testNotifyOrderSuccess_order_waiting() {
  522 + // mock 数据(PayOrderDO)
  523 + PayOrderDO order = randomPojo(PayOrderDO.class,
  524 + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
  525 + .setPrice(10));
  526 + orderMapper.insert(order);
  527 + // mock 数据(PayOrderExtensionDO)
  528 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  529 + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
  530 + .setNo("P110")
  531 + .setOrderId(order.getId()));
  532 + orderExtensionMapper.insert(orderExtension);
  533 + // 准备参数
  534 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  535 + .setFeeRate(0.1D));
  536 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  537 + o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
  538 + .setOutTradeNo("P110"));
  539 +
  540 + // 调用,并断言异常
  541 + orderService.notifyOrder(channel, notify);
  542 + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
  543 + orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  544 + .setChannelNotifyData(toJsonString(notify));
  545 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
  546 + "updateTime", "updater");
  547 + // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
  548 + order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  549 + .setChannelId(10L).setChannelCode(channel.getCode())
  550 + .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo())
  551 + .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId())
  552 + .setChannelFeeRate(0.1D).setChannelFeePrice(1);
  553 + assertPojoEquals(order, orderMapper.selectOne(null),
  554 + "updateTime", "updater");
  555 + // 断言,调用
  556 + verify(notifyService).createPayNotifyTask(argThat(new ArgumentMatcher<PayNotifyTaskCreateReqDTO>() {
  557 + @Override
  558 + public boolean matches(PayNotifyTaskCreateReqDTO reqDTO) {
  559 + assertEquals(reqDTO.getType(), PayNotifyTypeEnum.ORDER.getType());
  560 + assertEquals(reqDTO.getDataId(), orderExtension.getOrderId());
  561 + return true;
  562 + }
  563 + }));
308 } 564 }
309 565
310 } 566 }
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
1 package cn.iocoder.yudao.module.pay.service.refund; 1 package cn.iocoder.yudao.module.pay.service.refund;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.framework.pay.config.PayProperties;  
5 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 4 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
6 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 5 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  6 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
8 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; 8 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
9 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; 9 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
11 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; 11 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
12 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 12 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
13 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; 13 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
  14 +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
14 import cn.iocoder.yudao.module.pay.service.app.PayAppService; 15 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
15 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 16 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
16 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 17 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; @@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
29 import static org.junit.jupiter.api.Assertions.assertEquals; 30 import static org.junit.jupiter.api.Assertions.assertEquals;
30 31
31 @Import(PayRefundServiceImpl.class) 32 @Import(PayRefundServiceImpl.class)
32 -public class PayRefundServiceTest extends BaseDbUnitTest { 33 +public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
33 34
34 @Resource 35 @Resource
35 private PayRefundServiceImpl refundService; 36 private PayRefundServiceImpl refundService;
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql
1 DELETE FROM pay_app; 1 DELETE FROM pay_app;
2 DELETE FROM pay_channel; 2 DELETE FROM pay_channel;
3 DELETE FROM pay_order; 3 DELETE FROM pay_order;
  4 +DELETE FROM pay_order_extension;
4 DELETE FROM pay_refund; 5 DELETE FROM pay_refund;
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
@@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS `pay_order` ( @@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS `pay_order` (
44 `channel_fee_price` bigint(20) DEFAULT 0, 44 `channel_fee_price` bigint(20) DEFAULT 0,
45 `status` tinyint(4) NOT NULL, 45 `status` tinyint(4) NOT NULL,
46 `user_ip` varchar(50) NOT NULL, 46 `user_ip` varchar(50) NOT NULL,
47 - `expire_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, 47 + `expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
48 `success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, 48 `success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
49 `notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP, 49 `notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
50 `extension_id` bigint(20) DEFAULT NULL, 50 `extension_id` bigint(20) DEFAULT NULL,
@@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `pay_order_extension` ( @@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `pay_order_extension` (
71 `channel_extras` varchar(1024) NULL DEFAULT NULL, 71 `channel_extras` varchar(1024) NULL DEFAULT NULL,
72 `channel_error_code` varchar(64) NULL, 72 `channel_error_code` varchar(64) NULL,
73 `channel_error_msg` varchar(64) NULL, 73 `channel_error_msg` varchar(64) NULL,
74 - `channel_notify_data` varchar(64) NULL, 74 + `channel_notify_data` varchar(1024) NULL,
75 `creator` varchar(64) NULL DEFAULT '', 75 `creator` varchar(64) NULL DEFAULT '',
76 `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, 76 `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
77 `updater` varchar(64) NULL DEFAULT '', 77 `updater` varchar(64) NULL DEFAULT '',