From 56d7236011fd1cc90e8aa873925a8d4c5f947bdb Mon Sep 17 00:00:00 2001 From: bootx Date: Tue, 30 Jan 2024 22:06:25 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E9=80=80=E6=AC=BE=E5=90=8C=E6=AD=A5+?= =?UTF-8?q?=E9=80=80=E6=AC=BE=E4=BF=AE=E5=A4=8D=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _doc/Task.md | 20 +-- .../controller/order/PayOrderController.java | 6 +- .../order/PayRefundOrderController.java | 13 +- .../daxpay/param/pay/RefundSyncParam.java | 4 +- .../{PaySyncResult.java => SyncResult.java} | 6 +- .../gateway/controller/UniPayController.java | 4 +- .../wechat/service/WeChatPayService.java | 6 +- .../wechat/service/WeChatPaySyncService.java | 5 +- .../refund/service/PayRefundOrderService.java | 34 ------ .../factory/PayRepairStrategyFactory.java | 4 +- .../factory/RefundRepairStrategyFactory.java | 114 ++++++++++++++++++ .../repair/service/RefundRepairService.java | 102 +++++++++------- .../{ => pay}/AliPayRepairStrategy.java | 2 +- .../{ => pay}/CashPayRepairStrategy.java | 2 +- .../{ => pay}/UnionPayRepairStrategy.java | 2 +- .../{ => pay}/VoucherPayRepairStrategy.java | 2 +- .../{ => pay}/WalletPayRepairStrategy.java | 2 +- .../{ => pay}/WeChatPayRepairStrategy.java | 2 +- .../refund/AliRefundRepairStrategy.java | 30 +++++ .../refund/CashRefundRepairStrategy.java | 29 +++++ .../refund/UnionRefundRepairStrategy.java | 29 +++++ .../refund/VoucherRefundRepairStrategy.java | 29 +++++ .../refund/WalletRefundRepairStrategy.java | 29 +++++ .../refund/WeChatRefundRepairStrategy.java | 29 +++++ .../sync/service/PayRefundSyncService.java | 77 +++++++++--- .../payment/sync/service/PaySyncService.java | 16 +-- .../record/sync/entity/PaySyncRecord.java | 10 +- .../service/func/AbsRefundRepairStrategy.java | 51 ++++++-- 28 files changed, 511 insertions(+), 148 deletions(-) rename daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/{PaySyncResult.java => SyncResult.java} (84%) delete mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/service/PayRefundOrderService.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/RefundRepairStrategyFactory.java rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/AliPayRepairStrategy.java (99%) rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/CashPayRepairStrategy.java (99%) rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/UnionPayRepairStrategy.java (98%) rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/VoucherPayRepairStrategy.java (99%) rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/WalletPayRepairStrategy.java (99%) rename daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/{ => pay}/WeChatPayRepairStrategy.java (99%) create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/AliRefundRepairStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/CashRefundRepairStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/UnionRefundRepairStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/VoucherRefundRepairStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WalletRefundRepairStrategy.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WeChatRefundRepairStrategy.java diff --git a/_doc/Task.md b/_doc/Task.md index 7ae5780d..f9d91f36 100644 --- a/_doc/Task.md +++ b/_doc/Task.md @@ -90,21 +90,27 @@ - [x] 支付宝对账单下载异常排查-支付宝每日都会生成对账单, 哪怕为空, 也会生成 - [x] 订单修复记录前端显示调整 - 2044-01-30: - - [x] 退款接口更改为先落库, 后更新 - - [ ] 增加退款同步策略, 对退款中的状态的退款订单进行处理 - - [ ] 退款操作支持重试 - - [ ] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿 + - [x] 退款接口更改为先落库, 后更新, 同时退款余额先先进行扣减, 根据退款状态进行处理 + - [x] 增加退款同步策略, 对退款中的状态的退款订单进行处理 + - [x] 修改退款补偿处理, 更改为退款粒度为整个退款单, 要不全部成功, 要不全部失败 + - [x] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿 +- 2024-01-31: + - [ ] 微信退款同步策略 + - [ ] 退款操作支持重试 2.0.1 版本内容 - [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单 + - [ ] 完善各种同步支付方式 + - [ ] 增加聚合支付功能支持 + - [ ] 增加手机用户收银台功能 **任务池** - - [ ] 微信退款状态不一致补偿 + - [ ] ~~微信退款状态不一致补偿~~ - [ ] 支付SDK编写 - [ ] 接入支付网关的演示项目 - [ ] 支付宝关闭支付时支持撤销方式, - [ ] 支持转账操作, 通过支付通道专有参数进行实现, 转账时只能单个通道进行操作 - [ ] 支付成功回调后, 如果订单已超时, 则进入待退款订单中,提示进行退款,或者自动退款 - - [ ] 退款状态同步逻辑 - - [ ] 支付状态同步处理考虑退款情况 + - [ ] ~~退款状态同步逻辑~~ + - [ ] ~~支付状态同步处理考虑退款情况~~ - [ ] 增加回调机制(通知客户端) - [ ] 增加消息通知机制(通知客户端) - [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况 diff --git a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java index d3db81d0..30074236 100644 --- a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java +++ b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java @@ -7,7 +7,7 @@ import cn.bootx.platform.common.core.rest.ResResult; import cn.bootx.platform.common.core.rest.param.PageParam; import cn.bootx.platform.daxpay.param.pay.PayCloseParam; import cn.bootx.platform.daxpay.param.pay.PaySyncParam; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.result.pay.SyncResult; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService; import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderExtraService; @@ -78,8 +78,8 @@ public class PayOrderController { } @Operation(summary = "同步支付状态") - @PostMapping("/sync") - public ResResult sync(Long id){ + @PostMapping("/syncById") + public ResResult syncById(Long id){ PaySyncParam param = new PaySyncParam(); param.setPaymentId(id); return Res.ok(paySyncService.sync(param)); diff --git a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayRefundOrderController.java b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayRefundOrderController.java index 6f3d12bf..e32b3d2d 100644 --- a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayRefundOrderController.java +++ b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayRefundOrderController.java @@ -6,10 +6,11 @@ import cn.bootx.platform.common.core.rest.ResResult; import cn.bootx.platform.common.core.rest.param.PageParam; import cn.bootx.platform.common.spring.util.WebServletUtil; import cn.bootx.platform.daxpay.param.pay.RefundParam; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.param.pay.RefundSyncParam; +import cn.bootx.platform.daxpay.result.pay.SyncResult; import cn.bootx.platform.daxpay.service.core.order.refund.service.PayRefundOrderQueryService; -import cn.bootx.platform.daxpay.service.core.order.refund.service.PayRefundOrderService; import cn.bootx.platform.daxpay.service.core.payment.refund.service.PayRefundService; +import cn.bootx.platform.daxpay.service.core.payment.sync.service.PayRefundSyncService; import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto; import cn.bootx.platform.daxpay.service.dto.order.refund.RefundChannelOrderDto; import cn.bootx.platform.daxpay.service.param.order.PayOrderRefundParam; @@ -37,7 +38,7 @@ import java.util.Optional; public class PayRefundOrderController { private final PayRefundOrderQueryService payRefundQueryService; private final PayRefundService payRefundService; - private final PayRefundOrderService payRefundOrderService; + private final PayRefundSyncService refundSyncService; @Operation(summary = "分页查询") @@ -84,7 +85,9 @@ public class PayRefundOrderController { @Operation(summary = "退款同步") @PostMapping("/syncById") - public ResResult syncById(Long id){ - return Res.ok(payRefundOrderService.syncById(id)); + public ResResult syncById(Long id){ + RefundSyncParam refundSyncParam = new RefundSyncParam(); + refundSyncParam.setRefundId(id); + return Res.ok(refundSyncService.sync(refundSyncParam)); } } diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/RefundSyncParam.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/RefundSyncParam.java index e3bbf5cf..65cfe919 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/RefundSyncParam.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/RefundSyncParam.java @@ -15,13 +15,13 @@ import lombok.EqualsAndHashCode; public class RefundSyncParam extends PayCommonParam { /** - * 部分退款时 refundId 和 refundNo 必传一个, 同时传输时,以 refundId 为准 + * 退款订单ID,refundId和refundNo 必传一个, 同时传输时,以 refundId 为准 */ @Schema(description = "退款订单ID") private Long refundId; /** - * 退款订单号,部分退款时 refundId 和 refundNo 必传一个,同时传输时,以 refundId 为准 + * 退款订单号,refundId和refundNo 必传一个,同时传输时,以 refundId 为准 */ @Schema(description = "退款订单号") private String refundNo; diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/PaySyncResult.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/SyncResult.java similarity index 84% rename from daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/PaySyncResult.java rename to daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/SyncResult.java index 036539c9..6365a60d 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/PaySyncResult.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/SyncResult.java @@ -1,5 +1,6 @@ package cn.bootx.platform.daxpay.result.pay; +import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum; import cn.bootx.platform.daxpay.result.CommonResult; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,12 +18,13 @@ import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.FAIL; @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) -@Schema(title = "支付单同步结果") -public class PaySyncResult extends CommonResult { +@Schema(title = "同步结果") +public class SyncResult extends CommonResult { /** * 支付网关同步状态 * @see PaySyncStatusEnum + * @see PayRefundSyncStatusEnum */ @Schema(description = "支付网关同步状态") private String gatewayStatus = FAIL.getCode(); diff --git a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/UniPayController.java b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/UniPayController.java index e197cfcd..2b2255a5 100644 --- a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/UniPayController.java +++ b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/gateway/controller/UniPayController.java @@ -7,7 +7,7 @@ import cn.bootx.platform.daxpay.result.DaxResult; import cn.bootx.platform.daxpay.result.order.PayOrderResult; import cn.bootx.platform.daxpay.result.order.RefundOrderResult; import cn.bootx.platform.daxpay.result.pay.PayResult; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.result.pay.SyncResult; import cn.bootx.platform.daxpay.result.pay.RefundResult; import cn.bootx.platform.daxpay.service.annotation.PaymentApi; import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService; @@ -86,7 +86,7 @@ public class UniPayController { @PaymentApi("syncPay") @Operation(summary = "支付状态同步") @PostMapping("/syncPay") - public DaxResult syncPay(@RequestBody PaySyncParam param){ + public DaxResult syncPay(@RequestBody PaySyncParam param){ return DaxRes.ok(paySyncService.sync(param)); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayService.java index 144a321d..bd0e149c 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayService.java @@ -7,7 +7,7 @@ import cn.bootx.platform.common.spring.exception.RetryableException; import cn.bootx.platform.daxpay.code.PayWayEnum; import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.param.pay.PayChannelParam; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.result.pay.SyncResult; import cn.bootx.platform.daxpay.service.code.WeChatPayCode; import cn.bootx.platform.daxpay.service.code.WeChatPayWay; import cn.bootx.platform.daxpay.service.common.context.AsyncPayLocal; @@ -268,9 +268,9 @@ public class WeChatPayService { @Async("bigExecutor") @Retryable(value = RetryableException.class, maxAttempts = 10, backoff = @Backoff(value = 5000L)) public void rotationSync(PayOrder payOrder) { - PaySyncResult paySyncResult = paySyncService.syncPayOrder(payOrder); + SyncResult syncResult = paySyncService.syncPayOrder(payOrder); // 不为支付中状态后, 调用系统同步更新状态, 支付状态则继续重试 - if (Objects.equals(PAY_WAIT.getCode(), paySyncResult.getGatewayStatus())) { + if (Objects.equals(PAY_WAIT.getCode(), syncResult.getGatewayStatus())) { throw new RetryableException(); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java index fb1f59b1..2cb7bb56 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPaySyncService.java @@ -85,10 +85,9 @@ public class WeChatPaySyncService { return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT); } - // 已退款/退款中 触发一下退款记录的查询 + // 已退款/退款中 if (Objects.equals(tradeStatus, WeChatPayCode.PAY_REFUND)) { -// this.syncRefundStatus(order, weChatPayConfig); - // TODO 特殊处理, 提示用户走退款同步 + return syncResult.setSyncStatus(PaySyncStatusEnum.REFUND); } // 已关闭 if (Objects.equals(tradeStatus, WeChatPayCode.PAY_CLOSED) diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/service/PayRefundOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/service/PayRefundOrderService.java deleted file mode 100644 index fe6ceb2f..00000000 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/service/PayRefundOrderService.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.bootx.platform.daxpay.service.core.order.refund.service; - -import cn.bootx.platform.common.core.exception.DataNotExistException; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; -import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundOrderManager; -import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder; -import cn.bootx.platform.daxpay.service.core.payment.sync.service.PayRefundSyncService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -/** - * 退款订单服务类 - * @author xxm - * @since 2024/1/29 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class PayRefundOrderService { - private final PayRefundOrderManager refundOrderManager; - - private final PayRefundSyncService refundSyncService;; - - /** - * 退款同步 - */ - public PaySyncResult syncById(Long id){ - PayRefundOrder refundOrder = refundOrderManager.findById(id) - .orElseThrow(() -> new DataNotExistException("退款订单不存在")); - return refundSyncService.syncRefundOrder(refundOrder); - } - -} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/PayRepairStrategyFactory.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/PayRepairStrategyFactory.java index c31e782b..ae82512b 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/PayRepairStrategyFactory.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/PayRepairStrategyFactory.java @@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.repair.factory; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; -import cn.bootx.platform.daxpay.service.core.payment.repair.strategy.*; +import cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay.*; import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.extra.spring.SpringUtil; @@ -26,7 +26,7 @@ import static cn.bootx.platform.daxpay.code.PayChannelEnum.ASYNC_TYPE_CODE; public class PayRepairStrategyFactory { /** * 根据传入的支付通道创建策略 - * @return 支付策略 + * @return 支付修复策略 */ public static AbsPayRepairStrategy create(PayChannelEnum channelEnum) { diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/RefundRepairStrategyFactory.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/RefundRepairStrategyFactory.java new file mode 100644 index 00000000..2bb3c002 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/factory/RefundRepairStrategyFactory.java @@ -0,0 +1,114 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.factory; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; +import cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund.*; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.experimental.UtilityClass; +import lombok.val; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.bootx.platform.daxpay.code.PayChannelEnum.*; + + +/** + * 支付修复策略工厂类 + * @author xxm + * @since 2023/12/29 + */ +@UtilityClass +public class RefundRepairStrategyFactory { + /** + * 根据传入的通道创建策略 + * @return 退款修复策略 + */ + public static AbsRefundRepairStrategy create(PayChannelEnum channelEnum) { + + AbsRefundRepairStrategy strategy; + switch (channelEnum) { + case ALI: + strategy = SpringUtil.getBean(AliRefundRepairStrategy.class); + break; + case WECHAT: + strategy = SpringUtil.getBean(WeChatRefundRepairStrategy.class); + break; + case UNION_PAY: + strategy = SpringUtil.getBean(UnionRefundRepairStrategy.class); + break; + case CASH: + strategy = SpringUtil.getBean(CashRefundRepairStrategy.class); + break; + case WALLET: + strategy = SpringUtil.getBean(WalletRefundRepairStrategy.class); + break; + case VOUCHER: + strategy = SpringUtil.getBean(VoucherRefundRepairStrategy.class); + break; + default: + throw new PayUnsupportedMethodException(); + } + return strategy; + } + + /** + * 根据传入的支付类型批量创建策略, 异步支付在后面 + */ + public static List createAsyncLast(List channelCodes) { + return create(channelCodes, true); + } + + /** + * 根据传入的支付类型批量创建策略, 异步支付在前面 + */ + public static List createAsyncFront(List channelCodes) { + return create(channelCodes, false); + } + + /** + * 根据传入的支付类型批量创建策略 + * @param asyncSort 是否异步支付在后面 + * @return 支付策略 + */ + private static List create(List channelCodes, boolean asyncSort) { + if (CollectionUtil.isEmpty(channelCodes)) { + return Collections.emptyList(); + } + + // 同步支付 + val syncChannels = channelCodes.stream() + .filter(code -> !ASYNC_TYPE_CODE.contains(code)) + .map(PayChannelEnum::findByCode) + .collect(Collectors.toList()); + + // 异步支付 + val asyncChannels = channelCodes.stream() + .filter(ASYNC_TYPE_CODE::contains) + .map(PayChannelEnum::findByCode) + .collect(Collectors.toList()); + + List sortList = new ArrayList<>(channelCodes.size()); + + // 异步在后面 + if (asyncSort) { + sortList.addAll(syncChannels); + sortList.addAll(asyncChannels); + } + else { + sortList.addAll(asyncChannels); + sortList.addAll(syncChannels); + } + + // 此处有一个根据Type的反转排序, + return sortList.stream() + .filter(Objects::nonNull) + .map(RefundRepairStrategyFactory::create) + .collect(Collectors.toList()); + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/RefundRepairService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/RefundRepairService.java index 37a97198..c1e64c1f 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/RefundRepairService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/RefundRepairService.java @@ -1,6 +1,6 @@ package cn.bootx.platform.daxpay.service.core.payment.repair.service; -import cn.bootx.platform.common.core.exception.DataNotExistException; +import cn.bootx.platform.common.core.function.CollectorsFunction; import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; @@ -14,19 +14,20 @@ import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundChannelOr import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundOrderManager; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder; +import cn.bootx.platform.daxpay.service.core.payment.repair.factory.RefundRepairStrategyFactory; import cn.bootx.platform.daxpay.service.core.payment.repair.result.RefundRepairResult; import cn.bootx.platform.daxpay.service.core.record.repair.entity.PayRepairRecord; import cn.bootx.platform.daxpay.service.core.record.repair.service.PayRepairRecordService; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; import cn.hutool.core.util.IdUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; /** * 退款订单修复, 只有存在异步支付的退款订单才存在修复 @@ -49,7 +50,7 @@ public class RefundRepairService { private final PayRepairRecordService recordService; /** - * 修复支付单 + * 修复退款单 */ @Transactional(rollbackFor = Exception.class) public RefundRepairResult repair(PayRefundOrder refundOrder, RefundRepairWayEnum repairType){ @@ -58,19 +59,29 @@ public class RefundRepairService { PayOrder payOrder = payOrderManager.findById(refundOrder.getPaymentId()) .orElseThrow(() -> new RuntimeException("支付单不存在")); // 关联支付通道支付单 - PayChannelOrder payChannelOrder = payChannelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payOrder.getAsyncChannel()) - .orElseThrow(DataNotExistException::new); + Map payChannelOrderMap = payChannelOrderManager.findAllByPaymentId(refundOrder.getPaymentId()) + .stream() + .collect(Collectors.toMap(PayChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest)); // 异步通道退款单 - PayRefundChannelOrder refundChannelOrder = refundChannelOrderManager.findByRefundIdAndChannel(refundOrder.getId(), payOrder.getAsyncChannel()) - .orElseThrow(DataNotExistException::new); + Map refundChannelOrderMap = refundChannelOrderManager.findAllByRefundId(refundOrder.getId()) + .stream() + .collect(Collectors.toMap(PayRefundChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest)); + + // 2 初始化修复参数 + List channels = new ArrayList<>(payChannelOrderMap.keySet()); + List repairStrategies = RefundRepairStrategyFactory.createAsyncLast(channels); + for (AbsRefundRepairStrategy repairStrategy : repairStrategies) { + PayChannelOrder payChannelOrder = payChannelOrderMap.get(repairStrategy.getChannel().getCode()); + PayRefundChannelOrder payRefundChannelOrder = refundChannelOrderMap.get(repairStrategy.getChannel().getCode()); + repairStrategy.initRepairParam(refundOrder, payRefundChannelOrder, payOrder, payChannelOrder); + } // 根据不同的类型执行对应的修复逻辑 RefundRepairResult repairResult = new RefundRepairResult(); - //TODO 整个退款单是一个状态, 最终结果要么全部成功, 要么全部回退 if (Objects.requireNonNull(repairType) == RefundRepairWayEnum.SUCCESS) { - repairResult = this.success(refundOrder,payOrder,refundChannelOrder,payChannelOrder); + repairResult = this.success(refundOrder,payOrder,repairStrategies); } else if (repairType == RefundRepairWayEnum.FAIL) { - repairResult = this.closeLocal(refundOrder,payOrder,refundChannelOrder,payChannelOrder); + repairResult = this.close(refundOrder,payOrder,repairStrategies); } else { log.error("走到了理论上讲不会走到的分支"); } @@ -87,13 +98,7 @@ public class RefundRepairService { /** * 退款成功, 更新退款单和支付单 */ - private RefundRepairResult success(PayRefundOrder refundOrder, PayOrder payOrder, PayRefundChannelOrder refundChannelOrder, PayChannelOrder payChannelOrder) { - // 更新通道支付单全部退款还是部分退款 - if (Objects.equals(payChannelOrder.getRefundableBalance(),0)){ - payChannelOrder.setStatus(PayStatusEnum.REFUNDED.getCode()); - } else { - payChannelOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode()); - } + private RefundRepairResult success(PayRefundOrder refundOrder, PayOrder payOrder, List repairStrategies) { // 订单相关状态 PayStatusEnum beforePayStatus = PayStatusEnum.findByCode(refundOrder.getStatus()); @@ -108,14 +113,25 @@ public class RefundRepairService { } // 设置退款为完成状态 refundOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode()); - refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode()) - .setRefundTime(LocalDateTime.now()); payOrder.setStatus(afterPayRefundStatus.getCode()); + + // 执行退款成功逻辑 + repairStrategies.forEach(AbsRefundRepairStrategy::doSuccessHandler); + // 获取要更新的数据 + List payChannelOrders = repairStrategies.stream() + .map(AbsRefundRepairStrategy::getPayChannelOrder) + .collect(Collectors.toList()); + List refundChannelOrders = repairStrategies + .stream() + .map(AbsRefundRepairStrategy::getRefundChannelOrder) + .collect(Collectors.toList()); + // 更新订单和退款相关订单 - payChannelOrderManager.updateById(payChannelOrder); payOrderManager.updateById(payOrder); refundOrderManager.updateById(refundOrder); - refundChannelOrderManager.updateById(refundChannelOrder); + payChannelOrderManager.updateAllById(payChannelOrders); + refundChannelOrderManager.updateAllById(refundChannelOrders); + return new RefundRepairResult() .setBeforePayStatus(beforePayStatus) .setAfterPayStatus(afterPayRefundStatus) @@ -125,9 +141,9 @@ public class RefundRepairService { /** - * 退款失败, 将失败的退款金额归还回订单 + * 退款失败, 关闭退款单并将失败的退款金额归还回订单 */ - private RefundRepairResult closeLocal(PayRefundOrder refundOrder, PayOrder payOrder, PayRefundChannelOrder refundChannelOrder, PayChannelOrder payChannelOrder) { + private RefundRepairResult close(PayRefundOrder refundOrder, PayOrder payOrder, List repairStrategies) { // 要返回的状态 RefundRepairResult repairResult = new RefundRepairResult(); @@ -138,40 +154,38 @@ public class RefundRepairService { .setBeforeRefundStatus(beforeRefundStatus); // 退款失败返还后的余额 - int payOrderAmount = refundChannelOrder.getAmount() + payOrder.getRefundableBalance(); - int payChannelOrderAmount = refundChannelOrder.getAmount() + payChannelOrder.getRefundableBalance(); + int payOrderAmount = refundOrder.getAmount() + payOrder.getRefundableBalance(); // 退款失败返还后的余额+可退余额 == 订单金额 支付订单回退为为支付成功状态 if (payOrderAmount == payOrder.getAmount()){ payOrder.setStatus(PayStatusEnum.SUCCESS.getCode()); - // 说明这个退款只有异步这一个支付, 所以也可以直接回退 - payChannelOrder.setStatus(PayStatusEnum.SUCCESS.getCode()); repairResult.setAfterPayStatus(PayStatusEnum.SUCCESS); } else { // 回归部分退款状态 payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode()); repairResult.setAfterPayStatus(PayStatusEnum.PARTIAL_REFUND); } + // 更新支付订单相关的可退款金额 payOrder.setRefundableBalance(payOrderAmount); - payChannelOrder.setRefundableBalance(payChannelOrderAmount); + refundOrder.setStatus(PayRefundStatusEnum.CLOSE.getCode()); - // 判断退款订单是否只有异步通道一个关联的通道退款单, 退款值一致说明是一个 - if (Objects.equals(refundOrder.getAmount(), refundChannelOrder.getAmount())){ - // 退款单和通道退款单统一设置为失败 - refundOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); - refundChannelOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); - repairResult.setAfterRefundStatus(PayRefundStatusEnum.FAIL); - } else { - // 退款单设置为部分成功状态, 通道退款单设置为失败状态 - refundOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); - refundChannelOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); - } + // 执行关闭退款逻辑 + repairStrategies.forEach(AbsRefundRepairStrategy::doCloseHandler); + + // 获取要更新的数据 + List payChannelOrders = repairStrategies.stream() + .map(AbsRefundRepairStrategy::getPayChannelOrder) + .collect(Collectors.toList()); + List refundChannelOrders = repairStrategies + .stream() + .map(AbsRefundRepairStrategy::getRefundChannelOrder) + .collect(Collectors.toList()); // 更新订单和退款相关订单 - payChannelOrderManager.updateById(payChannelOrder); + payChannelOrderManager.updateAllById(payChannelOrders); payOrderManager.updateById(payOrder); refundOrderManager.updateById(refundOrder); - refundChannelOrderManager.updateById(refundChannelOrder); + refundChannelOrderManager.updateAllById(refundChannelOrders); return repairResult; } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/AliPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/AliPayRepairStrategy.java similarity index 99% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/AliPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/AliPayRepairStrategy.java index 1853c35a..1a3653f9 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/AliPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/AliPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/CashPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/CashPayRepairStrategy.java similarity index 99% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/CashPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/CashPayRepairStrategy.java index 5ee2b129..8c593ffd 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/CashPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/CashPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/UnionPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/UnionPayRepairStrategy.java similarity index 98% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/UnionPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/UnionPayRepairStrategy.java index 1721b71e..7ae4a84c 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/UnionPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/UnionPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/VoucherPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/VoucherPayRepairStrategy.java similarity index 99% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/VoucherPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/VoucherPayRepairStrategy.java index 400c0865..8c0bbaae 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/VoucherPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/VoucherPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WalletPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WalletPayRepairStrategy.java similarity index 99% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WalletPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WalletPayRepairStrategy.java index c85767db..8c7b4ecb 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WalletPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WalletPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WeChatPayRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WeChatPayRepairStrategy.java similarity index 99% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WeChatPayRepairStrategy.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WeChatPayRepairStrategy.java index bd1c18bd..27f72fc2 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/WeChatPayRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/pay/WeChatPayRepairStrategy.java @@ -1,4 +1,4 @@ -package cn.bootx.platform.daxpay.service.core.payment.repair.strategy; +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/AliRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/AliRefundRepairStrategy.java new file mode 100644 index 00000000..bc601865 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/AliRefundRepairStrategy.java @@ -0,0 +1,30 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class AliRefundRepairStrategy extends AbsRefundRepairStrategy { + + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.ALI; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/CashRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/CashRefundRepairStrategy.java new file mode 100644 index 00000000..8140cc2f --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/CashRefundRepairStrategy.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class CashRefundRepairStrategy extends AbsRefundRepairStrategy { + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.CASH; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/UnionRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/UnionRefundRepairStrategy.java new file mode 100644 index 00000000..15fae95f --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/UnionRefundRepairStrategy.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class UnionRefundRepairStrategy extends AbsRefundRepairStrategy { + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.UNION_PAY; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/VoucherRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/VoucherRefundRepairStrategy.java new file mode 100644 index 00000000..cedc77aa --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/VoucherRefundRepairStrategy.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class VoucherRefundRepairStrategy extends AbsRefundRepairStrategy { + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.VOUCHER; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WalletRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WalletRefundRepairStrategy.java new file mode 100644 index 00000000..522849ff --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WalletRefundRepairStrategy.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class WalletRefundRepairStrategy extends AbsRefundRepairStrategy { + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.WALLET; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WeChatRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WeChatRefundRepairStrategy.java new file mode 100644 index 00000000..a6813354 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/strategy/refund/WeChatRefundRepairStrategy.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.refund; + +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.func.AbsRefundRepairStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +/** + * + * @author xxm + * @since 2024/1/30 + */ +@Slf4j +@Scope(SCOPE_PROTOTYPE) +@Service +@RequiredArgsConstructor +public class WeChatRefundRepairStrategy extends AbsRefundRepairStrategy { + /** + * 策略标识 + */ + @Override + public PayChannelEnum getChannel() { + return PayChannelEnum.WECHAT; + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PayRefundSyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PayRefundSyncService.java index 60ddd1c5..1536c0a8 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PayRefundSyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PayRefundSyncService.java @@ -1,14 +1,20 @@ package cn.bootx.platform.daxpay.service.core.payment.sync.service; +import cn.bootx.platform.common.core.exception.BizException; import cn.bootx.platform.common.core.exception.RepetitiveOperationException; import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum; import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.param.pay.RefundSyncParam; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.result.pay.SyncResult; +import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; +import cn.bootx.platform.daxpay.service.code.RefundRepairWayEnum; +import cn.bootx.platform.daxpay.service.common.context.RepairLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundOrderManager; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder; import cn.bootx.platform.daxpay.service.core.payment.repair.result.RefundRepairResult; +import cn.bootx.platform.daxpay.service.core.payment.repair.service.RefundRepairService; import cn.bootx.platform.daxpay.service.core.payment.sync.factory.RefundSyncStrategyFactory; import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult; import cn.bootx.platform.daxpay.service.core.record.sync.entity.PaySyncRecord; @@ -37,13 +43,15 @@ public class PayRefundSyncService { private final PaySyncRecordService paySyncRecordService; + private final RefundRepairService repairService; + private final LockTemplate lockTemplate; /** * 退款同步, 开启一个新的事务, 不受外部抛出异常的影响 */ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) - public void sync(RefundSyncParam param){ + public SyncResult sync(RefundSyncParam param){ // 先获取退款单 PayRefundOrder requestOrder; if (Objects.nonNull(param.getRefundId())){ @@ -55,24 +63,21 @@ public class PayRefundSyncService { } // 如果不是异步支付, 直接返回返回 if (!requestOrder.isAsyncPay()){ - // TODO 需要限制同步的请求不进行同步 - return; + return new SyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单没有异步通道的退款,不需要同步"); } - this.syncRefundOrder(requestOrder); - + return this.syncRefundOrder(requestOrder); } /** * 退款订单信息同步 */ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) - public PaySyncResult syncRefundOrder(PayRefundOrder refundOrder) { + public SyncResult syncRefundOrder(PayRefundOrder refundOrder) { // 加锁 LockInfo lock = lockTemplate.lock("sync:refund:" + refundOrder.getId()); if (Objects.isNull(lock)) { throw new RepetitiveOperationException("退款同步处理中,请勿重复操作"); } - try { // 获取支付同步策略类 AbsRefundSyncStrategy syncPayStrategy = RefundSyncStrategyFactory.create(refundOrder.getAsyncChannel()); @@ -83,14 +88,13 @@ public class PayRefundSyncService { // 判断是否同步成功 if (Objects.equals(syncResult.getSyncStatus(), PayRefundSyncStatusEnum.FAIL)) { // 同步失败, 返回失败响应, 同时记录失败的日志 - return new PaySyncResult().setErrorMsg(syncResult.getErrorMsg()); + return new SyncResult().setErrorMsg(syncResult.getErrorMsg()); } - -// 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态 - boolean statusSync = this.checkAndAdjustSyncStatus(syncResult, refundOrder); + // 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态 + boolean statusSync = this.checkSyncStatus(syncResult, refundOrder); RefundRepairResult repairResult = new RefundRepairResult(); try { -// // 状态不一致,执行支付单修复逻辑 + // 状态不一致,执行退款单修复逻辑 if (!statusSync) { repairResult = this.repairHandler(syncResult, refundOrder); } @@ -98,11 +102,11 @@ public class PayRefundSyncService { // 同步失败, 返回失败响应, 同时记录失败的日志 syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL); this.saveRecord(refundOrder, syncResult, false, null, e.getMessage()); - return new PaySyncResult().setErrorMsg(e.getMessage()); + return new SyncResult().setErrorMsg(e.getMessage()); } -// // 同步成功记录日志 + // 同步成功记录日志 this.saveRecord(refundOrder, syncResult, !statusSync, repairResult.getRepairId(), null); - return new PaySyncResult() + return new SyncResult() .setGatewayStatus(syncResult.getSyncStatus().getCode()) .setSuccess(true) .setRepair(!statusSync) @@ -113,13 +117,45 @@ public class PayRefundSyncService { } - - private boolean checkAndAdjustSyncStatus(RefundGatewaySyncResult syncResult, PayRefundOrder order){ - return true; + /** + * 检查状态是否一致 + */ + private boolean checkSyncStatus(RefundGatewaySyncResult syncResult, PayRefundOrder order){ + PayRefundSyncStatusEnum syncStatus = syncResult.getSyncStatus(); + String orderStatus = order.getStatus(); + return Objects.equals(orderStatus, syncStatus.getCode()); } + /** + * 进行退款订单和支付订单的补偿 + */ private RefundRepairResult repairHandler(RefundGatewaySyncResult syncResult, PayRefundOrder order){ - return null; + PayRefundSyncStatusEnum syncStatusEnum = syncResult.getSyncStatus(); + // 如果没有支付来源, 设置支付来源为同步 + RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo(); + if (Objects.isNull(repairInfo.getSource())){ + repairInfo.setSource(PayRepairSourceEnum.SYNC); + } + RefundRepairResult repair = new RefundRepairResult(); + // 对支付网关同步的结果进行处理 + switch (syncStatusEnum) { + // 调用出错 + case SUCCESS: + repair = repairService.repair(order, RefundRepairWayEnum.SUCCESS); + break; + case REFUNDING: + // 不进行处理 TODO 添加重试 + log.warn("退款状态同步接口调用出错"); + break; + case FAIL: { + repair = repairService.repair(order, RefundRepairWayEnum.FAIL); + break; + } + default: { + throw new BizException("代码有问题"); + } + } + return repair; } @@ -135,6 +171,7 @@ public class PayRefundSyncService { PaySyncRecord paySyncRecord = new PaySyncRecord() .setOrderId(payOrder.getId()) .setOrderNo(payOrder.getBusinessNo()) + .setSyncType(PaymentTypeEnum.REFUND.getCode()) .setAsyncChannel(payOrder.getAsyncChannel()) .setSyncInfo(syncResult.getSyncInfo()) .setGatewayStatus(syncResult.getSyncStatus().getCode()) diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java index 75b45632..39d85494 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/service/PaySyncService.java @@ -7,9 +7,10 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum; import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.param.pay.PaySyncParam; -import cn.bootx.platform.daxpay.result.pay.PaySyncResult; +import cn.bootx.platform.daxpay.result.pay.SyncResult; import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum; import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.common.context.RepairLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; @@ -58,7 +59,7 @@ public class PaySyncService { * 支付同步, 开启一个新的事务, 不受外部抛出异常的影响 */ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) - public PaySyncResult sync(PaySyncParam param) { + public SyncResult sync(PaySyncParam param) { PayOrder payOrder = null; if (Objects.nonNull(param.getPaymentId())){ payOrder = payOrderQueryService.findById(param.getPaymentId()) @@ -70,7 +71,7 @@ public class PaySyncService { } // 如果不是异步支付, 直接返回返回 if (!payOrder.isAsyncPay()){ - return new PaySyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单没有异步支付方式,不需要同步"); + return new SyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单没有异步支付方式,不需要同步"); } // 执行订单同步逻辑 return this.syncPayOrder(payOrder); @@ -82,7 +83,7 @@ public class PaySyncService { * todo 需要进行异常处理, 现在会有 Transaction rolled back because it has been marked as rollback-only 问题 */ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) - public PaySyncResult syncPayOrder(PayOrder payOrder) { + public SyncResult syncPayOrder(PayOrder payOrder) { // 加锁 LockInfo lock = lockTemplate.lock("sync:payment" + payOrder.getId()); if (Objects.isNull(lock)){ @@ -99,7 +100,7 @@ public class PaySyncService { if (Objects.equals(syncResult.getSyncStatus(), PaySyncStatusEnum.FAIL)){ // 同步失败, 返回失败响应, 同时记录失败的日志 this.saveRecord(payOrder, syncResult, false, null, syncResult.getErrorMsg()); - return new PaySyncResult().setErrorMsg(syncResult.getErrorMsg()); + return new SyncResult().setErrorMsg(syncResult.getErrorMsg()); } // 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态 @@ -114,12 +115,12 @@ public class PaySyncService { // 同步失败, 返回失败响应, 同时记录失败的日志 syncResult.setSyncStatus(PaySyncStatusEnum.FAIL); this.saveRecord(payOrder, syncResult, false, null, e.getMessage()); - return new PaySyncResult().setErrorMsg(e.getMessage()); + return new SyncResult().setErrorMsg(e.getMessage()); } // 同步成功记录日志 this.saveRecord( payOrder, syncResult, !statusSync, repairResult.getRepairId(), null); - return new PaySyncResult() + return new SyncResult() .setGatewayStatus(syncResult.getSyncStatus().getCode()) .setSuccess(true) .setRepair(!statusSync) @@ -234,6 +235,7 @@ public class PaySyncService { PaySyncRecord paySyncRecord = new PaySyncRecord() .setOrderId(payOrder.getId()) .setOrderNo(payOrder.getBusinessNo()) + .setSyncType(PaymentTypeEnum.PAY.getCode()) .setAsyncChannel(payOrder.getAsyncChannel()) .setSyncInfo(syncResult.getSyncInfo()) .setGatewayStatus(syncResult.getSyncStatus().getCode()) diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/entity/PaySyncRecord.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/entity/PaySyncRecord.java index 1cc115d2..f23e01fe 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/entity/PaySyncRecord.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/entity/PaySyncRecord.java @@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.core.record.sync.convert.PaySyncRecordConvert; import cn.bootx.platform.daxpay.service.dto.record.sync.PaySyncRecordDto; import cn.bootx.table.modify.annotation.DbColumn; @@ -35,7 +36,14 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction< @DbColumn(comment = "本地业务号") private String orderNo; - @DbColumn(comment = "同步通道") + /** + * 同步类型 支付/退款 + * @see PaymentTypeEnum + */ + @DbColumn(comment = "同步类型") + private String syncType; + + @DbColumn(comment = "同步的异步通道") private String syncChannel; /** diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundRepairStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundRepairStrategy.java index 35c6c15f..5998997d 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundRepairStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundRepairStrategy.java @@ -1,12 +1,15 @@ package cn.bootx.platform.daxpay.service.func; +import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; +import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder; -import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult; import lombok.Getter; +import java.util.Objects; + /** * 支付退款修复策略 * @author xxm @@ -22,10 +25,10 @@ public abstract class AbsRefundRepairStrategy implements PayStrategy{ /** * 初始化参数 */ - public void initParam(PayRefundOrder refundOrder, - PayRefundChannelOrder refundChannelOrder, - PayOrder payOrder, - PayChannelOrder payChannelOrder){ + public void initRepairParam(PayRefundOrder refundOrder, + PayRefundChannelOrder refundChannelOrder, + PayOrder payOrder, + PayChannelOrder payChannelOrder){ this.refundOrder = refundOrder; this.refundChannelOrder = refundChannelOrder; this.payOrder = payOrder; @@ -34,8 +37,42 @@ public abstract class AbsRefundRepairStrategy implements PayStrategy{ /** - * 异步支付单与支付网关进行状态比对后的结果 + * 退款成功修复 */ - public abstract RefundGatewaySyncResult doSyncStatus(); + public void doSuccessHandler(){ + PayChannelOrder payChannelOrder = this.getPayChannelOrder(); + PayRefundChannelOrder refundChannelOrder = this.getRefundChannelOrder(); + + // 判断是全部退款还是部分退款 + if (Objects.equals(payChannelOrder.getRefundableBalance(), 0)){ + //全部退款 + payChannelOrder.setStatus(PayStatusEnum.REFUNDED.getCode()); + } else { + // 部分退款 + payChannelOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode()); + + } + refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode()); + } + + /** + * 退款失败, 关闭退款订单 + */ + public void doCloseHandler(){ + PayChannelOrder payChannelOrder = this.getPayChannelOrder(); + PayRefundChannelOrder refundChannelOrder = this.getRefundChannelOrder(); + int refundableBalance = payChannelOrder.getRefundableBalance() + payChannelOrder.getAmount(); + payChannelOrder.setRefundableBalance(refundableBalance); + // 判断是支付完成还是部分退款 + if (Objects.equals(payChannelOrder.getRefundableBalance(), payChannelOrder.getAmount())){ + // 全部退款 + payChannelOrder.setStatus(PayStatusEnum.SUCCESS.getCode()); + } else { + // 部分退款 + payChannelOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode()); + + } + refundChannelOrder.setStatus(PayRefundStatusEnum.CLOSE.getCode()); + } }