Commit e27ec2fd50146abca9c5604e9f1c197e22d22341

Authored by zhijiantianya@gmail.com
1 parent da529851

by gateway:

1. 完善部分 refund 单元测试
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
1 package cn.iocoder.yudao.module.pay.service.refund; 1 package cn.iocoder.yudao.module.pay.service.refund;
2 2
3 -import cn.hutool.core.date.DateUtil;  
4 -import cn.hutool.core.util.RandomUtil; 3 +import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 5 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
7 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
@@ -16,9 +15,9 @@ import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; @@ -16,9 +15,9 @@ import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
16 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; 15 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
17 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; 16 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
18 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 17 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
19 -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;  
20 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 18 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
21 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; 19 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
  20 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
22 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; 21 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
23 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 22 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
24 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 23 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
@@ -35,12 +34,11 @@ import org.springframework.transaction.annotation.Transactional; @@ -35,12 +34,11 @@ import org.springframework.transaction.annotation.Transactional;
35 import org.springframework.validation.annotation.Validated; 34 import org.springframework.validation.annotation.Validated;
36 35
37 import javax.annotation.Resource; 36 import javax.annotation.Resource;
38 -import java.time.LocalDateTime;  
39 import java.util.List; 37 import java.util.List;
40 38
41 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 39 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
42 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; 40 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
43 -import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND; 41 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
44 42
45 /** 43 /**
46 * 退款订单 Service 实现类 44 * 退款订单 Service 实现类
@@ -60,6 +58,8 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -60,6 +58,8 @@ public class PayRefundServiceImpl implements PayRefundService {
60 58
61 @Resource 59 @Resource
62 private PayRefundMapper refundMapper; 60 private PayRefundMapper refundMapper;
  61 + @Resource
  62 + private PayNoRedisDAO noRedisDAO;
63 63
64 @Resource 64 @Resource
65 private PayOrderService orderService; 65 private PayOrderService orderService;
@@ -112,8 +112,9 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -112,8 +112,9 @@ public class PayRefundServiceImpl implements PayRefundService {
112 } 112 }
113 113
114 // 2.1 插入退款单 114 // 2.1 插入退款单
  115 + String no = noRedisDAO.generate(payProperties.getRefundNoPrefix());
115 refund = PayRefundConvert.INSTANCE.convert(reqDTO) 116 refund = PayRefundConvert.INSTANCE.convert(reqDTO)
116 - .setNo(generateRefundNo()).setOrderId(order.getId()) 117 + .setNo(no).setOrderId(order.getId())
117 .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) 118 .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode())
118 // 商户相关的字段 119 // 商户相关的字段
119 .setNotifyUrl(app.getRefundNotifyUrl()) 120 .setNotifyUrl(app.getRefundNotifyUrl())
@@ -123,20 +124,27 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -123,20 +124,27 @@ public class PayRefundServiceImpl implements PayRefundService {
123 .setStatus(PayRefundStatusEnum.WAITING.getStatus()) 124 .setStatus(PayRefundStatusEnum.WAITING.getStatus())
124 .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); 125 .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice());
125 refundMapper.insert(refund); 126 refundMapper.insert(refund);
126 - // 2.2 向渠道发起退款申请  
127 - PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId());  
128 - PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO()  
129 - .setPayPrice(order.getPrice())  
130 - .setRefundPrice(reqDTO.getPrice())  
131 - .setOutTradeNo(orderExtension.getNo())  
132 - .setOutRefundNo(refund.getNo())  
133 - .setNotifyUrl(genChannelRefundNotifyUrl(channel))  
134 - .setReason(reqDTO.getReason());  
135 - PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO);  
136 - // 2.3 处理退款返回  
137 - notifyRefund(channel, refundRespDTO); 127 + try {
  128 + // 2.2 向渠道发起退款申请
  129 + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO()
  130 + .setPayPrice(order.getPrice())
  131 + .setRefundPrice(reqDTO.getPrice())
  132 + .setOutTradeNo(order.getNo())
  133 + .setOutRefundNo(refund.getNo())
  134 + .setNotifyUrl(genChannelRefundNotifyUrl(channel))
  135 + .setReason(reqDTO.getReason());
  136 + PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO);
  137 + // 2.3 处理退款返回
  138 + getSelf().notifyRefund(channel, refundRespDTO);
  139 + } catch (Throwable e) {
  140 + // 注意:这里仅打印异常,不进行抛出。
  141 + // 原因是:虽然调用支付渠道进行退款发生异常(网络请求超时),实际退款成功。这个结果,后续通过退款回调、或者退款轮询补偿可以拿到。
  142 + // 最终,在异常的情况下,支付中心会异步回调业务的退款回调接口,提供退款结果
  143 + log.error("[createPayRefund][退款 id({}) requestDTO({}) 发生异常]",
  144 + refund.getId(), reqDTO, e);
  145 + }
138 146
139 - // 成功在 退款回调中处理 147 + // 返回退款编号
140 return refund.getId(); 148 return refund.getId();
141 } 149 }
142 150
@@ -151,12 +159,12 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -151,12 +159,12 @@ public class PayRefundServiceImpl implements PayRefundService {
151 if (order == null) { 159 if (order == null) {
152 throw exception(ErrorCodeConstants.ORDER_NOT_FOUND); 160 throw exception(ErrorCodeConstants.ORDER_NOT_FOUND);
153 } 161 }
154 - // 校验状态,必须是支付状态  
155 - if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {  
156 - throw exception(ErrorCodeConstants.ORDER_STATUS_IS_NOT_SUCCESS); 162 + // 校验状态,必须是已支付、或者已退款
  163 + if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
  164 + throw exception(ORDER_REFUND_FAIL_STATUS_ERROR);
157 } 165 }
158 166
159 - // 校验金额 退款金额不能大于原定的金额 167 + // 校验金额,退款金额不能大于原定的金额
160 if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){ 168 if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
161 throw exception(ErrorCodeConstants.REFUND_PRICE_EXCEED); 169 throw exception(ErrorCodeConstants.REFUND_PRICE_EXCEED);
162 } 170 }
@@ -178,38 +186,22 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -178,38 +186,22 @@ public class PayRefundServiceImpl implements PayRefundService {
178 return payProperties.getRefundNotifyUrl() + "/" + channel.getId(); 186 return payProperties.getRefundNotifyUrl() + "/" + channel.getId();
179 } 187 }
180 188
181 - private String generateRefundNo() {  
182 -// wx  
183 -// 2014  
184 -// 10  
185 -// 27  
186 -// 20  
187 -// 09  
188 -// 39  
189 -// 5522657  
190 -// a690389285100  
191 - // 目前的算法  
192 - // 时间序列,年月日时分秒 14 位  
193 - // 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整  
194 - return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列  
195 - RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒  
196 - ;  
197 - }  
198 -  
199 @Override 189 @Override
200 public void notifyRefund(Long channelId, PayRefundRespDTO notify) { 190 public void notifyRefund(Long channelId, PayRefundRespDTO notify) {
201 // 校验支付渠道是否有效 191 // 校验支付渠道是否有效
202 - channelService.validPayChannel(channelId);  
203 - // 通知结果  
204 -  
205 - // 校验支付渠道是否有效  
206 PayChannelDO channel = channelService.validPayChannel(channelId); 192 PayChannelDO channel = channelService.validPayChannel(channelId);
207 // 更新退款订单 193 // 更新退款订单
208 - TenantUtils.execute(channel.getTenantId(), () -> notifyRefund(channel, notify)); 194 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify));
209 } 195 }
210 196
211 - // TODO 芋艿:事务问题  
212 - private void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { 197 + /**
  198 + * 通知并更新订单的退款结果
  199 + *
  200 + * @param channel 支付渠道
  201 + * @param notify 通知
  202 + */
  203 + @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
  204 + public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
213 // 情况一:退款成功 205 // 情况一:退款成功
214 if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { 206 if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
215 notifyRefundSuccess(channel, notify); 207 notifyRefundSuccess(channel, notify);
@@ -226,14 +218,14 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -226,14 +218,14 @@ public class PayRefundServiceImpl implements PayRefundService {
226 PayRefundDO refund = refundMapper.selectByAppIdAndNo( 218 PayRefundDO refund = refundMapper.selectByAppIdAndNo(
227 channel.getAppId(), notify.getOutRefundNo()); 219 channel.getAppId(), notify.getOutRefundNo());
228 if (refund == null) { 220 if (refund == null) {
229 - throw exception(ErrorCodeConstants.REFUND_NOT_FOUND); 221 + throw exception(REFUND_NOT_FOUND);
230 } 222 }
231 if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 223 if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
232 log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId()); 224 log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId());
233 return; 225 return;
234 } 226 }
235 if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { 227 if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) {
236 - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); 228 + throw exception(REFUND_STATUS_IS_NOT_WAITING);
237 } 229 }
238 // 1.2 更新 PayRefundDO 230 // 1.2 更新 PayRefundDO
239 PayRefundDO updateRefundObj = new PayRefundDO() 231 PayRefundDO updateRefundObj = new PayRefundDO()
@@ -243,7 +235,7 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -243,7 +235,7 @@ public class PayRefundServiceImpl implements PayRefundService {
243 .setChannelNotifyData(toJsonString(notify)); 235 .setChannelNotifyData(toJsonString(notify));
244 int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); 236 int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj);
245 if (updateCounts == 0) { // 校验状态,必须是等待状态 237 if (updateCounts == 0) { // 校验状态,必须是等待状态
246 - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); 238 + throw exception(REFUND_STATUS_IS_NOT_WAITING);
247 } 239 }
248 log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId()); 240 log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId());
249 241
@@ -261,14 +253,14 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -261,14 +253,14 @@ public class PayRefundServiceImpl implements PayRefundService {
261 PayRefundDO refund = refundMapper.selectByAppIdAndNo( 253 PayRefundDO refund = refundMapper.selectByAppIdAndNo(
262 channel.getAppId(), notify.getOutRefundNo()); 254 channel.getAppId(), notify.getOutRefundNo());
263 if (refund == null) { 255 if (refund == null) {
264 - throw exception(ErrorCodeConstants.REFUND_NOT_FOUND); 256 + throw exception(REFUND_NOT_FOUND);
265 } 257 }
266 if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 258 if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
267 log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId()); 259 log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId());
268 return; 260 return;
269 } 261 }
270 if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { 262 if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) {
271 - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); 263 + throw exception(REFUND_STATUS_IS_NOT_WAITING);
272 } 264 }
273 // 1.2 更新 PayRefundDO 265 // 1.2 更新 PayRefundDO
274 PayRefundDO updateRefundObj = new PayRefundDO() 266 PayRefundDO updateRefundObj = new PayRefundDO()
@@ -278,7 +270,7 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -278,7 +270,7 @@ public class PayRefundServiceImpl implements PayRefundService {
278 .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); 270 .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg());
279 int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); 271 int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj);
280 if (updateCounts == 0) { // 校验状态,必须是等待状态 272 if (updateCounts == 0) { // 校验状态,必须是等待状态
281 - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); 273 + throw exception(REFUND_STATUS_IS_NOT_WAITING);
282 } 274 }
283 log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId()); 275 log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId());
284 276
@@ -287,4 +279,13 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -287,4 +279,13 @@ public class PayRefundServiceImpl implements PayRefundService {
287 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()); 279 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
288 } 280 }
289 281
  282 + /**
  283 + * 获得自身的代理对象,解决 AOP 生效问题
  284 + *
  285 + * @return 自己
  286 + */
  287 + private PayRefundServiceImpl getSelf() {
  288 + return SpringUtil.getBean(getClass());
  289 + }
  290 +
290 } 291 }
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
@@ -30,7 +30,6 @@ import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -30,7 +30,6 @@ import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
30 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; 30 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
31 import org.junit.jupiter.api.BeforeEach; 31 import org.junit.jupiter.api.BeforeEach;
32 import org.junit.jupiter.api.Test; 32 import org.junit.jupiter.api.Test;
33 -import org.mockito.ArgumentMatcher;  
34 import org.mockito.MockedStatic; 33 import org.mockito.MockedStatic;
35 import org.springframework.boot.test.mock.mockito.MockBean; 34 import org.springframework.boot.test.mock.mockito.MockBean;
36 import org.springframework.context.annotation.Import; 35 import org.springframework.context.annotation.Import;
@@ -44,8 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString @@ -44,8 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
44 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 43 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
45 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 44 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
46 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 45 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
47 -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;  
48 -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; 46 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
49 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; 47 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
50 import static org.assertj.core.api.Assertions.assertThat; 48 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.junit.jupiter.api.Assertions.assertEquals; 49 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -86,6 +84,51 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { @@ -86,6 +84,51 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
86 } 84 }
87 85
88 @Test 86 @Test
  87 + public void testGetOrder_id() {
  88 + // mock 数据(PayOrderDO)
  89 + PayOrderDO order = randomPojo(PayOrderDO.class);
  90 + orderMapper.insert(order);
  91 + // 准备参数
  92 + Long id = order.getId();
  93 +
  94 + // 调用
  95 + PayOrderDO dbOrder = orderService.getOrder(id);
  96 + // 断言
  97 + assertPojoEquals(dbOrder, order);
  98 + }
  99 +
  100 + @Test
  101 + public void testGetOrder_appIdAndMerchantOrderId() {
  102 + // mock 数据(PayOrderDO)
  103 + PayOrderDO order = randomPojo(PayOrderDO.class);
  104 + orderMapper.insert(order);
  105 + // 准备参数
  106 + Long appId = order.getAppId();
  107 + String merchantOrderId = order.getMerchantOrderId();
  108 +
  109 + // 调用
  110 + PayOrderDO dbOrder = orderService.getOrder(appId, merchantOrderId);
  111 + // 断言
  112 + assertPojoEquals(dbOrder, order);
  113 + }
  114 +
  115 + @Test
  116 + public void testGetOrderCountByAppId() {
  117 + // mock 数据(PayOrderDO)
  118 + PayOrderDO order01 = randomPojo(PayOrderDO.class);
  119 + orderMapper.insert(order01);
  120 + PayOrderDO order02 = randomPojo(PayOrderDO.class);
  121 + orderMapper.insert(order02);
  122 + // 准备参数
  123 + Long appId = order01.getAppId();
  124 +
  125 + // 调用
  126 + Long count = orderService.getOrderCountByAppId(appId);
  127 + // 断言
  128 + assertEquals(count, 1L);
  129 + }
  130 +
  131 + @Test
89 public void testGetOrderPage() { 132 public void testGetOrderPage() {
90 // mock 数据 133 // mock 数据
91 PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 134 PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
@@ -350,7 +393,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { @@ -350,7 +393,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
350 // mock 方法(client) 393 // mock 方法(client)
351 PayClient client = mock(PayClient.class); 394 PayClient client = mock(PayClient.class);
352 when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); 395 when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
353 - // mock 方法() 396 + // mock 方法(支付渠道的调用)
354 PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null) 397 PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
355 .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou")); 398 .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou"));
356 when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { 399 when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
@@ -553,14 +596,193 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { @@ -553,14 +596,193 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
553 assertPojoEquals(order, orderMapper.selectOne(null), 596 assertPojoEquals(order, orderMapper.selectOne(null),
554 "updateTime", "updater"); 597 "updateTime", "updater");
555 // 断言,调用 598 // 断言,调用
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 - } 599 + verify(notifyService).createPayNotifyTask(argThat(reqDTO -> {
  600 + assertEquals(reqDTO.getType(), PayNotifyTypeEnum.ORDER.getType());
  601 + assertEquals(reqDTO.getDataId(), orderExtension.getOrderId());
  602 + return true;
563 })); 603 }));
564 } 604 }
565 605
  606 + @Test
  607 + public void testNotifyOrderClosed_orderExtension_notFound() {
  608 + // 准备参数
  609 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  610 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  611 + o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus()));
  612 +
  613 + // 调用,并断言异常
  614 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  615 + ORDER_EXTENSION_NOT_FOUND);
  616 + }
  617 +
  618 + @Test
  619 + public void testNotifyOrderClosed_orderExtension_closed() {
  620 + // mock 数据(PayOrderExtensionDO)
  621 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  622 + o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
  623 + .setNo("P110"));
  624 + orderExtensionMapper.insert(orderExtension);
  625 + // 准备参数
  626 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  627 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  628 + o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
  629 + .setOutTradeNo("P110"));
  630 +
  631 + // 调用,并断言
  632 + orderService.notifyOrder(channel, notify);
  633 + // 断言 PayOrderExtensionDO :数据未更新,因为它是 CLOSED
  634 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
  635 + }
  636 +
  637 + @Test
  638 + public void testNotifyOrderClosed_orderExtension_paid() {
  639 + // mock 数据(PayOrderExtensionDO)
  640 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  641 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  642 + .setNo("P110"));
  643 + orderExtensionMapper.insert(orderExtension);
  644 + // 准备参数
  645 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  646 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  647 + o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
  648 + .setOutTradeNo("P110"));
  649 +
  650 + // 调用,并断言
  651 + orderService.notifyOrder(channel, notify);
  652 + // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
  653 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
  654 + }
  655 +
  656 + @Test
  657 + public void testNotifyOrderClosed_orderExtension_refund() {
  658 + // mock 数据(PayOrderExtensionDO)
  659 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  660 + o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  661 + .setNo("P110"));
  662 + orderExtensionMapper.insert(orderExtension);
  663 + // 准备参数
  664 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  665 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  666 + o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
  667 + .setOutTradeNo("P110"));
  668 +
  669 + // 调用,并断言异常
  670 + assertServiceException(() -> orderService.notifyOrder(channel, notify),
  671 + ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
  672 + }
  673 +
  674 + @Test
  675 + public void testNotifyOrderClosed_orderExtension_waiting() {
  676 + // mock 数据(PayOrderExtensionDO)
  677 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
  678 + o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
  679 + .setNo("P110"));
  680 + orderExtensionMapper.insert(orderExtension);
  681 + // 准备参数
  682 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  683 + PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
  684 + o -> o.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus())
  685 + .setOutTradeNo("P110"));
  686 +
  687 + // 调用
  688 + orderService.notifyOrder(channel, notify);
  689 + // 断言 PayOrderExtensionDO
  690 + orderExtension.setStatus(PayOrderStatusEnum.CLOSED.getStatus()).setChannelNotifyData(toJsonString(notify))
  691 + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg());
  692 + assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
  693 + "updateTime", "updater");
  694 + }
  695 +
  696 + @Test
  697 + public void testUpdateOrderRefundPrice_notFound() {
  698 + // 准备参数
  699 + Long id = randomLongId();
  700 + Integer incrRefundPrice = randomInteger();
  701 +
  702 + // 调用,并断言异常
  703 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
  704 + ORDER_NOT_FOUND);
  705 + }
  706 +
  707 + @Test
  708 + public void testUpdateOrderRefundPrice_waiting() {
  709 + testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.WAITING.getStatus());
  710 + }
  711 +
  712 + @Test
  713 + public void testUpdateOrderRefundPrice_closed() {
  714 + testUpdateOrderRefundPrice_waitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus());
  715 + }
  716 +
  717 + private void testUpdateOrderRefundPrice_waitingOrClosed(Integer status) {
  718 + // mock 数据(PayOrderDO)
  719 + PayOrderDO order = randomPojo(PayOrderDO.class,
  720 + o -> o.setStatus(status));
  721 + orderMapper.insert(order);
  722 + // 准备参数
  723 + Long id = order.getId();
  724 + Integer incrRefundPrice = randomInteger();
  725 +
  726 + // 调用,并断言异常
  727 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
  728 + ORDER_REFUND_FAIL_STATUS_ERROR);
  729 + }
  730 +
  731 + @Test
  732 + public void testUpdateOrderRefundPrice_priceExceed() {
  733 + // mock 数据(PayOrderDO)
  734 + PayOrderDO order = randomPojo(PayOrderDO.class,
  735 + o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
  736 + .setRefundPrice(1).setPrice(10));
  737 + orderMapper.insert(order);
  738 + // 准备参数
  739 + Long id = order.getId();
  740 + Integer incrRefundPrice = 10;
  741 +
  742 + // 调用,并断言异常
  743 + assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice),
  744 + REFUND_PRICE_EXCEED);
  745 + }
  746 +
  747 + @Test
  748 + public void testUpdateOrderRefundPrice_refund() {
  749 + testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.REFUND.getStatus());
  750 + }
  751 +
  752 + @Test
  753 + public void testUpdateOrderRefundPrice_success() {
  754 + testUpdateOrderRefundPrice_refundOrSuccess(PayOrderStatusEnum.SUCCESS.getStatus());
  755 + }
  756 +
  757 + private void testUpdateOrderRefundPrice_refundOrSuccess(Integer status) {
  758 + // mock 数据(PayOrderDO)
  759 + PayOrderDO order = randomPojo(PayOrderDO.class,
  760 + o -> o.setStatus(status).setRefundPrice(1).setPrice(10));
  761 + orderMapper.insert(order);
  762 + // 准备参数
  763 + Long id = order.getId();
  764 + Integer incrRefundPrice = 8;
  765 +
  766 + // 调用
  767 + orderService.updateOrderRefundPrice(id, incrRefundPrice);
  768 + // 断言
  769 + order.setRefundPrice(9).setStatus(PayOrderStatusEnum.REFUND.getStatus());
  770 + assertPojoEquals(order, orderMapper.selectOne(null),
  771 + "updateTime", "updater");
  772 + }
  773 +
  774 + @Test
  775 + public void testGetOrderExtension() {
  776 + // mock 数据(PayOrderExtensionDO)
  777 + PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class);
  778 + orderExtensionMapper.insert(orderExtension);
  779 + // 准备参数
  780 + Long id = orderExtension.getId();
  781 +
  782 + // 调用
  783 + PayOrderExtensionDO dbOrderExtension = orderService.getOrderExtension(id);
  784 + // 断言
  785 + assertPojoEquals(dbOrderExtension, orderExtension);
  786 + }
  787 +
566 } 788 }
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.core.client.PayClient;
4 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 5 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
5 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 6 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.BaseDbAndRedisUnitTest;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 8 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
8 -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; 9 +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
9 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; 10 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
  11 +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
  12 +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
  13 +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
10 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 14 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
11 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; 15 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
  16 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
12 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 17 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
13 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; 18 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
14 import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; 19 import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
@@ -24,12 +29,25 @@ import javax.annotation.Resource; @@ -24,12 +29,25 @@ import javax.annotation.Resource;
24 import java.time.LocalDateTime; 29 import java.time.LocalDateTime;
25 import java.util.List; 30 import java.util.List;
26 31
  32 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  33 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
27 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 34 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
28 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 35 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  36 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
29 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 37 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  38 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
  39 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
30 import static org.junit.jupiter.api.Assertions.assertEquals; 40 import static org.junit.jupiter.api.Assertions.assertEquals;
  41 +import static org.mockito.ArgumentMatchers.eq;
  42 +import static org.mockito.Mockito.mock;
  43 +import static org.mockito.Mockito.when;
31 44
32 -@Import(PayRefundServiceImpl.class) 45 +/**
  46 + * {@link PayRefundServiceImpl} 的单元测试类
  47 + *
  48 + * @author 芋艿
  49 + */
  50 +@Import({PayRefundServiceImpl.class, PayNoRedisDAO.class})
33 public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { 51 public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
34 52
35 @Resource 53 @Resource
@@ -56,45 +74,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -56,45 +74,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
56 // mock 数据 74 // mock 数据
57 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 75 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
58 o.setAppId(1L); 76 o.setAppId(1L);
59 - o.setChannelId(1L);  
60 o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); 77 o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
61 - o.setOrderId(1L);  
62 - o.setNo("OT0000001");  
63 o.setMerchantOrderId("MOT0000001"); 78 o.setMerchantOrderId("MOT0000001");
64 o.setMerchantRefundId("MRF0000001"); 79 o.setMerchantRefundId("MRF0000001");
65 - o.setNotifyUrl("https://www.cancanzi.com");  
66 o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); 80 o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
67 - o.setPayPrice(100);  
68 - o.setRefundPrice(500);  
69 - o.setReason("就是想退款了,你有意见吗");  
70 - o.setUserIp("127.0.0.1");  
71 o.setChannelOrderNo("CH0000001"); 81 o.setChannelOrderNo("CH0000001");
72 o.setChannelRefundNo("CHR0000001"); 82 o.setChannelRefundNo("CHR0000001");
73 - o.setChannelErrorCode("");  
74 - o.setChannelErrorMsg("");  
75 - o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15));  
76 - o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10));  
77 - o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35)); 83 + o.setCreateTime(buildTime(2021, 1, 10));
78 }); 84 });
79 refundMapper.insert(dbRefund); 85 refundMapper.insert(dbRefund);
80 // 测试 appId 不匹配 86 // 测试 appId 不匹配
81 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); 87 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
82 // 测试 channelCode 不匹配 88 // 测试 channelCode 不匹配
83 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); 89 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
84 - // 测试 merchantRefundNo 不匹配  
85 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112"))); 90 + // 测试 merchantOrderId 不匹配
  91 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString())));
  92 + // 测试 merchantRefundId 不匹配
  93 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString())));
  94 + // 测试 channelOrderNo 不匹配
  95 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString())));
  96 + // 测试 channelRefundNo 不匹配
  97 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString())));
86 // 测试 status 不匹配 98 // 测试 status 不匹配
87 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); 99 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
88 // 测试 createTime 不匹配 100 // 测试 createTime 不匹配
89 - refundMapper.insert(cloneIgnoreId(dbRefund, o ->  
90 - o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10)))); 101 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1))));
91 // 准备参数 102 // 准备参数
92 PayRefundPageReqVO reqVO = new PayRefundPageReqVO(); 103 PayRefundPageReqVO reqVO = new PayRefundPageReqVO();
93 reqVO.setAppId(1L); 104 reqVO.setAppId(1L);
94 reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); 105 reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
  106 + reqVO.setMerchantOrderId("MOT0000001");
95 reqVO.setMerchantRefundId("MRF0000001"); 107 reqVO.setMerchantRefundId("MRF0000001");
96 - reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());  
97 - reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)})); 108 + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
  109 + reqVO.setChannelOrderNo("CH0000001");
  110 + reqVO.setChannelRefundNo("CHR0000001");
  111 + reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11));
98 112
99 // 调用 113 // 调用
100 PageResult<PayRefundDO> pageResult = refundService.getRefundPage(reqVO); 114 PageResult<PayRefundDO> pageResult = refundService.getRefundPage(reqVO);
@@ -109,45 +123,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -109,45 +123,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
109 // mock 数据 123 // mock 数据
110 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 124 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
111 o.setAppId(1L); 125 o.setAppId(1L);
112 - o.setChannelId(1L);  
113 o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); 126 o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
114 - o.setOrderId(1L);  
115 - o.setNo("OT0000001");  
116 o.setMerchantOrderId("MOT0000001"); 127 o.setMerchantOrderId("MOT0000001");
117 o.setMerchantRefundId("MRF0000001"); 128 o.setMerchantRefundId("MRF0000001");
118 - o.setNotifyUrl("https://www.cancanzi.com");  
119 o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); 129 o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
120 - o.setPayPrice(100);  
121 - o.setRefundPrice(500);  
122 - o.setReason("就是想退款了,你有意见吗");  
123 - o.setUserIp("127.0.0.1");  
124 o.setChannelOrderNo("CH0000001"); 130 o.setChannelOrderNo("CH0000001");
125 o.setChannelRefundNo("CHR0000001"); 131 o.setChannelRefundNo("CHR0000001");
126 - o.setChannelErrorCode("");  
127 - o.setChannelErrorMsg("");  
128 - o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15));  
129 - o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10));  
130 - o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35)); 132 + o.setCreateTime(buildTime(2021, 1, 10));
131 }); 133 });
132 refundMapper.insert(dbRefund); 134 refundMapper.insert(dbRefund);
133 // 测试 appId 不匹配 135 // 测试 appId 不匹配
134 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); 136 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
135 // 测试 channelCode 不匹配 137 // 测试 channelCode 不匹配
136 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); 138 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
137 - // 测试 merchantRefundNo 不匹配  
138 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112"))); 139 + // 测试 merchantOrderId 不匹配
  140 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString())));
  141 + // 测试 merchantRefundId 不匹配
  142 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString())));
  143 + // 测试 channelOrderNo 不匹配
  144 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString())));
  145 + // 测试 channelRefundNo 不匹配
  146 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString())));
139 // 测试 status 不匹配 147 // 测试 status 不匹配
140 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); 148 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
141 // 测试 createTime 不匹配 149 // 测试 createTime 不匹配
142 - refundMapper.insert(cloneIgnoreId(dbRefund, o ->  
143 - o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10))));  
144 - 150 + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1))));
145 // 准备参数 151 // 准备参数
146 PayRefundExportReqVO reqVO = new PayRefundExportReqVO(); 152 PayRefundExportReqVO reqVO = new PayRefundExportReqVO();
147 reqVO.setAppId(1L); 153 reqVO.setAppId(1L);
148 reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); 154 reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
149 - reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());  
150 - reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)})); 155 + reqVO.setMerchantOrderId("MOT0000001");
  156 + reqVO.setMerchantRefundId("MRF0000001");
  157 + reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
  158 + reqVO.setChannelOrderNo("CH0000001");
  159 + reqVO.setChannelRefundNo("CHR0000001");
  160 + reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11));
151 161
152 // 调用 162 // 调用
153 List<PayRefundDO> list = refundService.getRefundList(reqVO); 163 List<PayRefundDO> list = refundService.getRefundList(reqVO);
@@ -156,4 +166,151 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -156,4 +166,151 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
156 assertPojoEquals(dbRefund, list.get(0)); 166 assertPojoEquals(dbRefund, list.get(0));
157 } 167 }
158 168
  169 + @Test
  170 + public void testCreateRefund_orderNotFound() {
  171 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  172 + o -> o.setAppId(1L));
  173 + // mock 方法(app)
  174 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  175 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  176 +
  177 + // 调用,并断言异常
  178 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  179 + ORDER_NOT_FOUND);
  180 + }
  181 +
  182 + @Test
  183 + public void testCreateRefund_orderWaiting() {
  184 + testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.WAITING.getStatus());
  185 + }
  186 +
  187 + @Test
  188 + public void testCreateRefund_orderClosed() {
  189 + testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus());
  190 + }
  191 +
  192 + private void testCreateRefund_orderWaitingOrClosed(Integer status) {
  193 + // 准备参数
  194 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  195 + o -> o.setAppId(1L).setMerchantOrderId("100"));
  196 + // mock 方法(app)
  197 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  198 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  199 + // mock 数据(order)
  200 + PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
  201 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  202 +
  203 + // 调用,并断言异常
  204 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  205 + ORDER_REFUND_FAIL_STATUS_ERROR);
  206 + }
  207 +
  208 + @Test
  209 + public void testCreateRefund_refundPriceExceed() {
  210 + // 准备参数
  211 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  212 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
  213 + // mock 方法(app)
  214 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  215 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  216 + // mock 数据(order)
  217 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  218 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  219 + .setPrice(10).setRefundPrice(1));
  220 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  221 +
  222 + // 调用,并断言异常
  223 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  224 + REFUND_PRICE_EXCEED);
  225 + }
  226 +
  227 + @Test
  228 + public void testCreateRefund_orderHasRefunding() {
  229 + // 准备参数
  230 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  231 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
  232 + // mock 方法(app)
  233 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  234 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  235 + // mock 数据(order)
  236 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  237 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  238 + .setPrice(10).setRefundPrice(1));
  239 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  240 + // mock 数据(refund 在退款中)
  241 + PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
  242 + o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
  243 + refundMapper.insert(refund);
  244 +
  245 + // 调用,并断言异常
  246 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  247 + REFUND_PRICE_EXCEED);
  248 + }
  249 +
  250 + @Test
  251 + public void testCreateRefund_channelNotFound() {
  252 + // 准备参数
  253 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  254 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9));
  255 + // mock 方法(app)
  256 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  257 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  258 + // mock 数据(order)
  259 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  260 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  261 + .setPrice(10).setRefundPrice(1)
  262 + .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  263 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  264 + // mock 方法(channel)
  265 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  266 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  267 + when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
  268 +
  269 + // 调用,并断言异常
  270 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  271 + CHANNEL_NOT_FOUND);
  272 + }
  273 +
  274 + @Test
  275 + public void testCreateRefund_refundExists() {
  276 + // 准备参数
  277 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  278 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
  279 + .setReason("测试退款"));
  280 + // mock 方法(app)
  281 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  282 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  283 + // mock 数据(order)
  284 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  285 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  286 + .setPrice(10).setRefundPrice(1)
  287 + .setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  288 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  289 + // mock 方法(channel)
  290 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  291 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  292 + when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
  293 + // mock 方法(client)
  294 + PayClient client = mock(PayClient.class);
  295 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  296 + // mock 数据(refund 已存在)
  297 + PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
  298 + o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
  299 + refundMapper.insert(refund);
  300 +
  301 + // 调用,并断言异常
  302 + assertServiceException(() -> refundService.createPayRefund(reqDTO),
  303 + REFUND_EXISTS);
  304 + }
  305 +
  306 + @Test
  307 + public void testCreateRefund_invokeException() {
  308 +
  309 + }
  310 +
  311 + @Test
  312 + public void testCreateRefund_invokeSuccess() {
  313 +
  314 + }
  315 +
159 } 316 }
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
@@ -82,29 +82,25 @@ CREATE TABLE IF NOT EXISTS `pay_order_extension` ( @@ -82,29 +82,25 @@ CREATE TABLE IF NOT EXISTS `pay_order_extension` (
82 82
83 CREATE TABLE IF NOT EXISTS `pay_refund` ( 83 CREATE TABLE IF NOT EXISTS `pay_refund` (
84 "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, 84 "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  85 + `no` varchar(64) NOT NULL,
85 `app_id` bigint(20) NOT NULL, 86 `app_id` bigint(20) NOT NULL,
86 `channel_id` bigint(20) NOT NULL, 87 `channel_id` bigint(20) NOT NULL,
87 `channel_code` varchar(32) NOT NULL, 88 `channel_code` varchar(32) NOT NULL,
88 `order_id` bigint(20) NOT NULL, 89 `order_id` bigint(20) NOT NULL,
89 - `trade_no` varchar(64) NOT NULL,  
90 `merchant_order_id` varchar(64) NOT NULL, 90 `merchant_order_id` varchar(64) NOT NULL,
91 - `merchant_refund_no` varchar(64) NOT NULL, 91 + `merchant_refund_id` varchar(64) NOT NULL,
92 `notify_url` varchar(1024) NOT NULL, 92 `notify_url` varchar(1024) NOT NULL,
93 - `notify_status` tinyint(4) NOT NULL,  
94 `status` tinyint(4) NOT NULL, 93 `status` tinyint(4) NOT NULL,
95 - `type` tinyint(4) NOT NULL,  
96 - `pay_amount` bigint(20) NOT NULL,  
97 - `refund_amount` bigint(20) NOT NULL, 94 + `pay_price` bigint(20) NOT NULL,
  95 + `refund_price` bigint(20) NOT NULL,
98 `reason` varchar(256) NOT NULL, 96 `reason` varchar(256) NOT NULL,
99 `user_ip` varchar(50) NULL DEFAULT NULL, 97 `user_ip` varchar(50) NULL DEFAULT NULL,
100 `channel_order_no` varchar(64) NOT NULL, 98 `channel_order_no` varchar(64) NOT NULL,
101 `channel_refund_no` varchar(64) NULL DEFAULT NULL, 99 `channel_refund_no` varchar(64) NULL DEFAULT NULL,
  100 + `success_time` datetime(0) NULL DEFAULT NULL,
102 `channel_error_code` varchar(128) NULL DEFAULT NULL, 101 `channel_error_code` varchar(128) NULL DEFAULT NULL,
103 `channel_error_msg` varchar(256) NULL DEFAULT NULL, 102 `channel_error_msg` varchar(256) NULL DEFAULT NULL,
104 - `channel_extras` varchar(1024) NULL DEFAULT NULL,  
105 - `expire_time` datetime(0) NULL DEFAULT NULL,  
106 - `success_time` datetime(0) NULL DEFAULT NULL,  
107 - `notify_time` datetime(0) NULL DEFAULT NULL, 103 + `channel_notify_data` varchar(1024) NULL,
108 `creator` varchar(64) NULL DEFAULT '', 104 `creator` varchar(64) NULL DEFAULT '',
109 `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, 105 `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
110 `updater` varchar(64) NULL DEFAULT '', 106 `updater` varchar(64) NULL DEFAULT '',