Commit b54f7e9256b7efb4bfb0d5c6f950ad59be5ed6d1
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
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
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 '', |