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,6 +131,11 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { | ||
| 131 | order, payOrderId); | 131 | order, payOrderId); |
| 132 | throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); | 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 | // 2. 校验支付订单的合法性 | 140 | // 2. 校验支付订单的合法性 |
| 136 | PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId); | 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,6 +146,11 @@ public class PayDemoWithdrawServiceImpl implements PayDemoWithdrawService { | ||
| 146 | log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId); | 146 | log.error("[updateDemoWithdrawStatus][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId); |
| 147 | throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR); | 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 | // 2. 校验提现单的合法性 | 155 | // 2. 校验提现单的合法性 |
| 151 | PayTransferRespDTO payTransfer = validateDemoTransferStatusCanUpdate(withdraw, payTransferId); | 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 | +} |