增加 pay 支付服务

This commit is contained in:
YunaiV
2023-07-27 19:55:15 +08:00
parent 9ba06ec07e
commit c92f1c44a6
152 changed files with 10914 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
package cn.iocoder.yudao.module.pay.service.app;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.Map;
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.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;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static java.util.Collections.singleton;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
/**
* {@link PayAppServiceImpl} 的单元测试
*
* @author aquan
*/
@Import(PayAppServiceImpl.class)
public class PayAppServiceTest extends BaseDbUnitTest {
@Resource
private PayAppServiceImpl appService;
@Resource
private PayAppMapper appMapper;
@MockBean
private PayOrderService orderService;
@MockBean
private PayRefundService refundService;
@Test
public void testCreateApp_success() {
// 准备参数
PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o ->
o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()))
.setOrderNotifyUrl(randomURL())
.setRefundNotifyUrl(randomURL()));
// 调用
Long appId = appService.createApp(reqVO);
// 断言
assertNotNull(appId);
PayAppDO app = appMapper.selectById(appId);
assertPojoEquals(reqVO, app);
}
@Test
public void testUpdateApp_success() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class);
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setOrderNotifyUrl(randomURL()).setRefundNotifyUrl(randomURL());
o.setId(dbApp.getId()); // 设置更新的 ID
});
// 调用
appService.updateApp(reqVO);
// 校验是否更新正确
PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, app);
}
@Test
public void testUpdateApp_notExists() {
// 准备参数
PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o ->
o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
// 调用, 并断言异常
assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_FOUND);
}
@Test
public void testUpdateAppStatus() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
Integer status = CommonStatusEnum.ENABLE.getStatus();
// 调用
appService.updateAppStatus(id, status);
// 断言
PayAppDO app = appMapper.selectById(id); // 获取最新的
assertEquals(status, app.getStatus());
}
@Test
public void testDeleteApp_success() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class);
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// 调用
appService.deleteApp(id);
// 校验数据不存在了
assertNull(appMapper.selectById(id));
}
@Test
public void testDeleteApp_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> appService.deleteApp(id), APP_NOT_FOUND);
}
@Test
public void testDeleteApp_existOrder() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class);
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// mock 订单有订单
when(orderService.getOrderCountByAppId(eq(id))).thenReturn(10L);
// 调用, 并断言异常
assertServiceException(() -> appService.deleteApp(id), APP_EXIST_ORDER_CANT_DELETE);
}
@Test
public void testDeleteApp_existRefund() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class);
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// mock 订单有订单
when(refundService.getRefundCountByAppId(eq(id))).thenReturn(10L);
// 调用, 并断言异常
assertServiceException(() -> appService.deleteApp(id), APP_EXIST_REFUND_CANT_DELETE);
}
@Test
public void testApp() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class);
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// 调用
PayAppDO app = appService.getApp(id);
// 校验数据一致
assertPojoEquals(app, dbApp);
}
@Test
public void testAppMap() {
// mock 数据
PayAppDO dbApp01 = randomPojo(PayAppDO.class);
appMapper.insert(dbApp01);// @Sql: 先插入出一条存在的数据
PayAppDO dbApp02 = randomPojo(PayAppDO.class);
appMapper.insert(dbApp02);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp01.getId();
// 调用
Map<Long, PayAppDO> appMap = appService.getAppMap(singleton(id));
// 校验数据一致
assertEquals(1, appMap.size());
assertPojoEquals(dbApp01, appMap.get(id));
}
@Test
public void testGetAppPage() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
o.setName("灿灿姐的杂货铺");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(buildTime(2021,11,20));
});
appMapper.insert(dbApp);
// 测试 name 不匹配
appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺")));
// 测试 status 不匹配
appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配
appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21))));
// 准备参数
PayAppPageReqVO reqVO = new PayAppPageReqVO();
reqVO.setName("灿灿姐的杂货铺");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime(buildBetweenTime(2021, 11, 19, 2021, 11, 21));
// 调用
PageResult<PayAppDO> pageResult = appService.getAppPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbApp, pageResult.getList().get(0));
}
@Test
public void testValidPayApp_success() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class,
o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// 调用
PayAppDO app = appService.validPayApp(id);
// 校验数据一致
assertPojoEquals(app, dbApp);
}
@Test
public void testValidPayApp_notFound() {
assertServiceException(() -> appService.validPayApp(randomLongId()), APP_NOT_FOUND);
}
@Test
public void testValidPayApp_disable() {
// mock 数据
PayAppDO dbApp = randomPojo(PayAppDO.class,
o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbApp.getId();
// 调用,并断言异常
assertServiceException(() -> appService.validPayApp(id), APP_IS_DISABLE);
}
}

View File

@@ -0,0 +1,365 @@
package cn.iocoder.yudao.module.pay.service.channel;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
@Import({PayChannelServiceImpl.class})
public class PayChannelServiceTest extends BaseDbUnitTest {
private static final String ALIPAY_SERVER_URL = "https://openapi.alipay.com/gateway.do";
@Resource
private PayChannelServiceImpl channelService;
@Resource
private PayChannelMapper channelMapper;
@MockBean
private PayClientFactory payClientFactory;
@MockBean
private Validator validator;
@BeforeEach
public void setUp() {
channelService.setChannelCache(null);
}
@Test
public void testInitLocalCache() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
o -> o.setConfig(randomWxPayClientConfig()));
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 调用
channelService.initLocalCache();
// 校验缓存
assertEquals(1, channelService.getChannelCache().size());
assertEquals(dbChannel, channelService.getChannelCache().get(0));
}
@Test
public void testRefreshLocalCache() {
// mock 数据 01
PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
o -> o.setConfig(randomWxPayClientConfig()).setUpdateTime(addTime(Duration.ofMinutes(-2))));
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
channelService.initLocalCache();
// mock 数据 02
PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class,
o -> o.setConfig(randomWxPayClientConfig()));
channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
// 调用
channelService.refreshLocalCache();
// 校验缓存
assertEquals(2, channelService.getChannelCache().size());
assertEquals(dbChannel, channelService.getChannelCache().get(0));
assertEquals(dbChannel02, channelService.getChannelCache().get(1));
}
@Test
public void testCreateChannel_success() {
// 准备参数
WxPayClientConfig config = randomWxPayClientConfig();
PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
o.setStatus(randomCommonStatus());
o.setCode(PayChannelEnum.WX_PUB.getCode());
o.setConfig(JsonUtils.toJsonString(config));
});
// 调用
Long channelId = channelService.createChannel(reqVO);
// 校验记录的属性是否正确
PayChannelDO channel = channelMapper.selectById(channelId);
assertPojoEquals(reqVO, channel, "config");
assertPojoEquals(config, channel.getConfig());
// 校验缓存
assertEquals(1, channelService.getChannelCache().size());
assertEquals(channel, channelService.getChannelCache().get(0));
}
@Test
public void testCreateChannel_exists() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
o -> o.setConfig(randomWxPayClientConfig()));
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
o.setAppId(dbChannel.getAppId());
o.setCode(dbChannel.getCode());
});
// 调用, 并断言异常
assertServiceException(() -> channelService.createChannel(reqVO), CHANNEL_EXIST_SAME_CHANNEL_ERROR);
}
@Test
public void testUpdateChannel_success() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
AlipayPayClientConfig config = randomAlipayPayClientConfig();
PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
o.setId(dbChannel.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
o.setConfig(JsonUtils.toJsonString(config));
});
// 调用
channelService.updateChannel(reqVO);
// 校验是否更新正确
PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, channel, "config");
assertPojoEquals(config, channel.getConfig());
// 校验缓存
assertEquals(1, channelService.getChannelCache().size());
assertEquals(channel, channelService.getChannelCache().get(0));
}
@Test
public void testUpdateChannel_notExists() {
// 准备参数
AlipayPayClientConfig payClientPublicKeyConfig = randomAlipayPayClientConfig();
PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
});
// 调用, 并断言异常
assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_FOUND);
}
@Test
public void testDeleteChannel_success() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbChannel.getId();
// 调用
channelService.deleteChannel(id);
// 校验数据不存在了
assertNull(channelMapper.selectById(id));
// 校验缓存
assertEquals(0, channelService.getChannelCache().size());
}
@Test
public void testDeleteChannel_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_FOUND);
}
@Test
public void testGetChannel() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbChannel.getId();
// 调用
PayChannelDO channel = channelService.getChannel(id);
// 校验是否更新正确
assertPojoEquals(dbChannel, channel);
}
@Test
public void testGetChannelListByAppIds() {
// mock 数据
PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
});
channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据
PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.WX_PUB.getCode());
o.setConfig(randomWxPayClientConfig());
});
channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
// 准备参数
Long appId = dbChannel01.getAppId();
// 调用
List<PayChannelDO> channels = channelService.getChannelListByAppIds(Collections.singleton(appId));
// 校验是否更新正确
assertEquals(1, channels.size());
assertPojoEquals(dbChannel01, channels.get(0));
}
@Test
public void testGetChannelByAppIdAndCode() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long appId = dbChannel.getAppId();
String code = dbChannel.getCode();;
// 调用
PayChannelDO channel = channelService.getChannelByAppIdAndCode(appId, code);
// 断言
assertPojoEquals(channel, dbChannel);
}
@Test
public void testValidPayChannel_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_NOT_FOUND);
}
@Test
public void testValidPayChannel_isDisable() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbChannel.getId();
// 调用, 并断言异常
assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_IS_DISABLE);
}
@Test
public void testValidPayChannel_success() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbChannel.getId();
// 调用
PayChannelDO channel = channelService.validPayChannel(id);
// 断言异常
assertPojoEquals(channel, dbChannel);
}
@Test
public void testValidPayChannel_appIdAndCode() {
// mock 数据
PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
// 准备参数
Long appId = dbChannel.getAppId();
String code = dbChannel.getCode();
// 调用
PayChannelDO channel = channelService.validPayChannel(appId, code);
// 断言异常
assertPojoEquals(channel, dbChannel);
}
@Test
public void testGetEnableChannelList() {
// 准备参数
Long appId = randomLongId();
// mock 数据 01enable 不匹配)
PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
});
channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据
// mock 数据 02appId 不匹配)
PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
// mock 数据 03
PayChannelDO dbChannel03 = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setAppId(appId);
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
channelMapper.insert(dbChannel03);// @Sql: 先插入出一条存在的数据
// 调用
List<PayChannelDO> channel = channelService.getEnableChannelList(appId);
// 断言异常
assertPojoEquals(channel, dbChannel03);
}
public WxPayClientConfig randomWxPayClientConfig() {
return new WxPayClientConfig()
.setAppId(randomString())
.setMchId(randomString())
.setApiVersion(WxPayClientConfig.API_VERSION_V2)
.setMchKey(randomString());
}
public AlipayPayClientConfig randomAlipayPayClientConfig() {
return new AlipayPayClientConfig()
.setServerUrl(randomURL())
.setAppId(randomString())
.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
.setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY)
.setPrivateKey(randomString())
.setAlipayPublicKey(randomString());
}
}

View File

@@ -0,0 +1,351 @@
package cn.iocoder.yudao.module.pay.service.notify;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
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.notify.PayNotifyLogMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper;
import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundServiceImpl;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
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.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link PayRefundServiceImpl} 的单元测试类
*
* @author 芋艿
*/
@Import({PayJobConfiguration.class, PayNotifyServiceImpl.class, PayNotifyLockRedisDAO.class})
public class PayNotifyServiceTest extends BaseDbUnitTest {
@Resource
private PayNotifyServiceImpl notifyService;
@MockBean
private PayOrderService orderService;
@MockBean
private PayRefundService refundService;
@Resource
private PayNotifyTaskMapper notifyTaskMapper;
@Resource
private PayNotifyLogMapper notifyLogMapper;
@MockBean
private RedissonClient redissonClient;
@Test
public void testCreatePayNotifyTask_order() {
PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class);
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class)))
.thenReturn(payNotifyService);
// 准备参数
Integer type = PayNotifyTypeEnum.ORDER.getType();
Long dataId = 1L;
// mock 方法(order)
PayOrderDO order = randomPojo(PayOrderDO.class);
when(orderService.getOrder(eq(1L))).thenReturn(order);
// mock 方法lock
mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增
// 调用
notifyService.createPayNotifyTask(type, dataId);
// 断言task
PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null);
assertNotNull(dbTask.getNextNotifyTime());
assertThat(dbTask)
.extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes",
"appId", "merchantOrderId", "notifyUrl")
.containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9,
order.getAppId(), order.getMerchantOrderId(), order.getNotifyUrl());
// 断言,调用
verify(payNotifyService).executeNotify0(eq(dbTask));
}
}
@Test
public void testCreatePayNotifyTask_refund() {
PayNotifyServiceImpl payNotifyService = mock(PayNotifyServiceImpl.class);
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayNotifyServiceImpl.class)))
.thenReturn(payNotifyService);
// 准备参数
Integer type = PayNotifyTypeEnum.REFUND.getType();
Long dataId = 1L;
// mock 方法(refund)
PayRefundDO refund = randomPojo(PayRefundDO.class);
when(refundService.getRefund(eq(1L))).thenReturn(refund);
// mock 方法lock
mockLock(null); // null 的原因,是咱没办法拿到 taskId 新增
// 调用
notifyService.createPayNotifyTask(type, dataId);
// 断言task
PayNotifyTaskDO dbTask = notifyTaskMapper.selectOne(null);
assertNotNull(dbTask.getNextNotifyTime());
assertThat(dbTask)
.extracting("type", "dataId", "status", "notifyTimes", "maxNotifyTimes",
"appId", "merchantOrderId", "notifyUrl")
.containsExactly(type, dataId, PayNotifyStatusEnum.WAITING.getStatus(), 0, 9,
refund.getAppId(), refund.getMerchantOrderId(), refund.getNotifyUrl());
// 断言,调用
verify(payNotifyService).executeNotify0(eq(dbTask));
}
}
@Test
public void testExecuteNotify() throws InterruptedException {
// mock 数据notify
PayNotifyTaskDO dbTask01 = randomPojo(PayNotifyTaskDO.class,
o -> o.setStatus(PayNotifyStatusEnum.WAITING.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(-1))));
notifyTaskMapper.insert(dbTask01);
PayNotifyTaskDO dbTask02 = randomPojo(PayNotifyTaskDO.class,
o -> o.setStatus(PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(-1))));
notifyTaskMapper.insert(dbTask02);
PayNotifyTaskDO dbTask03 = randomPojo(PayNotifyTaskDO.class,
o -> o.setStatus(PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(-1))));
notifyTaskMapper.insert(dbTask03);
PayNotifyTaskDO dbTask04 = randomPojo(PayNotifyTaskDO.class, // 不满足状态
o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(-1))));
notifyTaskMapper.insert(dbTask04);
PayNotifyTaskDO dbTask05 = randomPojo(PayNotifyTaskDO.class, // 不满足状态
o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(-1))));
notifyTaskMapper.insert(dbTask05);
PayNotifyTaskDO dbTask06 = randomPojo(PayNotifyTaskDO.class, // 不满足时间
o -> o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus())
.setNextNotifyTime(addTime(Duration.ofMinutes(1))));
notifyTaskMapper.insert(dbTask06);
// mock 方法lock
mockLock(dbTask01.getId());
mockLock(dbTask02.getId());
mockLock(dbTask03.getId());
// 调用
int count = notifyService.executeNotify();
// 断言,数量
assertEquals(count, 3);
}
@Test // 由于 HttpUtil 不好 mock所以只测试异常的情况
public void testExecuteNotify0_exception() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class, o -> o.setType(-1)
.setNotifyTimes(0).setMaxNotifyTimes(9));
notifyTaskMapper.insert(task);
// 调用
notifyService.executeNotify0(task);
// 断言task
PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime());
assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime());
assertEquals(dbTask.getNotifyTimes(), 1);
assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus());
// 断言log
PayNotifyLogDO dbLog = notifyLogMapper.selectOne(null);
assertEquals(dbLog.getTaskId(), task.getId());
assertEquals(dbLog.getNotifyTimes(), 1);
assertTrue(dbLog.getResponse().contains("未知的通知任务类型:"));
assertEquals(dbLog.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus());
}
@Test
public void testProcessNotifyResult_success() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class,
o -> o.setNotifyTimes(0).setMaxNotifyTimes(9));
notifyTaskMapper.insert(task);
// 准备参数
CommonResult<?> invokeResult = CommonResult.success(randomString());
// 调用
notifyService.processNotifyResult(task, invokeResult, null);
// 断言
PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime());
assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime());
assertEquals(dbTask.getNotifyTimes(), 1);
assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.SUCCESS.getStatus());
}
@Test
public void testProcessNotifyResult_failure() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class,
o -> o.setNotifyTimes(8).setMaxNotifyTimes(9));
notifyTaskMapper.insert(task);
// 准备参数
CommonResult<?> invokeResult = CommonResult.error(BAD_REQUEST);
// 调用
notifyService.processNotifyResult(task, invokeResult, null);
// 断言
PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
assertEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime());
assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime());
assertEquals(dbTask.getNotifyTimes(), 9);
assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.FAILURE.getStatus());
}
@Test
public void testProcessNotifyResult_requestFailure() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class,
o -> o.setNotifyTimes(0).setMaxNotifyTimes(9));
notifyTaskMapper.insert(task);
// 准备参数
CommonResult<?> invokeResult = CommonResult.error(BAD_REQUEST);
// 调用
notifyService.processNotifyResult(task, invokeResult, null);
// 断言
PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime());
assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime());
assertEquals(dbTask.getNotifyTimes(), 1);
assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
}
@Test
public void testProcessNotifyResult_requestSuccess() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class,
o -> o.setNotifyTimes(0).setMaxNotifyTimes(9));
notifyTaskMapper.insert(task);
// 准备参数
CommonResult<?> invokeResult = CommonResult.error(BAD_REQUEST);
RuntimeException invokeException = new RuntimeException();
// 调用
notifyService.processNotifyResult(task, invokeResult, invokeException);
// 断言
PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
assertNotEquals(task.getNextNotifyTime(), dbTask.getNextNotifyTime());
assertNotEquals(task.getLastExecuteTime(), dbTask.getNextNotifyTime());
assertEquals(dbTask.getNotifyTimes(), 1);
assertEquals(dbTask.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus());
}
@Test
public void testGetNotifyTask() {
// mock 数据task
PayNotifyTaskDO task = randomPojo(PayNotifyTaskDO.class);
notifyTaskMapper.insert(task);
// 准备参数
Long id = task.getId();
// 调用
PayNotifyTaskDO dbTask = notifyService.getNotifyTask(id);
// 断言
assertPojoEquals(dbTask, task);
}
@Test
public void testGetNotifyTaskPage() {
// mock 数据
PayNotifyTaskDO dbTask = randomPojo(PayNotifyTaskDO.class, o -> { // 等会查询到
o.setAppId(1L);
o.setType(PayNotifyTypeEnum.REFUND.getType());
o.setDataId(100L);
o.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
o.setMerchantOrderId("P110");
o.setCreateTime(buildTime(2023, 2, 3));
});
notifyTaskMapper.insert(dbTask);
// 测试 appId 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setAppId(2L)));
// 测试 type 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setType(PayNotifyTypeEnum.ORDER.getType())));
// 测试 dataId 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setDataId(200L)));
// 测试 status 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setStatus(PayNotifyStatusEnum.FAILURE.getStatus())));
// 测试 merchantOrderId 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setMerchantOrderId(randomString())));
// 测试 createTime 不匹配
notifyTaskMapper.insert(cloneIgnoreId(dbTask, o -> o.setCreateTime(buildTime(2023, 1, 1))));
// 准备参数
PayNotifyTaskPageReqVO reqVO = new PayNotifyTaskPageReqVO();
reqVO.setAppId(1L);
reqVO.setType(PayNotifyTypeEnum.REFUND.getType());
reqVO.setDataId(100L);
reqVO.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
reqVO.setMerchantOrderId("P110");
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbTask, pageResult.getList().get(0));
}
@Test
public void testGetNotifyLogList() {
// mock 数据
PayNotifyLogDO dbLog = randomPojo(PayNotifyLogDO.class);
notifyLogMapper.insert(dbLog);
PayNotifyLogDO dbLog02 = randomPojo(PayNotifyLogDO.class);
notifyLogMapper.insert(dbLog02);
// 准备参数
Long taskId = dbLog.getTaskId();
// 调用
List<PayNotifyLogDO> logList = notifyService.getNotifyLogList(taskId);
// 断言
assertEquals(logList.size(), 1);
assertPojoEquals(dbLog, logList.get(0));
}
private void mockLock(Long id) {
RLock lock = mock(RLock.class);
if (id == null) {
when(redissonClient.getLock(anyString()))
.thenReturn(lock);
} else {
when(redissonClient.getLock(eq("pay_notify:lock:" + id)))
.thenReturn(lock);
}
}
}

View File

@@ -0,0 +1,703 @@
package cn.iocoder.yudao.module.pay.service.refund;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
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;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
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.order.PayOrderService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
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;
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.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link PayRefundServiceImpl} 的单元测试类
*
* @author 芋艿
*/
@Import({PayRefundServiceImpl.class, PayNoRedisDAO.class})
public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
@Resource
private PayRefundServiceImpl refundService;
@Resource
private PayRefundMapper refundMapper;
@MockBean
private PayProperties payProperties;
@MockBean
private PayClientFactory payClientFactory;
@MockBean
private PayOrderService orderService;
@MockBean
private PayAppService appService;
@MockBean
private PayChannelService channelService;
@MockBean
private PayNotifyService notifyService;
@BeforeEach
public void setUp() {
when(payProperties.getRefundNotifyUrl()).thenReturn("http://127.0.0.1");
}
@Test
public void testGetRefund() {
// mock 数据
PayRefundDO refund = randomPojo(PayRefundDO.class);
refundMapper.insert(refund);
// 准备参数
Long id = refund.getId();
// 调用
PayRefundDO dbRefund = refundService.getRefund(id);
// 断言
assertPojoEquals(dbRefund, refund);
}
@Test
public void testGetRefundCountByAppId() {
// mock 数据
PayRefundDO refund01 = randomPojo(PayRefundDO.class);
refundMapper.insert(refund01);
PayRefundDO refund02 = randomPojo(PayRefundDO.class);
refundMapper.insert(refund02);
// 准备参数
Long appId = refund01.getAppId();
// 调用
Long count = refundService.getRefundCountByAppId(appId);
// 断言
assertEquals(count, 1);
}
@Test
public void testGetRefundPage() {
// mock 数据
PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
o.setAppId(1L);
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
o.setMerchantOrderId("MOT0000001");
o.setMerchantRefundId("MRF0000001");
o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
o.setChannelOrderNo("CH0000001");
o.setChannelRefundNo("CHR0000001");
o.setCreateTime(buildTime(2021, 1, 10));
});
refundMapper.insert(dbRefund);
// 测试 appId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
// 测试 channelCode 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
// 测试 merchantOrderId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString())));
// 测试 merchantRefundId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString())));
// 测试 channelOrderNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString())));
// 测试 channelRefundNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString())));
// 测试 status 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
// 测试 createTime 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
PayRefundPageReqVO reqVO = new PayRefundPageReqVO();
reqVO.setAppId(1L);
reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
reqVO.setMerchantOrderId("MOT0000001");
reqVO.setMerchantRefundId("MRF0000001");
reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
reqVO.setChannelOrderNo("CH0000001");
reqVO.setChannelRefundNo("CHR0000001");
reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11));
// 调用
PageResult<PayRefundDO> pageResult = refundService.getRefundPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbRefund, pageResult.getList().get(0));
}
@Test
public void testGetRefundList() {
// mock 数据
PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
o.setAppId(1L);
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
o.setMerchantOrderId("MOT0000001");
o.setMerchantRefundId("MRF0000001");
o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
o.setChannelOrderNo("CH0000001");
o.setChannelRefundNo("CHR0000001");
o.setCreateTime(buildTime(2021, 1, 10));
});
refundMapper.insert(dbRefund);
// 测试 appId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
// 测试 channelCode 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
// 测试 merchantOrderId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantOrderId(randomString())));
// 测试 merchantRefundId 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId(randomString())));
// 测试 channelOrderNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelOrderNo(randomString())));
// 测试 channelRefundNo 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelRefundNo(randomString())));
// 测试 status 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
// 测试 createTime 不匹配
refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
PayRefundExportReqVO reqVO = new PayRefundExportReqVO();
reqVO.setAppId(1L);
reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
reqVO.setMerchantOrderId("MOT0000001");
reqVO.setMerchantRefundId("MRF0000001");
reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
reqVO.setChannelOrderNo("CH0000001");
reqVO.setChannelRefundNo("CHR0000001");
reqVO.setCreateTime(buildBetweenTime(2021, 1, 9, 2021, 1, 11));
// 调用
List<PayRefundDO> list = refundService.getRefundList(reqVO);
// 断言
assertEquals(1, list.size());
assertPojoEquals(dbRefund, list.get(0));
}
@Test
public void testCreateRefund_orderNotFound() {
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
ORDER_NOT_FOUND);
}
@Test
public void testCreateRefund_orderWaiting() {
testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.WAITING.getStatus());
}
@Test
public void testCreateRefund_orderClosed() {
testCreateRefund_orderWaitingOrClosed(PayOrderStatusEnum.CLOSED.getStatus());
}
private void testCreateRefund_orderWaitingOrClosed(Integer status) {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100"));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
ORDER_REFUND_FAIL_STATUS_ERROR);
}
@Test
public void testCreateRefund_refundPriceExceed() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
REFUND_PRICE_EXCEED);
}
@Test
public void testCreateRefund_orderHasRefunding() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// mock 数据refund 在退款中)
PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
refundMapper.insert(refund);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
REFUND_PRICE_EXCEED);
}
@Test
public void testCreateRefund_channelNotFound() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1)
.setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// mock 方法channel
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
CHANNEL_NOT_FOUND);
}
@Test
public void testCreateRefund_refundExists() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1)
.setChannelId(1L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// mock 方法channel
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
// mock 方法client
PayClient client = mock(PayClient.class);
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
// mock 数据refund 已存在)
PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
o.setAppId(1L).setMerchantRefundId("200"));
refundMapper.insert(refund);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
REFUND_EXISTS);
}
@Test
public void testCreateRefund_invokeException() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1)
.setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// mock 方法channel
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
// mock 方法client
PayClient client = mock(PayClient.class);
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
// mock 方法client 调用发生异常)
when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException());
// 调用
Long refundId = refundService.createPayRefund(reqDTO);
// 断言
PayRefundDO refundDO = refundMapper.selectById(refundId);
assertPojoEquals(reqDTO, refundDO);
assertNotNull(refundDO.getNo());
assertThat(refundDO)
.extracting("orderId", "orderNo", "channelId", "channelCode",
"notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice")
.containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(),
app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(),
order.getPrice(), reqDTO.getPrice());
}
@Test
public void testCreateRefund_invokeSuccess() {
PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
.thenReturn(payRefundServiceImpl);
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法app
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
when(appService.validPayApp(eq(1L))).thenReturn(app);
// mock 数据order
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
.setPrice(10).setRefundPrice(1)
.setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// mock 方法channel
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
// mock 方法client
PayClient client = mock(PayClient.class);
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
// mock 方法client 成功)
PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class);
when(client.unifiedRefund(argThat(unifiedReqDTO -> {
assertNotNull(unifiedReqDTO.getOutRefundNo());
assertThat(unifiedReqDTO)
.extracting("payPrice", "refundPrice", "outTradeNo",
"notifyUrl", "reason")
.containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(),
"http://127.0.0.1/10", reqDTO.getReason());
return true;
}))).thenReturn(refundRespDTO);
// 调用
Long refundId = refundService.createPayRefund(reqDTO);
// 断言
PayRefundDO refundDO = refundMapper.selectById(refundId);
assertPojoEquals(reqDTO, refundDO);
assertNotNull(refundDO.getNo());
assertThat(refundDO)
.extracting("orderId", "orderNo", "channelId", "channelCode",
"notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice")
.containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(),
app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(),
order.getPrice(), reqDTO.getPrice());
// 断言调用
verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO));
}
}
@Test
public void testNotifyRefund() {
PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
try (MockedStatic<SpringUtil> 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(PayNotifyTypeEnum.REFUND.getType()),
eq(refund.getId()));
}
@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(PayNotifyTypeEnum.REFUND.getType()),
eq(refund.getId()));
}
@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<SpringUtil> 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);
}
}

View File

@@ -0,0 +1,54 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
mybatis-plus:
global-config:
db-config:
id-type: AUTO # H2 主键递增
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
# Resilience4j 配置项
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module

View File

@@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>

View File

@@ -0,0 +1,7 @@
DELETE FROM pay_app;
DELETE FROM pay_channel;
DELETE FROM pay_order;
DELETE FROM pay_order_extension;
DELETE FROM pay_refund;
DELETE FROM pay_notify_task;
DELETE FROM pay_notify_log;

View File

@@ -0,0 +1,146 @@
CREATE TABLE IF NOT EXISTS "pay_app" (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(64) NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(255) DEFAULT NULL,
`order_notify_url` varchar(1024) NOT NULL,
`refund_notify_url` varchar(1024) NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit(1) NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT = '支付应用';
CREATE TABLE IF NOT EXISTS "pay_channel" (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"code" varchar(32) NOT NULL,
"status" tinyint(4) NOT NULL,
"remark" varchar(255) DEFAULT NULL,
"fee_rate" double NOT NULL DEFAULT 0,
"app_id" bigint(20) NOT NULL,
"config" varchar(10240) NOT NULL,
"creator" varchar(64) NULL DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) NULL DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit(1) NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT = '支付渠道';
CREATE TABLE IF NOT EXISTS `pay_order` (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`app_id` bigint(20) NOT NULL,
`channel_id` bigint(20) DEFAULT NULL,
`channel_code` varchar(32) DEFAULT NULL,
`merchant_order_id` varchar(64) NOT NULL,
`subject` varchar(32) NOT NULL,
`body` varchar(128) NOT NULL,
`notify_url` varchar(1024) NOT NULL,
`price` bigint(20) NOT NULL,
`channel_fee_rate` double DEFAULT 0,
`channel_fee_price` bigint(20) DEFAULT 0,
`status` tinyint(4) NOT NULL,
`user_ip` varchar(50) NOT NULL,
`expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
`notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
`extension_id` bigint(20) DEFAULT NULL,
`no` varchar(64) NULL,
`refund_price` bigint(20) NOT NULL,
`channel_user_id` varchar(255) DEFAULT NULL,
`channel_order_no` varchar(64) DEFAULT NULL,
`creator` varchar(64) DEFAULT '',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT = '支付订单';
CREATE TABLE IF NOT EXISTS `pay_order_extension` (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`no` varchar(64) NOT NULL,
`order_id` bigint(20) NOT NULL,
`channel_id` bigint(20) NOT NULL,
`channel_code` varchar(32) NOT NULL,
`user_ip` varchar(50) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`channel_extras` varchar(1024) NULL DEFAULT NULL,
`channel_error_code` varchar(64) NULL,
`channel_error_msg` varchar(64) NULL,
`channel_notify_data` varchar(1024) NULL,
`creator` varchar(64) NULL DEFAULT '',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) NULL DEFAULT '',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT = '支付订单拓展';
CREATE TABLE IF NOT EXISTS `pay_refund` (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`no` varchar(64) NOT NULL,
`app_id` bigint(20) NOT NULL,
`channel_id` bigint(20) NOT NULL,
`channel_code` varchar(32) NOT NULL,
`order_id` bigint(20) NOT NULL,
`order_no` varchar(64) NOT NULL,
`merchant_order_id` varchar(64) NOT NULL,
`merchant_refund_id` varchar(64) NOT NULL,
`notify_url` varchar(1024) NOT NULL,
`status` tinyint(4) NOT NULL,
`pay_price` bigint(20) NOT NULL,
`refund_price` bigint(20) NOT NULL,
`reason` varchar(256) NOT NULL,
`user_ip` varchar(50) NULL DEFAULT NULL,
`channel_order_no` varchar(64) NOT NULL,
`channel_refund_no` varchar(64) NULL DEFAULT NULL,
`success_time` datetime(0) NULL DEFAULT NULL,
`channel_error_code` varchar(128) NULL DEFAULT NULL,
`channel_error_msg` varchar(256) NULL DEFAULT NULL,
`channel_notify_data` varchar(1024) NULL,
`creator` varchar(64) NULL DEFAULT '',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) NULL DEFAULT '',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT = '退款订单';
CREATE TABLE IF NOT EXISTS `pay_notify_task` (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`app_id` bigint(20) NOT NULL,
`type` tinyint(4) NOT NULL,
`data_id` bigint(20) NOT NULL,
`merchant_order_id` varchar(64) NOT NULL,
`status` tinyint(4) NOT NULL,
`next_notify_time` datetime(0) NULL DEFAULT NULL,
`last_execute_time` datetime(0) NULL DEFAULT NULL,
`notify_times` int NOT NULL,
`max_notify_times` int NOT NULL,
`notify_url` varchar(1024) NOT NULL,
`creator` varchar(64) NULL DEFAULT '',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) NULL DEFAULT '',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT FALSE,
`tenant_id` bigint(20) NOT NULL DEFAULT 0,
PRIMARY KEY ("id")
) COMMENT = '支付通知任务';
CREATE TABLE IF NOT EXISTS `pay_notify_log` (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`task_id` bigint(20) NOT NULL,
`notify_times` int NOT NULL,
`response` varchar(1024) NOT NULL,
`status` tinyint(4) NOT NULL,
`creator` varchar(64) NULL DEFAULT '',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) NULL DEFAULT '',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT = '支付通知日志';