Commit e27ec2fd50146abca9c5604e9f1c197e22d22341
1 parent
da529851
by gateway:
1. 完善部分 refund 单元测试
Showing
4 changed files
with
492 additions
and
116 deletions
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
| 1 | 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 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 5 | import cn.iocoder.yudao.framework.pay.core.client.PayClient; |
| 7 | 6 | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; |
| ... | ... | @@ -16,9 +15,9 @@ import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; |
| 16 | 15 | import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; |
| 17 | 16 | import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; |
| 18 | 17 | import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; |
| 19 | -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; | |
| 20 | 18 | import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; |
| 21 | 19 | import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; |
| 20 | +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; | |
| 22 | 21 | import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; |
| 23 | 22 | import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; |
| 24 | 23 | import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; |
| ... | ... | @@ -35,12 +34,11 @@ import org.springframework.transaction.annotation.Transactional; |
| 35 | 34 | import org.springframework.validation.annotation.Validated; |
| 36 | 35 | |
| 37 | 36 | import javax.annotation.Resource; |
| 38 | -import java.time.LocalDateTime; | |
| 39 | 37 | import java.util.List; |
| 40 | 38 | |
| 41 | 39 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 42 | 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 | 44 | * 退款订单 Service 实现类 |
| ... | ... | @@ -60,6 +58,8 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 60 | 58 | |
| 61 | 59 | @Resource |
| 62 | 60 | private PayRefundMapper refundMapper; |
| 61 | + @Resource | |
| 62 | + private PayNoRedisDAO noRedisDAO; | |
| 63 | 63 | |
| 64 | 64 | @Resource |
| 65 | 65 | private PayOrderService orderService; |
| ... | ... | @@ -112,8 +112,9 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | 114 | // 2.1 插入退款单 |
| 115 | + String no = noRedisDAO.generate(payProperties.getRefundNoPrefix()); | |
| 115 | 116 | refund = PayRefundConvert.INSTANCE.convert(reqDTO) |
| 116 | - .setNo(generateRefundNo()).setOrderId(order.getId()) | |
| 117 | + .setNo(no).setOrderId(order.getId()) | |
| 117 | 118 | .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) |
| 118 | 119 | // 商户相关的字段 |
| 119 | 120 | .setNotifyUrl(app.getRefundNotifyUrl()) |
| ... | ... | @@ -123,20 +124,27 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 123 | 124 | .setStatus(PayRefundStatusEnum.WAITING.getStatus()) |
| 124 | 125 | .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); |
| 125 | 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 | 148 | return refund.getId(); |
| 141 | 149 | } |
| 142 | 150 | |
| ... | ... | @@ -151,12 +159,12 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 151 | 159 | if (order == null) { |
| 152 | 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 | 168 | if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){ |
| 161 | 169 | throw exception(ErrorCodeConstants.REFUND_PRICE_EXCEED); |
| 162 | 170 | } |
| ... | ... | @@ -178,38 +186,22 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 178 | 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 | 189 | @Override |
| 200 | 190 | public void notifyRefund(Long channelId, PayRefundRespDTO notify) { |
| 201 | 191 | // 校验支付渠道是否有效 |
| 202 | - channelService.validPayChannel(channelId); | |
| 203 | - // 通知结果 | |
| 204 | - | |
| 205 | - // 校验支付渠道是否有效 | |
| 206 | 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 | 206 | if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { |
| 215 | 207 | notifyRefundSuccess(channel, notify); |
| ... | ... | @@ -226,14 +218,14 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 226 | 218 | PayRefundDO refund = refundMapper.selectByAppIdAndNo( |
| 227 | 219 | channel.getAppId(), notify.getOutRefundNo()); |
| 228 | 220 | if (refund == null) { |
| 229 | - throw exception(ErrorCodeConstants.REFUND_NOT_FOUND); | |
| 221 | + throw exception(REFUND_NOT_FOUND); | |
| 230 | 222 | } |
| 231 | 223 | if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 |
| 232 | 224 | log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId()); |
| 233 | 225 | return; |
| 234 | 226 | } |
| 235 | 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 | 230 | // 1.2 更新 PayRefundDO |
| 239 | 231 | PayRefundDO updateRefundObj = new PayRefundDO() |
| ... | ... | @@ -243,7 +235,7 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 243 | 235 | .setChannelNotifyData(toJsonString(notify)); |
| 244 | 236 | int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); |
| 245 | 237 | if (updateCounts == 0) { // 校验状态,必须是等待状态 |
| 246 | - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); | |
| 238 | + throw exception(REFUND_STATUS_IS_NOT_WAITING); | |
| 247 | 239 | } |
| 248 | 240 | log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId()); |
| 249 | 241 | |
| ... | ... | @@ -261,14 +253,14 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 261 | 253 | PayRefundDO refund = refundMapper.selectByAppIdAndNo( |
| 262 | 254 | channel.getAppId(), notify.getOutRefundNo()); |
| 263 | 255 | if (refund == null) { |
| 264 | - throw exception(ErrorCodeConstants.REFUND_NOT_FOUND); | |
| 256 | + throw exception(REFUND_NOT_FOUND); | |
| 265 | 257 | } |
| 266 | 258 | if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 |
| 267 | 259 | log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId()); |
| 268 | 260 | return; |
| 269 | 261 | } |
| 270 | 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 | 265 | // 1.2 更新 PayRefundDO |
| 274 | 266 | PayRefundDO updateRefundObj = new PayRefundDO() |
| ... | ... | @@ -278,7 +270,7 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 278 | 270 | .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); |
| 279 | 271 | int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); |
| 280 | 272 | if (updateCounts == 0) { // 校验状态,必须是等待状态 |
| 281 | - throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING); | |
| 273 | + throw exception(REFUND_STATUS_IS_NOT_WAITING); | |
| 282 | 274 | } |
| 283 | 275 | log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId()); |
| 284 | 276 | |
| ... | ... | @@ -287,4 +279,13 @@ public class PayRefundServiceImpl implements PayRefundService { |
| 287 | 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 | 30 | import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; |
| 31 | 31 | import org.junit.jupiter.api.BeforeEach; |
| 32 | 32 | import org.junit.jupiter.api.Test; |
| 33 | -import org.mockito.ArgumentMatcher; | |
| 34 | 33 | import org.mockito.MockedStatic; |
| 35 | 34 | import org.springframework.boot.test.mock.mockito.MockBean; |
| 36 | 35 | import org.springframework.context.annotation.Import; |
| ... | ... | @@ -44,8 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString |
| 44 | 43 | import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; |
| 45 | 44 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; |
| 46 | 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 | 47 | import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; |
| 50 | 48 | import static org.assertj.core.api.Assertions.assertThat; |
| 51 | 49 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| ... | ... | @@ -86,6 +84,51 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { |
| 86 | 84 | } |
| 87 | 85 | |
| 88 | 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 | 132 | public void testGetOrderPage() { |
| 90 | 133 | // mock 数据 |
| 91 | 134 | PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到 |
| ... | ... | @@ -350,7 +393,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { |
| 350 | 393 | // mock 方法(client) |
| 351 | 394 | PayClient client = mock(PayClient.class); |
| 352 | 395 | when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); |
| 353 | - // mock 方法() | |
| 396 | + // mock 方法(支付渠道的调用) | |
| 354 | 397 | PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null) |
| 355 | 398 | .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou")); |
| 356 | 399 | when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> { |
| ... | ... | @@ -553,14 +596,193 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { |
| 553 | 596 | assertPojoEquals(order, orderMapper.selectOne(null), |
| 554 | 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 | 1 | package cn.iocoder.yudao.module.pay.service.refund; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.framework.pay.core.client.PayClient; | |
| 4 | 5 | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; |
| 5 | 6 | import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; |
| 6 | 7 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; |
| 7 | 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 | 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 | 14 | import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; |
| 11 | 15 | import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; |
| 16 | +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; | |
| 12 | 17 | import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; |
| 13 | 18 | import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; |
| 14 | 19 | import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; |
| ... | ... | @@ -24,12 +29,25 @@ import javax.annotation.Resource; |
| 24 | 29 | import java.time.LocalDateTime; |
| 25 | 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 | 34 | import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; |
| 28 | 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 | 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 | 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 | 51 | public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { |
| 34 | 52 | |
| 35 | 53 | @Resource |
| ... | ... | @@ -56,45 +74,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { |
| 56 | 74 | // mock 数据 |
| 57 | 75 | PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 |
| 58 | 76 | o.setAppId(1L); |
| 59 | - o.setChannelId(1L); | |
| 60 | 77 | o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); |
| 61 | - o.setOrderId(1L); | |
| 62 | - o.setNo("OT0000001"); | |
| 63 | 78 | o.setMerchantOrderId("MOT0000001"); |
| 64 | 79 | o.setMerchantRefundId("MRF0000001"); |
| 65 | - o.setNotifyUrl("https://www.cancanzi.com"); | |
| 66 | 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 | 81 | o.setChannelOrderNo("CH0000001"); |
| 72 | 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 | 85 | refundMapper.insert(dbRefund); |
| 80 | 86 | // 测试 appId 不匹配 |
| 81 | 87 | refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); |
| 82 | 88 | // 测试 channelCode 不匹配 |
| 83 | 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 | 98 | // 测试 status 不匹配 |
| 87 | 99 | refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); |
| 88 | 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 | 103 | PayRefundPageReqVO reqVO = new PayRefundPageReqVO(); |
| 93 | 104 | reqVO.setAppId(1L); |
| 94 | 105 | reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode()); |
| 106 | + reqVO.setMerchantOrderId("MOT0000001"); | |
| 95 | 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 | 114 | PageResult<PayRefundDO> pageResult = refundService.getRefundPage(reqVO); |
| ... | ... | @@ -109,45 +123,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { |
| 109 | 123 | // mock 数据 |
| 110 | 124 | PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 |
| 111 | 125 | o.setAppId(1L); |
| 112 | - o.setChannelId(1L); | |
| 113 | 126 | o.setChannelCode(PayChannelEnum.WX_PUB.getCode()); |
| 114 | - o.setOrderId(1L); | |
| 115 | - o.setNo("OT0000001"); | |
| 116 | 127 | o.setMerchantOrderId("MOT0000001"); |
| 117 | 128 | o.setMerchantRefundId("MRF0000001"); |
| 118 | - o.setNotifyUrl("https://www.cancanzi.com"); | |
| 119 | 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 | 130 | o.setChannelOrderNo("CH0000001"); |
| 125 | 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 | 134 | refundMapper.insert(dbRefund); |
| 133 | 135 | // 测试 appId 不匹配 |
| 134 | 136 | refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L))); |
| 135 | 137 | // 测试 channelCode 不匹配 |
| 136 | 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 | 147 | // 测试 status 不匹配 |
| 140 | 148 | refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); |
| 141 | 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 | 152 | PayRefundExportReqVO reqVO = new PayRefundExportReqVO(); |
| 147 | 153 | reqVO.setAppId(1L); |
| 148 | 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 | 163 | List<PayRefundDO> list = refundService.getRefundList(reqVO); |
| ... | ... | @@ -156,4 +166,151 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { |
| 156 | 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 | 82 | |
| 83 | 83 | CREATE TABLE IF NOT EXISTS `pay_refund` ( |
| 84 | 84 | "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| 85 | + `no` varchar(64) NOT NULL, | |
| 85 | 86 | `app_id` bigint(20) NOT NULL, |
| 86 | 87 | `channel_id` bigint(20) NOT NULL, |
| 87 | 88 | `channel_code` varchar(32) NOT NULL, |
| 88 | 89 | `order_id` bigint(20) NOT NULL, |
| 89 | - `trade_no` varchar(64) NOT NULL, | |
| 90 | 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 | 92 | `notify_url` varchar(1024) NOT NULL, |
| 93 | - `notify_status` tinyint(4) NOT NULL, | |
| 94 | 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 | 96 | `reason` varchar(256) NOT NULL, |
| 99 | 97 | `user_ip` varchar(50) NULL DEFAULT NULL, |
| 100 | 98 | `channel_order_no` varchar(64) NOT NULL, |
| 101 | 99 | `channel_refund_no` varchar(64) NULL DEFAULT NULL, |
| 100 | + `success_time` datetime(0) NULL DEFAULT NULL, | |
| 102 | 101 | `channel_error_code` varchar(128) NULL DEFAULT NULL, |
| 103 | 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 | 104 | `creator` varchar(64) NULL DEFAULT '', |
| 109 | 105 | `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 110 | 106 | `updater` varchar(64) NULL DEFAULT '', | ... | ... |