Commit e585349a77452e4ca23a256df2d5756395eeb8bb
1 parent
5f839f5b
fix(pay): 校验示例回调与业务单据绑定编号
Showing
4 changed files
with
224 additions
and
0 deletions
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 | +} | ... | ... |