Commit e585349a77452e4ca23a256df2d5756395eeb8bb

Authored by YunaiV
1 parent 5f839f5b

fix(pay): 校验示例回调与业务单据绑定编号

yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
... ... @@ -131,6 +131,11 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
131 131 order, payOrderId);
132 132 throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
133 133 }
  134 + // 1.3 校验支付单号,与订单绑定的支付单号一致。避免其它支付应用中,相同商户订单编号、金额的支付单被用于串单
  135 + if (ObjectUtil.notEqual(order.getPayOrderId(), payOrderId)) {
  136 + log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({}),请进行处理!]", order, payOrderId);
  137 + throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
  138 + }
134 139  
135 140 // 2. 校验支付订单的合法性
136 141 PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId);
... ...
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImpl.java
... ... @@ -146,6 +146,11 @@ public class PayDemoWithdrawServiceImpl implements PayDemoWithdrawService {
146 146 log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
147 147 throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
148 148 }
  149 + // 1.3 校验转账单编号,与提现单绑定的转账单编号一致。避免其它支付应用、或重新转账前的转账单被用于更新状态
  150 + if (ObjectUtil.notEqual(withdraw.getPayTransferId(), payTransferId)) {
  151 + log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
  152 + throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
  153 + }
149 154  
150 155 // 2. 校验提现单的合法性
151 156 PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId);
... ...
yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.service.demo;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
  5 +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
  6 +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
  7 +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
  8 +import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
  9 +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
  10 +import org.junit.jupiter.api.Test;
  11 +import org.mockito.InjectMocks;
  12 +import org.mockito.Mock;
  13 +
  14 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  15 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
  16 +import static org.mockito.Mockito.*;
  17 +
  18 +/**
  19 + * {@link PayDemoOrderServiceImpl} 的单元测试
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +public class PayDemoOrderServiceImplTest extends BaseMockitoUnitTest {
  24 +
  25 + @InjectMocks
  26 + private PayDemoOrderServiceImpl payDemoOrderService;
  27 +
  28 + @Mock
  29 + private PayOrderApi payOrderApi;
  30 + @Mock
  31 + private PayRefundApi payRefundApi;
  32 + @Mock
  33 + private PayDemoOrderMapper payDemoOrderMapper;
  34 +
  35 + @Test
  36 + public void testUpdateDemoOrderPaid_success() {
  37 + // mock 数据
  38 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(false));
  39 + when(payOrderApi.getOrder(eq(100L))).thenReturn(buildPayOrder(100L, PayOrderStatusEnum.SUCCESS.getStatus(), 10, "1"));
  40 + when(payDemoOrderMapper.updateByIdAndPayed(eq(1L), eq(false), any())).thenReturn(1);
  41 +
  42 + // 调用
  43 + payDemoOrderService.updateDemoOrderPaid(1L, 100L);
  44 + // 断言
  45 + verify(payDemoOrderMapper).updateByIdAndPayed(eq(1L), eq(false),
  46 + argThat(updateObj -> updateObj.getPayStatus() && "mock".equals(updateObj.getPayChannelCode())));
  47 + }
  48 +
  49 + @Test
  50 + public void testUpdateDemoOrderPaid_repeatNotify() {
  51 + // mock 数据:已支付,且支付单号相同
  52 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(true));
  53 +
  54 + // 调用:重复通知,幂等返回
  55 + payDemoOrderService.updateDemoOrderPaid(1L, 100L);
  56 + // 断言
  57 + verifyNoInteractions(payOrderApi);
  58 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  59 + }
  60 +
  61 + @Test
  62 + public void testUpdateDemoOrderPaid_paidPayOrderIdNotMatch() {
  63 + // mock 数据:已支付,但支付单号不同
  64 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(true));
  65 +
  66 + // 调用,并断言异常
  67 + assertServiceException(() -> payDemoOrderService.updateDemoOrderPaid(1L, 200L),
  68 + DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
  69 + verifyNoInteractions(payOrderApi);
  70 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  71 + }
  72 +
  73 + @Test
  74 + public void testUpdateDemoOrderPaid_unpaidPayOrderIdNotBound() {
  75 + // mock 数据:未支付;其它支付应用中,存在相同商户订单编号、金额,且已支付的支付单
  76 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(false));
  77 +
  78 + // 调用,并断言异常
  79 + assertServiceException(() -> payDemoOrderService.updateDemoOrderPaid(1L, 200L),
  80 + DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
  81 + // 断言:不查询支付单,也不更新订单
  82 + verifyNoInteractions(payOrderApi);
  83 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  84 + }
  85 +
  86 + @Test
  87 + public void testUpdateDemoOrderPaid_payOrderNotSuccess() {
  88 + // mock 数据:支付单号一致,但未支付
  89 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(false));
  90 + when(payOrderApi.getOrder(eq(100L))).thenReturn(buildPayOrder(100L, PayOrderStatusEnum.WAITING.getStatus(), 10, "1"));
  91 +
  92 + // 调用,并断言异常
  93 + assertServiceException(() -> payDemoOrderService.updateDemoOrderPaid(1L, 100L),
  94 + DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
  95 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  96 + }
  97 +
  98 + @Test
  99 + public void testUpdateDemoOrderPaid_payPriceNotMatch() {
  100 + // mock 数据:支付单号一致,但金额不同
  101 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(false));
  102 + when(payOrderApi.getOrder(eq(100L))).thenReturn(buildPayOrder(100L, PayOrderStatusEnum.SUCCESS.getStatus(), 1, "1"));
  103 +
  104 + // 调用,并断言异常
  105 + assertServiceException(() -> payDemoOrderService.updateDemoOrderPaid(1L, 100L),
  106 + DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
  107 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  108 + }
  109 +
  110 + @Test
  111 + public void testUpdateDemoOrderPaid_merchantOrderIdNotMatch() {
  112 + // mock 数据:支付单号一致,但商户订单编号不同
  113 + when(payDemoOrderMapper.selectById(eq(1L))).thenReturn(buildOrder(false));
  114 + when(payOrderApi.getOrder(eq(100L))).thenReturn(buildPayOrder(100L, PayOrderStatusEnum.SUCCESS.getStatus(), 10, "2"));
  115 +
  116 + // 调用,并断言异常
  117 + assertServiceException(() -> payDemoOrderService.updateDemoOrderPaid(1L, 100L),
  118 + DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
  119 + verify(payDemoOrderMapper, never()).updateByIdAndPayed(anyLong(), anyBoolean(), any());
  120 + }
  121 +
  122 + private static PayDemoOrderDO buildOrder(boolean payStatus) {
  123 + return new PayDemoOrderDO().setId(1L).setUserId(1L).setSpuId(1L).setSpuName("华为手机")
  124 + .setPrice(10).setPayStatus(payStatus).setPayOrderId(100L);
  125 + }
  126 +
  127 + private static PayOrderRespDTO buildPayOrder(Long id, Integer status, Integer price, String merchantOrderId) {
  128 + return new PayOrderRespDTO().setId(id).setStatus(status).setPrice(price)
  129 + .setMerchantOrderId(merchantOrderId).setChannelCode("mock");
  130 + }
  131 +
  132 +}
... ...
yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.service.demo;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
  5 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
  6 +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
  7 +import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoWithdrawMapper;
  8 +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
  9 +import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum;
  10 +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.mockito.InjectMocks;
  13 +import org.mockito.Mock;
  14 +
  15 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  16 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR;
  17 +import static org.mockito.Mockito.*;
  18 +
  19 +/**
  20 + * {@link PayDemoWithdrawServiceImpl} 的单元测试
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +public class PayDemoWithdrawServiceImplTest extends BaseMockitoUnitTest {
  25 +
  26 + @InjectMocks
  27 + private PayDemoWithdrawServiceImpl payDemoWithdrawService;
  28 +
  29 + @Mock
  30 + private PayDemoWithdrawMapper demoTransferMapper;
  31 + @Mock
  32 + private PayTransferApi payTransferApi;
  33 +
  34 + @Test
  35 + public void testUpdateDemoWithdrawTransferred_success() {
  36 + // mock 数据
  37 + when(demoTransferMapper.selectById(eq(1L))).thenReturn(buildWithdraw(PayDemoWithdrawStatusEnum.WAITING.getStatus()));
  38 + when(payTransferApi.getTransfer(eq(100L))).thenReturn(buildTransfer(100L));
  39 +
  40 + // 调用
  41 + payDemoWithdrawService.updateDemoWithdrawTransferred(1L, 100L);
  42 + // 断言
  43 + verify(demoTransferMapper).updateByIdAndStatus(eq(1L), eq(PayDemoWithdrawStatusEnum.WAITING.getStatus()),
  44 + argThat(updateObj -> PayDemoWithdrawStatusEnum.SUCCESS.getStatus().equals(updateObj.getStatus())));
  45 + }
  46 +
  47 + @Test
  48 + public void testUpdateDemoWithdrawTransferred_repeatNotify() {
  49 + // mock 数据:已结束,且转账单编号相同
  50 + when(demoTransferMapper.selectById(eq(1L))).thenReturn(buildWithdraw(PayDemoWithdrawStatusEnum.SUCCESS.getStatus()));
  51 +
  52 + // 调用:重复通知,幂等返回
  53 + payDemoWithdrawService.updateDemoWithdrawTransferred(1L, 100L);
  54 + // 断言
  55 + verifyNoInteractions(payTransferApi);
  56 + verify(demoTransferMapper, never()).updateByIdAndStatus(anyLong(), anyInt(), any());
  57 + }
  58 +
  59 + @Test
  60 + public void testUpdateDemoWithdrawTransferred_waitingPayTransferIdNotBound() {
  61 + // mock 数据:未结束;其它支付应用、或重新转账前,存在相同商户转账编号、金额、渠道的转账单
  62 + when(demoTransferMapper.selectById(eq(1L))).thenReturn(buildWithdraw(PayDemoWithdrawStatusEnum.WAITING.getStatus()));
  63 +
  64 + // 调用,并断言异常
  65 + assertServiceException(() -> payDemoWithdrawService.updateDemoWithdrawTransferred(1L, 200L),
  66 + DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
  67 + // 断言:不查询转账单,也不更新提现单
  68 + verifyNoInteractions(payTransferApi);
  69 + verify(demoTransferMapper, never()).updateByIdAndStatus(anyLong(), anyInt(), any());
  70 + }
  71 +
  72 + private static PayDemoWithdrawDO buildWithdraw(Integer status) {
  73 + return new PayDemoWithdrawDO().setId(1L).setSubject("提现").setPrice(10).setStatus(status)
  74 + .setPayTransferId(100L).setTransferChannelCode(PayChannelEnum.WALLET.getCode());
  75 + }
  76 +
  77 + private static PayTransferRespDTO buildTransfer(Long id) {
  78 + return new PayTransferRespDTO().setId(id).setStatus(PayTransferStatusEnum.SUCCESS.getStatus())
  79 + .setPrice(10).setMerchantTransferId("1").setChannelCode(PayChannelEnum.WALLET.getCode());
  80 + }
  81 +
  82 +}
... ...