diff --git a/_doc/Task.md b/_doc/Task.md index c360498b..7ae5780d 100644 --- a/_doc/Task.md +++ b/_doc/Task.md @@ -89,10 +89,14 @@ - 2024-01-28: - [x] 支付宝对账单下载异常排查-支付宝每日都会生成对账单, 哪怕为空, 也会生成 - [x] 订单修复记录前端显示调整 -- 2044-01-29: +- 2044-01-30: + - [x] 退款接口更改为先落库, 后更新 - [ ] 增加退款同步策略, 对退款中的状态的退款订单进行处理 + - [ ] 退款操作支持重试 - [ ] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿 -- **任务池** +2.0.1 版本内容 + - [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单 +**任务池** - [ ] 微信退款状态不一致补偿 - [ ] 支付SDK编写 - [ ] 接入支付网关的演示项目 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 19ff1e1b..6f3d12bf 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,6 +6,7 @@ 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.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; @@ -83,8 +84,7 @@ public class PayRefundOrderController { @Operation(summary = "退款同步") @PostMapping("/syncById") - public ResResult syncById(Long ID){ - payRefundOrderService.syncById(ID); - return Res.ok(); + public ResResult syncById(Long id){ + return Res.ok(payRefundOrderService.syncById(id)); } } diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/code/PayRefundStatusEnum.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/code/PayRefundStatusEnum.java index ed4e393d..28983883 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/code/PayRefundStatusEnum.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/code/PayRefundStatusEnum.java @@ -20,9 +20,8 @@ public enum PayRefundStatusEnum { * 接口调用成功不代表成功 */ PROGRESS("progress","退款中"), - /** 部分成功 */ - PART_SUCCESS("part_success","部分成功"), SUCCESS("success","成功"), + CLOSE("close","关闭"), FAIL("fail","失败"); /** 编码 */ 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/PaySyncResult.java index b23b94d6..036539c9 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/PaySyncResult.java @@ -33,7 +33,7 @@ public class PaySyncResult extends CommonResult { @Schema(description = "是否进行了修复") private boolean repair; - @Schema(description = "支付单修复ID") + @Schema(description = "修复ID") private Long repairId; @Schema(description = "失败原因") diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/RefundSyncResult.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/RefundSyncResult.java index 30801e19..47d89cc0 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/RefundSyncResult.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/result/pay/RefundSyncResult.java @@ -13,4 +13,15 @@ import lombok.experimental.Accessors; @Accessors(chain = true) @Schema(title = "退款同步结果") public class RefundSyncResult { + + + + @Schema(description = "是否进行了修复") + private boolean repair; + + @Schema(description = "支付单修复ID") + private Long repairId; + + @Schema(description = "失败原因") + private String errorMsg; } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayCallbackTypeEnum.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayCallbackTypeEnum.java deleted file mode 100644 index 3d37a03b..00000000 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayCallbackTypeEnum.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.bootx.platform.daxpay.service.code; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 回调类型枚举 - * @author xxm - * @since 2024/1/24 - */ -@Getter -@AllArgsConstructor -public enum PayCallbackTypeEnum { - - PAY("pay", "支付回调"), - REFUND("refund", "退款回调"); - - public final String code; - public final String name; -} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayRepairPayTypeEnum.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PaymentTypeEnum.java similarity index 61% rename from daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayRepairPayTypeEnum.java rename to daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PaymentTypeEnum.java index dece359e..5a265959 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PayRepairPayTypeEnum.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/code/PaymentTypeEnum.java @@ -4,16 +4,17 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 支付修复时的支付/退款类型 + * 支付系统中常见的操作类型, 如支付/退款/转账等 * @author xxm * @since 2024/1/28 */ @Getter @AllArgsConstructor -public enum PayRepairPayTypeEnum { +public enum PaymentTypeEnum { PAY("pay","支付"), - REFUND("refund","退款"); + REFUND("refund","退款"), + TRANSFER("transfer","转账"); private final String code; private final String name; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/common/context/RefundLocal.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/common/context/RefundLocal.java index ad26a5ef..7f1a6c70 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/common/context/RefundLocal.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/common/context/RefundLocal.java @@ -1,7 +1,6 @@ package cn.bootx.platform.daxpay.service.common.context; import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; -import cn.hutool.core.util.IdUtil; import lombok.Data; import lombok.experimental.Accessors; @@ -19,11 +18,6 @@ public class RefundLocal { */ private String gatewayOrderNo; - /** - * 支付退款ID, 用于异步支付时传入的退款号, 使用退款单ID - */ - private long refundId = IdUtil.getSnowflakeNextId(); - /** * 退款状态, 默认为成功, 通常含有异步支付时, 才会出现别的状态 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayCallbackService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayCallbackService.java index 72ef0afe..fcaaef46 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayCallbackService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayCallbackService.java @@ -4,7 +4,7 @@ import cn.bootx.platform.common.core.util.CertUtil; import cn.bootx.platform.common.core.util.LocalDateTimeUtil; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayCallbackTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.common.context.CallbackLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig; @@ -137,18 +137,18 @@ public class AliPayCallbackService extends AbsCallbackStrategy { /** * 判断类型 支付回调/退款回调 * - * @see PayCallbackTypeEnum + * @see PaymentTypeEnum */ @Override - public PayCallbackTypeEnum getCallbackType() { + public PaymentTypeEnum getCallbackType() { CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo(); Map callbackParam = callback.getCallbackParam(); String refundFee = callbackParam.get(REFUND_FEE); // 如果有退款金额,说明是退款回调 if (StrUtil.isNotBlank(refundFee)){ - return PayCallbackTypeEnum.REFUND; + return PaymentTypeEnum.REFUND; } else { - return PayCallbackTypeEnum.PAY; + return PaymentTypeEnum.PAY; } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayRefundService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayRefundService.java index c6b0c56b..6d648564 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayRefundService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPayRefundService.java @@ -5,7 +5,7 @@ import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.service.code.AliPayCode; import cn.bootx.platform.daxpay.service.common.context.RefundLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; -import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; +import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder; import com.alipay.api.AlipayApiException; import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.response.AlipayTradeRefundResponse; @@ -29,16 +29,16 @@ public class AliPayRefundService { /** * 退款, 调用支付宝退款 */ - public void refund(PayOrder payOrder, int amount) { + public void refund(PayRefundOrder refundOrder, int amount) { + RefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); AlipayTradeRefundModel refundModel = new AlipayTradeRefundModel(); - refundModel.setOutTradeNo(String.valueOf(payOrder.getId())); + refundModel.setOutTradeNo(String.valueOf(refundOrder.getPaymentId())); + refundModel.setOutRequestNo(String.valueOf(refundOrder.getId())); // 金额转换 String refundAmount = String.valueOf(amount*0.01); refundModel.setRefundAmount(refundAmount); // 设置退款信息 - RefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); - refundModel.setOutRequestNo(String.valueOf(refundInfo.getRefundId())); try { AlipayTradeRefundResponse response = AliPayApi.tradeRefundToResponse(refundModel); if (!Objects.equals(AliPayCode.SUCCESS, response.getCode())) { @@ -49,9 +49,12 @@ public class AliPayRefundService { } // 接口返回fund_change=Y为退款成功,fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态 if (response.getFundChange().equals("Y")){ - refundInfo.setStatus(PayRefundStatusEnum.SUCCESS) - .setGatewayOrderNo(response.getTradeNo()); + // TODO 测试退款同步 +// refundInfo.setStatus(PayRefundStatusEnum.SUCCESS) +// .setGatewayOrderNo(response.getTradeNo()); } + refundInfo.setStatus(PayRefundStatusEnum.PROGRESS) + .setGatewayOrderNo(response.getTradeNo()); } catch (AlipayApiException e) { log.error("订单退款失败:", e); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java index dafb77cd..bc698bc0 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/alipay/service/AliPaySyncService.java @@ -51,6 +51,13 @@ public class AliPaySyncService { AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel); String tradeStatus = response.getTradeStatus(); syncResult.setSyncInfo(JSONUtil.toJsonStr(response)); + // 失败 + if (!Objects.equals(AliPayCode.SUCCESS, response.getCode())) { + syncResult.setSyncStatus(PaySyncStatusEnum.FAIL); + syncResult.setErrorCode(response.getSubCode()); + syncResult.setErrorMsg(response.getSubMsg()); + return syncResult; + } // 支付完成 TODO 部分退款也在这个地方, 但无法进行区分, 需要借助对账进行处理 if (Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_SUCCESS) || Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_FINISHED)) { PaymentContextLocal.get().getPaySyncInfo().setGatewayOrderNo(response.getTradeNo()); @@ -92,14 +99,25 @@ public class AliPaySyncService { RefundGatewaySyncResult syncResult = new RefundGatewaySyncResult().setSyncStatus(PayRefundSyncStatusEnum.FAIL); try { AlipayTradeFastpayRefundQueryModel queryModel = new AlipayTradeFastpayRefundQueryModel(); - queryModel.setOutTradeNo(String.valueOf(refundOrder.getId())); + // 退款请求号 + queryModel.setOutRequestNo(String.valueOf(refundOrder.getId())); + // 商户订单号 + queryModel.setOutTradeNo(String.valueOf(refundOrder.getPaymentId())); AlipayTradeFastpayRefundQueryResponse response = AliPayApi.tradeRefundQueryToResponse(queryModel); - syncResult.setSyncInfo(JSONUtil.toJsonStr(response)); + // 失败 + if (!Objects.equals(AliPayCode.SUCCESS, response.getCode())) { + syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL); + syncResult.setErrorCode(response.getSubCode()); + syncResult.setErrorMsg(response.getSubMsg()); + return syncResult; + } String tradeStatus = response.getRefundStatus(); // 成功 - if (Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_SUCCESS)){ + if (Objects.equals(tradeStatus, AliPayCode.REFUND_SUCCESS)){ return syncResult.setSyncStatus(PayRefundSyncStatusEnum.SUCCESS); + } else { + return syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL).setErrorMsg("支付宝网关反正退款未成功"); } } catch (AlipayApiException e) { log.error("退款订单同步失败:", e); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayCallbackService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayCallbackService.java index 80d0d313..f901c23f 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayCallbackService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WeChatPayCallbackService.java @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.service.core.channel.wechat.service; import cn.bootx.platform.common.core.util.LocalDateTimeUtil; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayCallbackTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.common.context.CallbackLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; @@ -62,8 +62,8 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy { } // 退款回调不用进行校验 - PayCallbackTypeEnum callbackType = this.getCallbackType(); - if (callbackType == PayCallbackTypeEnum.REFUND){ + PaymentTypeEnum callbackType = this.getCallbackType(); + if (callbackType == PaymentTypeEnum.REFUND){ return true; } // 支付回调信息校验 @@ -141,16 +141,16 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy { /** * 判断类型 支付回调/退款回调 * - * @see PayCallbackTypeEnum + * @see PaymentTypeEnum */ @Override - public PayCallbackTypeEnum getCallbackType() { + public PaymentTypeEnum getCallbackType() { CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo(); Map callbackParam = callbackInfo.getCallbackParam(); if (StrUtil.isNotBlank(callbackParam.get(REQ_INFO))){ - return PayCallbackTypeEnum.REFUND; + return PaymentTypeEnum.REFUND; } else { - return PayCallbackTypeEnum.PAY; + return PaymentTypeEnum.PAY; } } 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 d1b86891..fb1f59b1 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 @@ -9,8 +9,9 @@ import cn.bootx.platform.daxpay.service.code.WeChatPayCode; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager; -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.dao.PayRefundChannelOrderManager; +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.PayGatewaySyncResult; import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult; @@ -20,7 +21,7 @@ import com.ijpay.core.enums.SignType; import com.ijpay.core.kit.WxPayKit; import com.ijpay.wxpay.WxPayApi; import com.ijpay.wxpay.model.OrderQueryModel; -import com.ijpay.wxpay.model.UnifiedOrderModel; +import com.ijpay.wxpay.model.RefundQueryModel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -40,6 +41,7 @@ import java.util.Objects; @RequiredArgsConstructor public class WeChatPaySyncService { private final PayChannelOrderManager payChannelOrderManager; + private final PayRefundChannelOrderManager refundChannelOrderManager; /** * 同步查询 @@ -103,23 +105,25 @@ public class WeChatPaySyncService { } /** - * 退款查询 + * 退款信息查询 */ public RefundGatewaySyncResult syncRefundStatus(PayRefundOrder refundOrder, WeChatPayConfig weChatPayConfig){ - PayChannelOrder orderChannel = payChannelOrderManager.findByPaymentIdAndChannel(refundOrder.getId(), PayChannelEnum.WECHAT.getCode()) + PayRefundChannelOrder orderChannel = refundChannelOrderManager.findByRefundIdAndChannel(refundOrder.getId(), PayChannelEnum.WECHAT.getCode()) .orElseThrow(() -> new PayFailureException("支付订单通道信息不存在")); - Map params = UnifiedOrderModel.builder() + Map params = RefundQueryModel.builder() .appid(weChatPayConfig.getWxAppId()) .mch_id(weChatPayConfig.getWxMchId()) .nonce_str(WxPayKit.generateStr()) - .out_trade_no(String.valueOf(refundOrder.getId())) + .out_refund_no(String.valueOf(refundOrder.getId())) .build() .createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256); String xmlResult = WxPayApi.orderRefundQuery(false, params); Map result = WxPayKit.xmlToMap(xmlResult); // TODO 处理退款同步的情况 + + Integer refundFee = Integer.valueOf(result.get(WeChatPayCode.REFUND_FEE)); if (Objects.equals(refundFee, orderChannel.getAmount())){ return new RefundGatewaySyncResult().setSyncStatus(PayRefundSyncStatusEnum.REFUNDING); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatRefundService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatRefundService.java index 6bb53ff1..fac09012 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatRefundService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/wechat/service/WechatRefundService.java @@ -8,7 +8,7 @@ import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager; 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.PayRefundOrder; import cn.hutool.core.codec.Base64; import cn.hutool.core.util.StrUtil; import com.ijpay.core.enums.SignType; @@ -39,7 +39,7 @@ public class WechatRefundService { * 退款方法 * 微信需要同时传输订单金额或退款金额 */ - public void refund(PayOrder payOrder, int amount, PayChannelOrder orderChannel, WeChatPayConfig weChatPayConfig) { + public void refund(PayRefundOrder refundOrder, int amount, PayChannelOrder orderChannel, WeChatPayConfig weChatPayConfig) { String refundFee = String.valueOf(amount); String totalFee = String.valueOf(orderChannel.getAmount()); // 设置退款信息 @@ -48,8 +48,8 @@ public class WechatRefundService { .appid(weChatPayConfig.getWxAppId()) .mch_id(weChatPayConfig.getWxMchId()) .notify_url(weChatPayConfig.getNotifyUrl()) - .out_trade_no(String.valueOf(payOrder.getId())) - .out_refund_no(String.valueOf(refundInfo.getRefundId())) + .out_trade_no(String.valueOf(refundOrder.getPaymentId())) + .out_refund_no(String.valueOf(refundOrder.getId())) .total_fee(totalFee) .refund_fee(refundFee) .nonce_str(WxPayKit.generateStr()) diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java index 152c9dc2..6fed2ee3 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java @@ -87,7 +87,7 @@ public class PayChannelOrderService { * 更新异步支付通道退款余额和状态 */ public void updateAsyncPayRefund(PayChannelOrder payChannelOrder, PayRefundChannelOrder refundChannelOrder){ - // 支付通道订单客可退余额 + // 支付通道订单可退余额 int refundableBalance = payChannelOrder.getRefundableBalance() - refundChannelOrder.getAmount(); payChannelOrder.setRefundableBalance(refundableBalance); // 支付通道订单状态 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/reconcile/service/PayReconcileOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/reconcile/service/PayReconcileOrderService.java index 715560cd..31ef1a73 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/reconcile/service/PayReconcileOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/reconcile/service/PayReconcileOrderService.java @@ -31,7 +31,7 @@ public class PayReconcileOrderService { /** * 更新, 开启一个新事务进行更新 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void update(PayReconcileOrder order){ reconcileOrderManager.updateById(order); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundChannelOrderManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundChannelOrderManager.java index c9da28a0..57b7386d 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundChannelOrderManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/dao/PayRefundChannelOrderManager.java @@ -29,9 +29,9 @@ public class PayRefundChannelOrderManager extends BaseManager findByPaymentIdAndChannel(Long paymentId, String channel) { + public Optional findByRefundIdAndChannel(Long refundId, String channel) { return lambdaQuery() - .eq(PayRefundChannelOrder::getRefundId,paymentId) + .eq(PayRefundChannelOrder::getRefundId,refundId) .eq(PayRefundChannelOrder::getChannel,channel) .oneOpt(); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java index 12d37f9b..acc6b7e8 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/refund/entity/PayRefundOrder.java @@ -8,6 +8,8 @@ import cn.bootx.platform.daxpay.service.core.order.refund.convert.PayRefundOrder import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto; import cn.bootx.table.modify.annotation.DbColumn; import cn.bootx.table.modify.annotation.DbTable; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -17,6 +19,7 @@ import java.time.LocalDateTime; /** * 退款记录 + * 主键作为退款的请求号 * * @author xxm * @since 2022/3/2 @@ -71,6 +74,7 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction

new DataNotExistException("退款订单不存在")); - refundSyncService.syncPayOrder(refundOrder); + return refundSyncService.syncRefundOrder(refundOrder); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundAssistService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundAssistService.java index e54c6756..85490980 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundAssistService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundAssistService.java @@ -18,6 +18,7 @@ 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.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,6 +32,8 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import static cn.bootx.platform.daxpay.code.PayRefundStatusEnum.SUCCESS; + /** * 支付退款支撑服务 * @author xxm @@ -109,10 +112,11 @@ public class PayRefundAssistService { List tradesStatus = Arrays.asList( PayStatusEnum.PROGRESS.getCode(), PayStatusEnum.CLOSE.getCode(), + PayStatusEnum.REFUNDING.getCode(), PayStatusEnum.FAIL.getCode()); if (tradesStatus.contains(payOrder.getStatus())) { PayStatusEnum statusEnum = PayStatusEnum.findByCode(payOrder.getStatus()); - throw new PayFailureException("当前状态["+statusEnum.getName()+"]不允许状态非法, 无法退款"); + throw new PayFailureException("当前状态["+statusEnum.getName()+"]不允许发起退款操作"); } // 过滤掉金额为0的退款参数 @@ -130,36 +134,29 @@ public class PayRefundAssistService { } /** - * 保存退款订单 成不成功都记录 + * 预先创建退款相关订单并保存, 使用新事务, 防止丢单 */ - public PayRefundOrder generateRefundOrder(RefundParam refundParam, PayOrder payOrder){ - RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo(); + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) + public PayRefundOrder createOrderAndChannel(RefundParam refundParam, PayOrder payOrder, List refundChannelOrders) { // 此次的总退款金额 Integer amount = refundParam.getRefundChannels() .stream() .map(RefundChannelParam::getAmount) .reduce(0, Integer::sum); + int refundableBalance = payOrder.getRefundableBalance(); + // 生成退款订单 PayRefundOrder refundOrder = new PayRefundOrder() .setPaymentId(payOrder.getId()) + .setStatus(PayRefundStatusEnum.PROGRESS.getCode()) .setBusinessNo(payOrder.getBusinessNo()) .setRefundNo(refundParam.getRefundNo()) .setOrderAmount(payOrder.getAmount()) .setAmount(amount) - .setRefundableBalance(payOrder.getRefundableBalance()) - .setRefundTime(LocalDateTime.now()) + .setRefundableBalance(refundableBalance) .setTitle(payOrder.getTitle()) - .setGatewayOrderNo(asyncRefundInfo.getGatewayOrderNo()) - .setStatus(asyncRefundInfo.getStatus().getCode()) .setClientIp(refundParam.getClientIp()) .setReqId(PaymentContextLocal.get().getRequestInfo().getReqId()); - // 错误状态特殊处理 - if (asyncRefundInfo.getStatus() == PayRefundStatusEnum.FAIL){ - refundOrder.setErrorCode(asyncRefundInfo.getErrorCode()); - refundOrder.setErrorMsg(asyncRefundInfo.getErrorMsg()); - // 退款失败不保存剩余可退余额, 否则数据看起开会产生困惑 - refundOrder.setRefundableBalance(null); - } // 退款参数中是否存在异步通道 RefundChannelParam asyncChannel = refundParam.getRefundChannels() @@ -173,33 +170,43 @@ public class PayRefundAssistService { } // 主键使用预先生成的ID, 如果有异步通道, 关联的退款号就是这个ID - long refundId = asyncRefundInfo.getRefundId(); - refundOrder.setId(refundId); + refundOrder.setId(IdUtil.getSnowflakeNextId()); // 退款号, 如不传输, 使用ID作为退款号 if(StrUtil.isBlank(refundOrder.getRefundNo())){ - refundOrder.setRefundNo(String.valueOf(refundId)); + refundOrder.setRefundNo(String.valueOf(refundOrder.getId())); } - return refundOrder; + refundChannelOrders.forEach(r->r.setRefundId(refundOrder.getId())); + payRefundChannelOrderManager.saveAll(refundChannelOrders); + return payRefundOrderManager.save(refundOrder); } /** - * 保存退款记录和对应的通道记录 + * 更新退款成功信息 */ @Transactional(rollbackFor = Exception.class) - public void saveOrderAndChannels(PayRefundOrder refundOrder,List refundChannelOrders){ - payRefundOrderManager.save(refundOrder); - for (PayRefundChannelOrder refundOrderChannel : refundChannelOrders) { - refundOrderChannel.setRefundId(refundOrder.getId()); + public void updateOrderAndChannel(PayRefundOrder refundOrder, List refundChannelOrders){ + RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo(); + refundOrder.setStatus(asyncRefundInfo.getStatus().getCode()) + .setGatewayOrderNo(asyncRefundInfo.getGatewayOrderNo()); + // 退款成功更新退款时间 + if (Objects.equals(refundOrder.getStatus(), SUCCESS.getCode())){ + refundOrder.setRefundTime(LocalDateTime.now()); } - payRefundChannelOrderManager.saveAll(refundChannelOrders); + payRefundOrderManager.updateById(refundOrder); + payRefundChannelOrderManager.updateAllById(refundChannelOrders); } /** - * 保存退款记录, 开启新事物 + * 更新退款错误信息 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void saveOrder(PayRefundOrder refundOrder){ - payRefundOrderManager.save(refundOrder); + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) + public void updateOrderByError(PayRefundOrder refundOrder){ + RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo(); + refundOrder.setErrorCode(asyncRefundInfo.getErrorCode()); + refundOrder.setErrorMsg(asyncRefundInfo.getErrorMsg()); + // 退款失败不保存剩余可退余额, 否则数据看起开会产生困惑 + refundOrder.setRefundableBalance(null); } + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundService.java index 941d15a1..98475e3c 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/service/PayRefundService.java @@ -23,11 +23,13 @@ import cn.bootx.platform.daxpay.service.core.payment.refund.factory.PayRefundStr import cn.bootx.platform.daxpay.service.func.AbsRefundStrategy; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.extra.spring.SpringUtil; import com.baomidou.lock.LockInfo; import com.baomidou.lock.LockTemplate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.Collections; @@ -126,78 +128,99 @@ public class PayRefundService { /** * 分支付通道进行退款 - * TODO 增加错误处理, 目前出现错误后存储的数据不全 + * 1. 创建退款订单和通道订单并保存(单独事务) + * 2. 调用API发起退款(异步退款) + * 3. 根据API返回信息更新退款订单信息 */ public RefundResult refundByChannel(RefundParam refundParam, PayOrder payOrder, List payChannelOrders){ - // 0.基础数据准备, 并比对通道支付单是否与可退款记录数量一致 + // 1.1 基础数据准备 Map orderChannelMap = payChannelOrders.stream() .collect(Collectors.toMap(PayChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest)); List refundChannels = refundParam.getRefundChannels(); + // 1.2获取退款参数方式,通过工厂生成对应的策略组 + List payRefundStrategies = PayRefundStrategyFactory.createAsyncLast(refundChannels); + if (CollectionUtil.isEmpty(payRefundStrategies)) { + throw new PayUnsupportedMethodException(); + } + // 1.3初始化退款策略的参数 + for (AbsRefundStrategy refundStrategy : payRefundStrategies) { + PayChannelOrder payChannelOrder = orderChannelMap.get(refundStrategy.getChannel().getCode()); + if (Objects.isNull(payChannelOrder)){ + throw new PayFailureException("[数据异常]进行退款的通道没有对应的支付单, 无法退款"); + } + refundStrategy.initRefundParam(payOrder, refundParam, payChannelOrder); + } + + // 对通道支付订单进行预扣款 + payRefundStrategies.forEach(AbsRefundStrategy::doPreDeductOrderHandler); + + // 退款操作的预处理, 使用独立的新事物进行发起, 返回创建成功的退款订单, 成功后才可以进行下一阶段的操作 + PayRefundOrder refundOrder = SpringUtil.getBean(this.getClass()).preRefundMethod(refundParam, payOrder, payRefundStrategies); + + // 设置退款订单对象 + payRefundStrategies.forEach(r->r.setRefundOrder(refundOrder)); + try { - // 1.获取退款参数方式,通过工厂生成对应的策略组 - List payRefundStrategies = PayRefundStrategyFactory.createAsyncLast(refundChannels); - if (CollectionUtil.isEmpty(payRefundStrategies)) { - throw new PayUnsupportedMethodException(); - } - - // 2.初始化退款策略的参数 - for (AbsRefundStrategy refundStrategy : payRefundStrategies) { - PayChannelOrder payChannelOrder = orderChannelMap.get(refundStrategy.getChannel().getCode()); - if (Objects.isNull(payChannelOrder)){ - throw new PayFailureException("[数据异常]进行退款的通道没有对应的支付单, 无法退款"); - } - refundStrategy.initRefundParam(payOrder, refundParam, payChannelOrder); - } - // 3.1 退款前准备操作 payRefundStrategies.forEach(AbsRefundStrategy::doBeforeRefundHandler); - // 3.2 生成各通道退款订单 - payRefundStrategies.forEach(AbsRefundStrategy::generateChannelOrder); - // 3.3 执行退款策略 + // 3.2 执行退款策略 payRefundStrategies.forEach(AbsRefundStrategy::doRefundHandler); // 3.4 执行退款发起成功后操作 payRefundStrategies.forEach(AbsRefundStrategy::doSuccessHandler); - // 4 更新各支付通道订单的信息 - List channelOrders = payRefundStrategies.stream() - .map(AbsRefundStrategy::getPayChannelOrder) - .collect(Collectors.toList()); - payChannelOrderManager.updateAllById(channelOrders); - - // 5 获取退款通道订单, 进行保存 + // 4.进行成功处理, 分别处理退款订单, 通道退款订单, 支付订单 List refundChannelOrders = payRefundStrategies.stream() .map(AbsRefundStrategy::getRefundChannelOrder) .collect(Collectors.toList()); - - // 6.进行成功处理, 分别处理退款订单, 通道退款订单, 支付订单 - PayRefundOrder refundOrder = this.successHandler(refundParam, refundChannelOrders, payOrder); + this.successHandler(refundOrder, refundChannelOrders, payOrder); return new RefundResult() .setRefundId(refundOrder.getId()) .setRefundNo(refundParam.getRefundNo()); } catch (Exception e) { - // 失败处理 + // 5. 失败处理 PaymentContextLocal.get().getRefundInfo().setStatus(PayRefundStatusEnum.FAIL); - this.errorHandler(refundParam, payOrder); + this.errorHandler(refundOrder); throw e; } } /** - * 退款订单成功处理, 保存退款订单, 通道退款订单, 更新支付订单 + * 退款一阶段: 进行支付订单和支付通道订单的预扣, 预创建退款订单并保存, 使用独立的新事物进行发起 */ - private PayRefundOrder successHandler(RefundParam refundParam, List refundChannelOrders, PayOrder payOrder) { - RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo(); - // ----------------------- 支付订单处理 --------------------------------------- + @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class ) + public PayRefundOrder preRefundMethod(RefundParam refundParam, PayOrder payOrder, List payRefundStrategies) { + // --------------------------- 支付订单 ------------------------------------- + // 预扣支付相关订单要退款的金额并进行更新 + payRefundStrategies.forEach(AbsRefundStrategy::generateChannelOrder); + List channelOrders = payRefundStrategies.stream() + .map(AbsRefundStrategy::getPayChannelOrder) + .collect(Collectors.toList()); + payChannelOrderManager.updateAllById(channelOrders); // 此次的总退款金额 - Integer amount = refundParam.getRefundChannels().stream() + Integer amount = refundParam.getRefundChannels() + .stream() .map(RefundChannelParam::getAmount) .reduce(0, Integer::sum); - // 剩余可退款余额 - int refundableBalance = payOrder.getRefundableBalance() - amount; - payOrder.setRefundableBalance(refundableBalance); + int orderRefundableBalance = payOrder.getRefundableBalance() - amount; + payOrder.setRefundableBalance(orderRefundableBalance) + .setStatus(PayStatusEnum.REFUNDING.getCode()); + payOrderService.updateById(payOrder); + // ----------------------- 退款订单 ------------------------- + List refundChannelOrders = payRefundStrategies.stream() + .map(AbsRefundStrategy::getRefundChannelOrder) + .collect(Collectors.toList()); + return payRefundAssistService.createOrderAndChannel(refundParam, payOrder,refundChannelOrders); + } + /** + * 成功处理, 更新退款订单, 退款通道订单, 支付订单, 支付通道订单 + */ + private void successHandler(PayRefundOrder payRefundOrder, List refundChannelOrders, PayOrder payOrder) { + RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo(); + // 剩余可退款余额 + int refundableBalance = payRefundOrder.getRefundableBalance(); // 设置支付订单状态 if (asyncRefundInfo.getStatus() == PayRefundStatusEnum.PROGRESS) { // 设置为退款中 @@ -209,22 +232,17 @@ public class PayRefundService { // 部分退款 payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode()); } - - // ----------------------- 退款订单处理 --------------------------------------- - // 生成退款订单 - PayRefundOrder refundOrder = payRefundAssistService.generateRefundOrder(refundParam, payOrder); - // 更新或保存相关订单 - payRefundAssistService.saveOrderAndChannels(refundOrder,refundChannelOrders); payOrderService.updateById(payOrder); - return refundOrder; + + // 更新退款订单和相关通道订单 + payRefundAssistService.updateOrderAndChannel(payRefundOrder,refundChannelOrders); } /** - * 失败处理 + * 失败处理, 只更新退款订单, 通道订单不进行错误更新 */ - private void errorHandler(RefundParam refundParam, PayOrder payOrder) { + private void errorHandler(PayRefundOrder refundOrder) { // 记录退款失败的记录 - PayRefundOrder refundOrder = payRefundAssistService.generateRefundOrder(refundParam, payOrder); - payRefundAssistService.saveOrder(refundOrder); + payRefundAssistService.updateOrderByError(refundOrder); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/AliPayRefundStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/AliPayRefundStrategy.java index 9ef6a0fb..7f91d4f3 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/AliPayRefundStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/AliPayRefundStrategy.java @@ -12,8 +12,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; - import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; /** @@ -54,7 +52,7 @@ public class AliPayRefundStrategy extends AbsRefundStrategy { */ @Override public void doRefundHandler() { - aliRefundService.refund(this.getPayOrder(), this.getRefundChannelParam().getAmount()); + aliRefundService.refund(this.getRefundOrder(), this.getRefundChannelParam().getAmount()); } /** diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WalletPayRefundStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WalletPayRefundStrategy.java index 15dd8ad3..85a19e51 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WalletPayRefundStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WalletPayRefundStrategy.java @@ -1,8 +1,8 @@ package cn.bootx.platform.daxpay.service.core.payment.refund.strategy; import cn.bootx.platform.daxpay.code.PayChannelEnum; -import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayService; import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayOrderService; +import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayService; import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.service.func.AbsRefundStrategy; import lombok.RequiredArgsConstructor; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WeChatPayRefundStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WeChatPayRefundStrategy.java index 6ff42323..4c998290 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WeChatPayRefundStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/refund/strategy/WeChatPayRefundStrategy.java @@ -12,8 +12,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import java.time.LocalDateTime; - import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; /** @@ -52,12 +50,13 @@ public class WeChatPayRefundStrategy extends AbsRefundStrategy { this.weChatPayConfig = weChatPayConfigService.getConfig(); } + /** * 退款操作 */ @Override public void doRefundHandler() { - wechatRefundService.refund(this.getPayOrder(), this.getRefundChannelParam().getAmount(), this.getPayChannelOrder(), this.weChatPayConfig); + wechatRefundService.refund(this.getRefundOrder(), this.getRefundChannelParam().getAmount(), this.getPayChannelOrder(), this.weChatPayConfig); } /** diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/PayRepairService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/PayRepairService.java index 74b76d99..ab6ef948 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/PayRepairService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/repair/service/PayRepairService.java @@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.repair.service; import cn.bootx.platform.common.core.function.CollectorsFunction; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayRepairPayTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager; @@ -161,7 +161,7 @@ public class PayRepairService { .setOrderNo(order.getBusinessNo()) .setBeforeStatus(repairResult.getBeforeStatus().getCode()) .setAfterStatus(afterStatus) - .setRepairType(PayRepairPayTypeEnum.PAY.getCode()) + .setRepairType(PaymentTypeEnum.PAY.getCode()) .setRepairSource(source) .setRepairWay(recordType.getCode()); payRepairRecord.setId(repairResult.getRepairId()); 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 58133604..37a97198 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 @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.service.core.payment.repair.service; import cn.bootx.platform.common.core.exception.DataNotExistException; import cn.bootx.platform.daxpay.code.PayRefundStatusEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayRepairPayTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.code.RefundRepairWayEnum; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager; @@ -57,15 +57,16 @@ public class RefundRepairService { // 获取关联支付单 PayOrder payOrder = payOrderManager.findById(refundOrder.getPaymentId()) .orElseThrow(() -> new RuntimeException("支付单不存在")); - // 关联异步支付通道支付单 + // 关联支付通道支付单 PayChannelOrder payChannelOrder = payChannelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payOrder.getAsyncChannel()) .orElseThrow(DataNotExistException::new); // 异步通道退款单 - PayRefundChannelOrder refundChannelOrder = refundChannelOrderManager.findByPaymentIdAndChannel(refundOrder.getId(), payOrder.getAsyncChannel()) + PayRefundChannelOrder refundChannelOrder = refundChannelOrderManager.findByRefundIdAndChannel(refundOrder.getId(), payOrder.getAsyncChannel()) .orElseThrow(DataNotExistException::new); // 根据不同的类型执行对应的修复逻辑 RefundRepairResult repairResult = new RefundRepairResult(); + //TODO 整个退款单是一个状态, 最终结果要么全部成功, 要么全部回退 if (Objects.requireNonNull(repairType) == RefundRepairWayEnum.SUCCESS) { repairResult = this.success(refundOrder,payOrder,refundChannelOrder,payChannelOrder); } else if (repairType == RefundRepairWayEnum.FAIL) { @@ -164,7 +165,6 @@ public class RefundRepairService { // 退款单设置为部分成功状态, 通道退款单设置为失败状态 refundOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); refundChannelOrder.setStatus(PayRefundStatusEnum.FAIL.getCode()); - repairResult.setAfterRefundStatus(PayRefundStatusEnum.PART_SUCCESS); } // 更新订单和退款相关订单 @@ -190,7 +190,7 @@ public class RefundRepairService { return new PayRepairRecord() .setRepairId(repairResult.getRepairId()) .setOrderId(order.getId()) - .setRepairType(PayRepairPayTypeEnum.PAY.getCode()) + .setRepairType(PaymentTypeEnum.PAY.getCode()) .setRepairSource(source) .setRepairWay(repairType.getCode()) .setAsyncChannel(order.getAsyncChannel()) @@ -214,7 +214,7 @@ public class RefundRepairService { .setOrderId(refundOrder.getId()) .setRepairId(repairResult.getRepairId()) .setOrderNo(refundOrder.getRefundNo()) - .setRepairType(PayRepairPayTypeEnum.REFUND.getCode()) + .setRepairType(PaymentTypeEnum.REFUND.getCode()) .setBeforeStatus(repairResult.getBeforeRefundStatus().getCode()) .setAfterStatus(afterStatus) .setRepairSource(source) 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 a6c7a4c1..60ddd1c5 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 @@ -4,10 +4,15 @@ 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.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.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; +import cn.bootx.platform.daxpay.service.core.record.sync.service.PaySyncRecordService; import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy; import com.baomidou.lock.LockInfo; import com.baomidou.lock.LockTemplate; @@ -30,12 +35,14 @@ import java.util.Objects; public class PayRefundSyncService { private final PayRefundOrderManager refundOrderManager; + private final PaySyncRecordService paySyncRecordService; + private final LockTemplate lockTemplate; /** * 退款同步, 开启一个新的事务, 不受外部抛出异常的影响 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void sync(RefundSyncParam param){ // 先获取退款单 PayRefundOrder requestOrder; @@ -51,12 +58,15 @@ public class PayRefundSyncService { // TODO 需要限制同步的请求不进行同步 return; } - this.syncPayOrder(requestOrder); + this.syncRefundOrder(requestOrder); } - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void syncPayOrder(PayRefundOrder refundOrder) { + /** + * 退款订单信息同步 + */ + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) + public PaySyncResult syncRefundOrder(PayRefundOrder refundOrder) { // 加锁 LockInfo lock = lockTemplate.lock("sync:refund:" + refundOrder.getId()); if (Objects.isNull(lock)) { @@ -73,36 +83,66 @@ public class PayRefundSyncService { // 判断是否同步成功 if (Objects.equals(syncResult.getSyncStatus(), PayRefundSyncStatusEnum.FAIL)) { // 同步失败, 返回失败响应, 同时记录失败的日志 -// return new PaySyncResult().setErrorMsg(syncResult.getErrorMsg()); - log.error("同步失败"); - return; + return new PaySyncResult().setErrorMsg(syncResult.getErrorMsg()); } - // 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态 -// boolean statusSync = this.checkAndAdjustSyncStatus(syncResult, payOrder); -// PayRepairResult repairResult = new PayRepairResult(); -// try { +// 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态 + boolean statusSync = this.checkAndAdjustSyncStatus(syncResult, refundOrder); + RefundRepairResult repairResult = new RefundRepairResult(); + try { // // 状态不一致,执行支付单修复逻辑 -// if (!statusSync) { -// repairResult = this.resultHandler(syncResult, payOrder); -// } -// } catch (PayFailureException e) { -// // 同步失败, 返回失败响应, 同时记录失败的日志 -// syncResult.setSyncStatus(PaySyncStatusEnum.FAIL); -// this.saveRecord(payOrder, syncResult, false, null, e.getMessage()); -// return new PaySyncResult().setErrorMsg(e.getMessage()); -// } -// + if (!statusSync) { + repairResult = this.repairHandler(syncResult, refundOrder); + } + } catch (PayFailureException e) { + // 同步失败, 返回失败响应, 同时记录失败的日志 + syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL); + this.saveRecord(refundOrder, syncResult, false, null, e.getMessage()); + return new PaySyncResult().setErrorMsg(e.getMessage()); + } // // 同步成功记录日志 -// this.saveRecord(payOrder, syncResult, !statusSync, repairResult.getRepairId(), null); -// return new PaySyncResult() -// .setGatewayStatus(syncResult.getSyncStatus() -// .getCode()) -// .setSuccess(true) -// .setRepair(!statusSync) -// .setRepairId(repairResult.getRepairId()); + this.saveRecord(refundOrder, syncResult, !statusSync, repairResult.getRepairId(), null); + return new PaySyncResult() + .setGatewayStatus(syncResult.getSyncStatus().getCode()) + .setSuccess(true) + .setRepair(!statusSync) + .setRepairId(repairResult.getRepairId()); } finally { lockTemplate.releaseLock(lock); } } + + + + private boolean checkAndAdjustSyncStatus(RefundGatewaySyncResult syncResult, PayRefundOrder order){ + return true; + } + + private RefundRepairResult repairHandler(RefundGatewaySyncResult syncResult, PayRefundOrder order){ + return null; + } + + + + /** + * 保存同步记录 + * @param payOrder 支付单 + * @param syncResult 同步结果 + * @param repair 是否修复 + * @param errorMsg 错误信息 + */ + private void saveRecord(PayRefundOrder payOrder, RefundGatewaySyncResult syncResult, boolean repair, Long repairOrderId, String errorMsg){ + PaySyncRecord paySyncRecord = new PaySyncRecord() + .setOrderId(payOrder.getId()) + .setOrderNo(payOrder.getBusinessNo()) + .setAsyncChannel(payOrder.getAsyncChannel()) + .setSyncInfo(syncResult.getSyncInfo()) + .setGatewayStatus(syncResult.getSyncStatus().getCode()) + .setRepairOrder(repair) + .setRepairOrderId(repairOrderId) + .setErrorMsg(errorMsg) + .setClientIp(PaymentContextLocal.get().getRequestInfo().getClientIp()) + .setReqId(PaymentContextLocal.get().getRequestInfo().getReqId()); + paySyncRecordService.saveRecord(paySyncRecord); + } } 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 cf8db51c..75b45632 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 @@ -57,7 +57,7 @@ public class PaySyncService { /** * 支付同步, 开启一个新的事务, 不受外部抛出异常的影响 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public PaySyncResult sync(PaySyncParam param) { PayOrder payOrder = null; if (Objects.nonNull(param.getPaymentId())){ @@ -81,7 +81,7 @@ public class PaySyncService { * 2. 如果状态不一致, 调用修复逻辑进行修复 * todo 需要进行异常处理, 现在会有 Transaction rolled back because it has been marked as rollback-only 问题 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public PaySyncResult syncPayOrder(PayOrder payOrder) { // 加锁 LockInfo lock = lockTemplate.lock("sync:payment" + payOrder.getId()); @@ -108,7 +108,7 @@ public class PaySyncService { try { // 状态不一致,执行支付单修复逻辑 if (!statusSync){ - repairResult = this.resultHandler(syncResult, payOrder); + repairResult = this.repairHandler(syncResult, payOrder); } } catch (PayFailureException e) { // 同步失败, 返回失败响应, 同时记录失败的日志 @@ -173,9 +173,9 @@ public class PaySyncService { } /** - * 根据同步的结果对支付单进行处理 + * 根据同步的结果对支付单进行修复处理 */ - private PayRepairResult resultHandler(PayGatewaySyncResult syncResult, PayOrder payOrder){ + private PayRepairResult repairHandler(PayGatewaySyncResult syncResult, PayOrder payOrder){ PaySyncStatusEnum syncStatusEnum = syncResult.getSyncStatus(); // 如果没有支付来源, 设置支付来源为同步 RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo(); @@ -195,6 +195,8 @@ public class PaySyncService { repair = repairService.repair(payOrder, PayRepairWayEnum.WAIT_PAY); break; } + case REFUND: + throw new PayFailureException("支付订单为退款状态,请通过执行对应的退款订单进行同步,来更新具体为什么类型退款状态"); // 交易关闭和未找到, 都对本地支付订单进行关闭, 不需要再调用网关进行关闭 case CLOSED: case NOT_FOUND: { @@ -222,7 +224,7 @@ public class PaySyncService { /** - * 保存同步记录 TODO 目前出现一次请求多次与网关同步, 未全部记录 + * 保存同步记录 * @param payOrder 支付单 * @param syncResult 同步结果 * @param repair 是否修复 @@ -230,8 +232,8 @@ public class PaySyncService { */ private void saveRecord(PayOrder payOrder, PayGatewaySyncResult syncResult, boolean repair, Long repairOrderId, String errorMsg){ PaySyncRecord paySyncRecord = new PaySyncRecord() - .setPaymentId(payOrder.getId()) - .setBusinessNo(payOrder.getBusinessNo()) + .setOrderId(payOrder.getId()) + .setOrderNo(payOrder.getBusinessNo()) .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/strategy/Refund/WeChatRefundSyncStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/strategy/Refund/WeChatRefundSyncStrategy.java index ff45f03f..2646a92e 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/strategy/Refund/WeChatRefundSyncStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/sync/strategy/Refund/WeChatRefundSyncStrategy.java @@ -1,6 +1,9 @@ package cn.bootx.platform.daxpay.service.core.payment.sync.strategy.Refund; import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; +import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService; +import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPaySyncService; import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult; import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy; import lombok.RequiredArgsConstructor; @@ -18,6 +21,8 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT @Component @RequiredArgsConstructor public class WeChatRefundSyncStrategy extends AbsRefundSyncStrategy { + private final WeChatPaySyncService weChatPaySyncService; + private final WeChatPayConfigService weChatPayConfigService; /** * 策略标识 @@ -32,7 +37,8 @@ public class WeChatRefundSyncStrategy extends AbsRefundSyncStrategy { */ @Override public RefundGatewaySyncResult doSyncStatus() { - return null; + WeChatPayConfig config = weChatPayConfigService.getConfig(); + return weChatPaySyncService.syncRefundStatus(this.getRefundOrder(), config); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/entity/PayCallbackRecord.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/entity/PayCallbackRecord.java index 8434bbb5..1ca141e6 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/entity/PayCallbackRecord.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/entity/PayCallbackRecord.java @@ -4,7 +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.service.code.PayCallbackStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayCallbackTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.core.record.callback.convert.PayCallbackRecordConvert; import cn.bootx.platform.daxpay.service.dto.record.callback.PayCallbackRecordDto; import cn.bootx.table.modify.annotation.DbColumn; @@ -46,7 +46,7 @@ public class PayCallbackRecord extends MpCreateEntity implements EntityBaseFunct /** * 回调类型 - * @see PayCallbackTypeEnum + * @see PaymentTypeEnum */ @DbColumn(comment = "回调类型") private String callbackType; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/service/PayCallbackRecordService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/service/PayCallbackRecordService.java index af22d864..220dcb98 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/service/PayCallbackRecordService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/callback/service/PayCallbackRecordService.java @@ -42,7 +42,7 @@ public class PayCallbackRecordService { /** * 保存回调记录 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void save(PayCallbackRecord record) { callbackRecordManager.save(record); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/close/service/PayCloseRecordService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/close/service/PayCloseRecordService.java index 7a6b96f3..e5af7ae2 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/close/service/PayCloseRecordService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/close/service/PayCloseRecordService.java @@ -43,7 +43,7 @@ public class PayCloseRecordService { /** * 新开事务进行记录保存 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void saveRecord(PayCloseRecord record){ manager.save(record); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/entity/PayRepairRecord.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/entity/PayRepairRecord.java index 13a4a160..2aa8bbac 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/entity/PayRepairRecord.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/entity/PayRepairRecord.java @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.service.core.record.repair.entity; import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity; import cn.bootx.platform.daxpay.code.PayStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayRepairPayTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum; import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum; import cn.bootx.platform.daxpay.service.code.RefundRepairWayEnum; @@ -48,7 +48,7 @@ public class PayRepairRecord extends MpCreateEntity implements EntityBaseFunctio /** * 修复类型 支付修复/退款修复 - * @see PayRepairPayTypeEnum + * @see PaymentTypeEnum */ @DbColumn(comment = "修复类型") private String repairType; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/service/PayRepairRecordService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/service/PayRepairRecordService.java index 2804808a..c12a0a59 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/service/PayRepairRecordService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/repair/service/PayRepairRecordService.java @@ -44,14 +44,14 @@ public class PayRepairRecordService { /** * 保存记录 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void saveRecord(PayRepairRecord record){ repairRecordManager.save(record); } /** * 保存记录 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void saveAllRecord(List records){ repairRecordManager.saveAll(records); } 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 4a1c9d34..1cc115d2 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 @@ -27,13 +27,16 @@ import lombok.experimental.Accessors; @TableName("pay_sync_record") public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction { - /** 支付记录id */ - @DbColumn(comment = "支付记录id") - private Long paymentId; + /** 本地订单ID */ + @DbColumn(comment = "本地订单ID") + private Long orderId; - /** 业务号 */ - @DbColumn(comment = "业务号") - private String businessNo; + /** 本地业务号 */ + @DbColumn(comment = "本地业务号") + private String orderNo; + + @DbColumn(comment = "同步通道") + private String syncChannel; /** * 同步通道 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/service/PaySyncRecordService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/service/PaySyncRecordService.java index b451c8ad..6d6fa997 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/service/PaySyncRecordService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/record/sync/service/PaySyncRecordService.java @@ -44,7 +44,7 @@ public class PaySyncRecordService { /** * 记录同步记录 同步支付单的不进行记录 */ - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void saveRecord(PaySyncRecord paySyncRecord){ orderManager.save(paySyncRecord); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/record/callback/PayCallbackRecordDto.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/record/callback/PayCallbackRecordDto.java index 57210a34..9abaebf7 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/record/callback/PayCallbackRecordDto.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/dto/record/callback/PayCallbackRecordDto.java @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.service.dto.record.callback; import cn.bootx.platform.common.core.rest.dto.BaseDto; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.service.code.PayCallbackStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayCallbackTypeEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -34,7 +34,7 @@ public class PayCallbackRecordDto extends BaseDto { /** * 回调类型 - * @see PayCallbackTypeEnum + * @see PaymentTypeEnum */ @Schema(description = "回调类型") private String callbackType; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsCallbackStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsCallbackStrategy.java index 1b8294ba..eb99b9b8 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsCallbackStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsCallbackStrategy.java @@ -1,8 +1,8 @@ package cn.bootx.platform.daxpay.service.func; import cn.bootx.platform.daxpay.service.code.PayCallbackStatusEnum; -import cn.bootx.platform.daxpay.service.code.PayCallbackTypeEnum; import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum; +import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum; import cn.bootx.platform.daxpay.service.common.context.CallbackLocal; import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.service.core.payment.callback.service.PayCallbackService; @@ -51,8 +51,8 @@ public abstract class AbsCallbackStrategy implements PayStrategy { PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK); // 判断回调类型 - PayCallbackTypeEnum callbackType = this.getCallbackType(); - if (callbackType == PayCallbackTypeEnum.PAY){ + PaymentTypeEnum callbackType = this.getCallbackType(); + if (callbackType == PaymentTypeEnum.PAY){ // 解析支付数据并放处理 this.resolvePayData(); payCallbackService.payCallback(); @@ -78,9 +78,9 @@ public abstract class AbsCallbackStrategy implements PayStrategy { /** * 判断类型 支付回调/退款回调 - * @see PayCallbackTypeEnum + * @see PaymentTypeEnum */ - public abstract PayCallbackTypeEnum getCallbackType(); + public abstract PaymentTypeEnum getCallbackType(); /** * 解析支付回调数据并放到上下文中 @@ -105,7 +105,7 @@ public abstract class AbsCallbackStrategy implements PayStrategy { // 回调类型 String callbackType = Optional.ofNullable(this.getCallbackType()) - .map(PayCallbackTypeEnum::getCode) + .map(PaymentTypeEnum::getCode) .orElse(null); PayCallbackRecord payNotifyRecord = new PayCallbackRecord() diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundStrategy.java index 3b48079b..2af3234f 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundStrategy.java @@ -7,6 +7,7 @@ import cn.bootx.platform.daxpay.param.pay.RefundParam; 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 lombok.Getter; import lombok.Setter; @@ -25,6 +26,9 @@ public abstract class AbsRefundStrategy implements PayStrategy{ /** 支付订单 */ private PayOrder payOrder = null; + /** 退款订单 已经持久化, 后续需要更新 */ + private PayRefundOrder refundOrder = null; + /** 当前通道的订单 */ private PayChannelOrder payChannelOrder = null; @@ -34,7 +38,7 @@ public abstract class AbsRefundStrategy implements PayStrategy{ /** 当前通道的退款参数 退款参数中的与这个不一致, 以这个为准 */ private RefundChannelParam refundChannelParam = null; - /** 当前通道的退款订单 */ + /** 当前通道的退款订单 未持久化, 需要后续更新 */ private PayRefundChannelOrder refundChannelOrder; /** @@ -46,8 +50,19 @@ public abstract class AbsRefundStrategy implements PayStrategy{ this.refundParam = refundParam; } + /** - * 退款前对处理 包含必要的校验以及对Payment对象的创建和保存操作 + * 退款前预扣通道和支付订单的金额 + */ + public void doPreDeductOrderHandler(){ + PayChannelOrder payChannelOrder = this.getPayChannelOrder(); + int refundableBalance = payChannelOrder.getRefundableBalance() - this.getRefundChannelParam().getAmount(); + payChannelOrder.setRefundableBalance(refundableBalance) + .setStatus(PayStatusEnum.REFUNDING.getCode()); + } + + /** + * 退款前对处理 */ public void doBeforeRefundHandler() {} @@ -64,7 +79,7 @@ public abstract class AbsRefundStrategy implements PayStrategy{ this.refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode()) .setRefundTime(LocalDateTime.now()); - // 支付通道订单客可退余额 + // 支付通道订单可退余额 int refundableBalance = this.getPayChannelOrder().getRefundableBalance() - this.refundChannelOrder.getAmount(); // 支付通道订单状态 PayStatusEnum status = refundableBalance == 0 ? PayStatusEnum.REFUNDED : PayStatusEnum.PARTIAL_REFUND; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundSyncStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundSyncStrategy.java index 79ab15da..a4b39df5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundSyncStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/func/AbsRefundSyncStrategy.java @@ -22,6 +22,10 @@ public abstract class AbsRefundSyncStrategy implements PayStrategy{ this.refundOrder = refundOrder; } + /** + * 同步前处理, 主要是预防请求过于迅速, 支付网关没有处理完退款请求, 导致返回的状态不正确 + */ + public void doBeforeHandler(){} /** * 异步支付单与支付网关进行状态比对后的结果 * @see PaySyncStatusEnum