feat 退款同步+退款修复策略

This commit is contained in:
bootx
2024-01-30 22:06:25 +08:00
parent d2680b3dfd
commit 56d7236011
28 changed files with 511 additions and 148 deletions

View File

@@ -90,21 +90,27 @@
- [x] 支付宝对账单下载异常排查-支付宝每日都会生成对账单, 哪怕为空, 也会生成
- [x] 订单修复记录前端显示调整
- 2044-01-30:
- [x] 退款接口更改为先落库, 后更新
- [ ] 增加退款同步策略, 对退款中的状态的退款订单进行处理
- [ ] 退款操作支持重试
- [ ] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿
- [x] 退款接口更改为先落库, 后更新, 同时退款余额先先进行扣减, 根据退款状态进行处理
- [x] 增加退款同步策略, 对退款中的状态的退款订单进行处理
- [x] 修改退款补偿处理, 更改为退款粒度为整个退款单, 要不全部成功, 要不全部失败
- [x] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿
- 2024-01-31:
- [ ] 微信退款同步策略
- [ ] 退款操作支持重试
2.0.1 版本内容
- [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
- [ ] 完善各种同步支付方式
- [ ] 增加聚合支付功能支持
- [ ] 增加手机用户收银台功能
**任务池**
- [ ] 微信退款状态不一致补偿
- [ ] ~~微信退款状态不一致补偿~~
- [ ] 支付SDK编写
- [ ] 接入支付网关的演示项目
- [ ] 支付宝关闭支付时支持撤销方式,
- [ ] 支持转账操作, 通过支付通道专有参数进行实现, 转账时只能单个通道进行操作
- [ ] 支付成功回调后, 如果订单已超时, 则进入待退款订单中,提示进行退款,或者自动退款
- [ ] 退款状态同步逻辑
- [ ] 支付状态同步处理考虑退款情况
- [ ] ~~退款状态同步逻辑~~
- [ ] ~~支付状态同步处理考虑退款情况~~
- [ ] 增加回调机制(通知客户端)
- [ ] 增加消息通知机制(通知客户端)
- [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况

View File

@@ -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<PaySyncResult> sync(Long id){
@PostMapping("/syncById")
public ResResult<SyncResult> syncById(Long id){
PaySyncParam param = new PaySyncParam();
param.setPaymentId(id);
return Res.ok(paySyncService.sync(param));

View File

@@ -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<PaySyncResult> syncById(Long id){
return Res.ok(payRefundOrderService.syncById(id));
public ResResult<SyncResult> syncById(Long id){
RefundSyncParam refundSyncParam = new RefundSyncParam();
refundSyncParam.setRefundId(id);
return Res.ok(refundSyncService.sync(refundSyncParam));
}
}

View File

@@ -15,13 +15,13 @@ import lombok.EqualsAndHashCode;
public class RefundSyncParam extends PayCommonParam {
/**
* 部分退款时 refundIdrefundNo 必传一个, 同时传输时,以 refundId 为准
* 退款订单IDrefundIdrefundNo 必传一个, 同时传输时,以 refundId 为准
*/
@Schema(description = "退款订单ID")
private Long refundId;
/**
* 退款订单号,部分退款时 refundIdrefundNo 必传一个,同时传输时,以 refundId 为准
* 退款订单号refundIdrefundNo 必传一个,同时传输时,以 refundId 为准
*/
@Schema(description = "退款订单号")
private String refundNo;

View File

@@ -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();

View File

@@ -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<PaySyncResult> syncPay(@RequestBody PaySyncParam param){
public DaxResult<SyncResult> syncPay(@RequestBody PaySyncParam param){
return DaxRes.ok(paySyncService.sync(param));
}

View File

@@ -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();
}
}

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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<AbsRefundRepairStrategy> createAsyncLast(List<String> channelCodes) {
return create(channelCodes, true);
}
/**
* 根据传入的支付类型批量创建策略, 异步支付在前面
*/
public static List<AbsRefundRepairStrategy> createAsyncFront(List<String> channelCodes) {
return create(channelCodes, false);
}
/**
* 根据传入的支付类型批量创建策略
* @param asyncSort 是否异步支付在后面
* @return 支付策略
*/
private static List<AbsRefundRepairStrategy> create(List<String> 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<PayChannelEnum> 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());
}
}

View File

@@ -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<String, PayChannelOrder> 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<String, PayRefundChannelOrder> refundChannelOrderMap = refundChannelOrderManager.findAllByRefundId(refundOrder.getId())
.stream()
.collect(Collectors.toMap(PayRefundChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest));
// 2 初始化修复参数
List<String> channels = new ArrayList<>(payChannelOrderMap.keySet());
List<AbsRefundRepairStrategy> 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<AbsRefundRepairStrategy> 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<PayChannelOrder> payChannelOrders = repairStrategies.stream()
.map(AbsRefundRepairStrategy::getPayChannelOrder)
.collect(Collectors.toList());
List<PayRefundChannelOrder> 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<AbsRefundRepairStrategy> 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<PayChannelOrder> payChannelOrders = repairStrategies.stream()
.map(AbsRefundRepairStrategy::getPayChannelOrder)
.collect(Collectors.toList());
List<PayRefundChannelOrder> 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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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())

View File

@@ -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())

View File

@@ -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;
/**

View File

@@ -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());
}
}