From 6aea461e8d99f2cfaa00255a3e127faec10e7e2d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Jul 2023 23:20:52 +0800 Subject: [PATCH] =?UTF-8?q?mall=20+=20pay=EF=BC=9A=201.=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=20PayRefundServiceTest=20=E7=9A=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/refund/PayRefundServiceImpl.java | 5 +- .../service/refund/PayRefundServiceTest.java | 266 +++++++++++++++++- 2 files changed, 266 insertions(+), 5 deletions(-) diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java index 1b8d05458..0823af6a2 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java @@ -212,7 +212,7 @@ public class PayRefundServiceImpl implements PayRefundService { } } - public void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { + private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { // 1.1 查询 PayRefundDO PayRefundDO refund = refundMapper.selectByAppIdAndNo( channel.getAppId(), notify.getOutRefundNo()); @@ -246,8 +246,7 @@ public class PayRefundServiceImpl implements PayRefundService { .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()); } - @Transactional(rollbackFor = Exception.class) - public void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { + private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { // 1.1 查询 PayRefundDO PayRefundDO refund = refundMapper.selectByAppIdAndNo( channel.getAppId(), notify.getOutRefundNo()); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java index 8ef92270f..d89f84434 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; @@ -17,12 +18,14 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,6 +38,7 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; @@ -42,8 +46,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -439,4 +442,263 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { } } + @Test + public void testNotifyRefund() { + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) + .thenReturn(payRefundServiceImpl); + + // 准备参数 + Long channelId = 10L; + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + refundService.notifyRefund(channelId, refundRespDTO); + // 断言 + verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO)); + } + } + + @Test + public void testNotifyRefundSuccess_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_NOT_FOUND); + } + + @Test + public void testNotifyRefundSuccess_isSuccess() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund 没有更新,因为已经退款成功 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundSuccess_failure() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.FAILURE.getStatus())); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyRefundSuccess_updateOrderException() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + // mock 方法(order + 更新异常) + doThrow(new RuntimeException()).when(orderService) + .updateOrderRefundPrice(eq(100L), eq(23)); + + // 调用,并断言异常 + assertThrows(RuntimeException.class, () -> refundService.notifyRefund(channel, refundRespDTO)); + // 断言,refund 没有更新,因为事务回滚了 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundSuccess_success() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund + refund.setSuccessTime(refundRespDTO.getSuccessTime()) + .setChannelRefundNo(refundRespDTO.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(refundRespDTO)); + assertPojoEquals(refund, refundMapper.selectById(refund.getId()), + "updateTime", "updater"); + // 断言,调用 + verify(orderService).updateOrderRefundPrice(eq(100L), eq(23)); + verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build())); + } + + @Test + public void testNotifyRefundFailure_notFound() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_NOT_FOUND); + } + + @Test + public void testNotifyRefundFailure_isFailure() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 退款失败) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.FAILURE.getStatus())); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund 没有更新,因为已经退款失败 + assertPojoEquals(refund, refundMapper.selectById(refund.getId())); + } + + @Test + public void testNotifyRefundFailure_isSuccess() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())); + refundMapper.insert(refund); + + // 调用,并断言异常 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO), + REFUND_STATUS_IS_NOT_WAITING); + } + + @Test + public void testNotifyRefundFailure_success() { + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L)); + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class, + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100")); + // mock 数据(refund + 已支付) + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100") + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderId(100L).setRefundPrice(23)); + refundMapper.insert(refund); + + // 调用 + refundService.notifyRefund(channel, refundRespDTO); + // 断言,refund + refund.setChannelRefundNo(refundRespDTO.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) + .setChannelNotifyData(toJsonString(refundRespDTO)) + .setChannelErrorCode(refundRespDTO.getChannelErrorCode()) + .setChannelErrorMsg(refundRespDTO.getChannelErrorMsg()); + assertPojoEquals(refund, refundMapper.selectById(refund.getId()), + "updateTime", "updater"); + // 断言,调用 + verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build())); + } + + @Test + public void testSyncRefund_notFound() { + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus())); + refundMapper.insert(refund); + + // 调用 + int count = refundService.syncRefund(); + // 断言 + assertEquals(count, 0); + } + + @Test + public void testSyncRefund_waiting() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.WAITING.getStatus()), 0); + } + + @Test + public void testSyncRefund_success() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.SUCCESS.getStatus()), 1); + } + + @Test + public void testSyncRefund_failure() { + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.FAILURE.getStatus()), 1); + } + + private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) { + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class); + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class))) + .thenReturn(payRefundServiceImpl); + + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderNo("P110").setNo("R220")); + refundMapper.insert(refund); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 返回指定状态) + PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status)); + when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO); + // mock 方法(channel) + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); + when(channelService.validPayChannel(eq(10L))).thenReturn(channel); + + // 调用 + return refundService.syncRefund(); + } + } + + @Test + public void testSyncRefund_exception() { + // 准备参数 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L) + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setOrderNo("P110").setNo("R220")); + refundMapper.insert(refund); + // mock 方法(client) + PayClient client = mock(PayClient.class); + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + // mock 方法(client 抛出异常) + when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException()); + + // 调用 + int count = refundService.syncRefund(); + // 断言 + assertEquals(count, 0); + } + }