diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java index 59e6b3e..b6fb5f3 100644 --- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java @@ -26,6 +26,14 @@ public interface PayTransferMapper extends BaseMapperX { .eq(PayTransferDO::getStatus, whereStatus)); } + default int updateChannelPackageInfoIfAbsent(Long id, String channelPackageInfo) { + return update(new PayTransferDO().setChannelPackageInfo(channelPackageInfo), + new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id) + .and(wrapper -> wrapper.isNull(PayTransferDO::getChannelPackageInfo) + .or().eq(PayTransferDO::getChannelPackageInfo, ""))); + } + default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { return selectOne(PayTransferDO::getAppId, appId, PayTransferDO::getMerchantTransferId, merchantOrderId); @@ -59,7 +67,3 @@ public interface PayTransferMapper extends BaseMapperX { } } - - - - diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index 8f123f8..e2edb15 100644 --- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.transfer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -162,6 +163,7 @@ public class PayTransferServiceImpl implements PayTransferService { throw exception(PAY_TRANSFER_NOT_FOUND); } if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + updateChannelPackageInfoIfAbsent(transfer, notify); log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); return; } @@ -175,11 +177,35 @@ public class PayTransferServiceImpl implements PayTransferService { new PayTransferDO().setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) .setChannelPackageInfo(notify.getChannelPackageInfo())); if (updateCounts == 0) { + PayTransferDO latestTransfer = transferMapper.selectById(transfer.getId()); + if (latestTransfer != null && PayTransferStatusEnum.isProcessing(latestTransfer.getStatus())) { + updateChannelPackageInfoIfAbsent(latestTransfer, notify); + log.info("[notifyTransferProgressing][transfer({}) 已被并发更新为转账中状态,无需重复更新]", + transfer.getId()); + return; + } throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); } log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId()); } + /** + * 补充渠道 package 信息:处理同步任务先更新为转账中,发起转账接口后返回 channelPackageInfo 的场景 + * + * @see Issue #1144 + */ + private void updateChannelPackageInfoIfAbsent(PayTransferDO transfer, PayTransferRespDTO notify) { + if (StrUtil.isBlank(notify.getChannelPackageInfo()) + || StrUtil.isNotBlank(transfer.getChannelPackageInfo())) { + return; + } + int updateCount = transferMapper.updateChannelPackageInfoIfAbsent(transfer.getId(), + notify.getChannelPackageInfo()); + if (updateCount > 0) { + log.info("[updateChannelPackageInfoIfAbsent][transfer({}) 补充渠道 package 信息]", transfer.getId()); + } + } + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { // 1. 校验状态 PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java new file mode 100644 index 0000000..81e0c28 --- /dev/null +++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +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 jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link PayTransferServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import({PayTransferServiceImpl.class, PayNoRedisDAO.class}) +public class PayTransferServiceTest extends BaseDbAndRedisUnitTest { + + @Resource + private PayTransferServiceImpl transferService; + + @Resource + private PayTransferMapper transferMapper; + + @MockitoBean + private PayProperties payProperties; + @MockitoBean + private PayAppService appService; + @MockitoBean + private PayChannelService channelService; + @MockitoBean + private PayNotifyService notifyService; + + @Test + public void testNotifyTransferProgressing_fillChannelPackageInfoIfAbsent() { + // mock 数据(PayTransferDO):初始为等待转账 + PayTransferDO transfer = randomPojo(PayTransferDO.class, + o -> o.setAppId(10L) + .setNo("T110") + .setStatus(PayTransferStatusEnum.WAITING.getStatus()) + .setChannelPackageInfo(null)); + transferMapper.insert(transfer); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setAppId(10L)); + PayTransferRespDTO syncNotify = PayTransferRespDTO.processingOf("WX_TRANSFER_110", + "T110", "sync"); + + // 调用:模拟同步任务先把 WAITING 更新为 PROCESSING,但是不返回 package 信息 + transferService.notifyTransfer(channel, syncNotify); + + // 断言:状态已转账中,package 信息为空 + PayTransferDO dbTransfer = transferMapper.selectById(transfer.getId()); + assertEquals(PayTransferStatusEnum.PROCESSING.getStatus(), dbTransfer.getStatus()); + assertNull(dbTransfer.getChannelPackageInfo()); + + // 调用:模拟发起转账接口随后返回 PROCESSING,并携带微信确认收款 package 信息 + PayTransferRespDTO createNotify = PayTransferRespDTO.processingOf("WX_TRANSFER_110", + "T110", "create"); + createNotify.setChannelPackageInfo("package-info-110"); + transferService.notifyTransfer(channel, createNotify); + + // 断言:已是 PROCESSING 时仍会补写 package 信息 + dbTransfer = transferMapper.selectById(transfer.getId()); + assertEquals(PayTransferStatusEnum.PROCESSING.getStatus(), dbTransfer.getStatus()); + assertEquals("package-info-110", dbTransfer.getChannelPackageInfo()); + + // 调用:后续同步任务仍不返回 package 信息,不应覆盖已有值 + transferService.notifyTransfer(channel, syncNotify); + + // 断言:已有 package 信息不被空值覆盖 + dbTransfer = transferMapper.selectById(transfer.getId()); + assertEquals("package-info-110", dbTransfer.getChannelPackageInfo()); + } + +} diff --git a/yudao-module-pay/src/test/resources/sql/create_tables.sql b/yudao-module-pay/src/test/resources/sql/create_tables.sql index 6af612f..a3df280 100644 --- a/yudao-module-pay/src/test/resources/sql/create_tables.sql +++ b/yudao-module-pay/src/test/resources/sql/create_tables.sql @@ -165,12 +165,14 @@ CREATE TABLE IF NOT EXISTS `pay_transfer` ( `user_name` varchar(64) NULL DEFAULT NULL, `status` tinyint(4) NOT NULL, `notify_url` varchar(1024) NULL DEFAULT NULL, + `user_ip` varchar(50) NULL DEFAULT NULL, `channel_transfer_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 DEFAULT NULL, `channel_extras` varchar(1024) NULL DEFAULT NULL, + `channel_package_info` varchar(1024) NULL DEFAULT NULL, `creator` varchar(64) NULL DEFAULT '', `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, `updater` varchar(64) NULL DEFAULT '',