feat 退款重试功能

This commit is contained in:
xxm1995
2024-03-06 18:39:02 +08:00
parent d73bd4e8ea
commit f44a2d7e2f
10 changed files with 196 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* 支付订单服务
@@ -27,6 +28,13 @@ public class PayOrderService {
// 支付完成常量集合
private final List<String> ORDER_FINISH = Arrays.asList(PayStatusEnum.CLOSE.getCode(), PayStatusEnum.SUCCESS.getCode());
/**
* 查询
*/
public Optional<PayOrder> findById(Long id){
return payOrderManager.findById(id);
}
/**
* 新增
*/

View File

@@ -11,8 +11,8 @@ import cn.bootx.platform.daxpay.param.pay.QueryRefundParam;
import cn.bootx.platform.daxpay.param.pay.RefundParam;
import cn.bootx.platform.daxpay.result.order.RefundChannelOrderResult;
import cn.bootx.platform.daxpay.result.order.RefundOrderResult;
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderConvert;
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderChannelConvert;
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderConvert;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundChannelOrder;
@@ -22,8 +22,8 @@ import cn.bootx.platform.daxpay.service.core.payment.refund.service.RefundServic
import cn.bootx.platform.daxpay.service.core.system.config.dao.PayApiConfigManager;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PayApiConfig;
import cn.bootx.platform.daxpay.service.core.system.config.service.PayApiConfigService;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderDto;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundChannelOrderDto;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderDto;
import cn.bootx.platform.daxpay.service.param.order.PayOrderRefundParam;
import cn.bootx.platform.daxpay.service.param.order.RefundOrderQuery;
import cn.hutool.core.util.StrUtil;

View File

@@ -3,6 +3,7 @@ package cn.bootx.platform.daxpay.service.core.payment.refund.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundChannelOrder;
import cn.bootx.platform.daxpay.service.core.payment.refund.strategy.*;
import cn.bootx.platform.daxpay.service.func.AbsRefundStrategy;
import cn.hutool.core.collection.CollectionUtil;
@@ -24,6 +25,20 @@ import static cn.bootx.platform.daxpay.code.PayChannelEnum.ASYNC_TYPE_CODE;
*/
public class RefundStrategyFactory {
/**
* 通过订单生成退款策略
*/
public static List<AbsRefundStrategy> createAsyncFrontByOrder(List<RefundChannelOrder> refundChannelOrders) {
// 生成通道订单参数
List<RefundChannelParam> channelParams = refundChannelOrders.stream()
.map(o -> new RefundChannelParam()
.setAmount(o.getAmount())
.setChannel(o.getChannel()))
.collect(Collectors.toList());
return createAsyncFront(channelParams);
}
/**
* 根据传入的支付通道创建策略
* @return 支付策略

View File

@@ -1,5 +1,6 @@
package cn.bootx.platform.daxpay.service.core.payment.refund.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.common.core.function.CollectorsFunction;
import cn.bootx.platform.common.core.util.ValidationUtil;
@@ -17,6 +18,8 @@ import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManage
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.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrder;
import cn.bootx.platform.daxpay.service.core.payment.notice.service.ClientNoticeService;
@@ -55,6 +58,10 @@ public class RefundService {
private final PayChannelOrderManager payChannelOrderManager;
private final RefundOrderManager refundOrderManager;
private final RefundChannelOrderManager refundChannelOrderManager;
private final LockTemplate lockTemplate;
/**
@@ -151,7 +158,7 @@ public class RefundService {
if (Objects.isNull(payChannelOrder)){
throw new PayFailureException("[数据异常]进行退款的通道没有对应的支付单, 无法退款");
}
refundStrategy.initRefundParam(payOrder, refundParam, payChannelOrder);
refundStrategy.initRefundParam(payOrder, payChannelOrder);
}
// 2.1 退款前校验操作
@@ -184,7 +191,7 @@ public class RefundService {
.setRefundNo(refundParam.getRefundNo());
}
catch (Exception e) {
// 5. 失败处理
// 5. 失败处理, 所有记录都会回滚, 可以调用重新
PaymentContextLocal.get().getRefundInfo().setStatus(RefundStatusEnum.FAIL);
this.errorHandler(refundOrder);
throw e;
@@ -219,6 +226,60 @@ public class RefundService {
return refundAssistService.createOrderAndChannel(refundParam, payOrder,refundChannelOrders);
}
/**
* 重新发退款处理
* 1. 查出相关退款订单
* 2. 构建退款策略, 发起退款
*/
public void resetRefund(Long id){
// 查询
RefundOrder refundOrder = refundOrderManager.findById(id)
.orElseThrow(() -> new DataNotExistException("未查找到退款订单"));
// 退款失败才可以重新发起退款, 重新发起退款
if (!Objects.equals(refundOrder.getStatus(), RefundStatusEnum.FAIL.getCode())){
throw new PayFailureException("退款状态不正确, 无法重新发起退款");
}
// 构建策略
List<RefundChannelOrder> refundChannels = refundChannelOrderManager
.findAllByRefundId(refundOrder.getId());
PayOrder payOrder = payOrderService.findById(refundOrder.getPaymentId())
.orElseThrow(() -> new DataNotExistException("未查找到支付订单"));
List<PayChannelOrder> payChannelOrders = payChannelOrderManager.findAllByPaymentId(payOrder.getId());
Map<String, PayChannelOrder> orderChannelMap = payChannelOrders.stream()
.collect(Collectors.toMap(PayChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest));
List<AbsRefundStrategy> strategies = RefundStrategyFactory.createAsyncFrontByOrder(refundChannels);
for (AbsRefundStrategy strategy : strategies) {
strategy.initRefundParam(payOrder, orderChannelMap.get(strategy.getChannel().getCode()));
}
// 设置退款订单对象
strategies.forEach(r->r.setRefundOrder(refundOrder));
try {
// 3.1 退款前准备操作
strategies.forEach(AbsRefundStrategy::doBeforeRefundHandler);
// 3.2 执行退款策略
strategies.forEach(AbsRefundStrategy::doRefundHandler);
// 3.3 执行退款发起成功后操作
strategies.forEach(AbsRefundStrategy::doSuccessHandler);
// 4.进行成功处理, 分别处理退款订单, 通道退款订单, 支付订单
List<RefundChannelOrder> refundChannelOrders = strategies.stream()
.map(AbsRefundStrategy::getRefundChannelOrder)
.collect(Collectors.toList());
this.successHandler(refundOrder, refundChannelOrders, payOrder);
}
catch (Exception e) {
// 5. 失败处理, 所有记录都会回滚, 可以调用重新
PaymentContextLocal.get().getRefundInfo().setStatus(RefundStatusEnum.FAIL);
this.errorHandler(refundOrder);
throw e;
}
}
/**
* 成功处理, 更新退款订单, 退款通道订单, 支付订单, 支付通道订单
*/
@@ -254,6 +315,6 @@ public class RefundService {
*/
private void errorHandler(RefundOrder refundOrder) {
// 记录退款失败的记录
refundAssistService.updateOrderByError(refundOrder);
refundAssistService.updateOrderByError(refundOrder);
}
}

View File

@@ -9,6 +9,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -36,6 +38,10 @@ public class CashRefundStrategy extends AbsRefundStrategy {
*/
@Override
public void doRefundHandler() {
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 不包含异步支付
if (!this.getPayOrder().isAsyncPay()){
cashRecordService.refund(this.getRefundChannelOrder(),this.getPayOrder().getTitle());
@@ -47,6 +53,10 @@ public class CashRefundStrategy extends AbsRefundStrategy {
*/
@Override
public void doSuccessHandler() {
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 包含异步支付, 变更状态到退款中
if (this.getPayOrder().isAsyncPay()) {
this.getPayChannelOrder().setStatus(PayStatusEnum.REFUNDING.getCode());
@@ -56,4 +66,6 @@ public class CashRefundStrategy extends AbsRefundStrategy {
super.doSuccessHandler();
}
}
}

View File

@@ -8,12 +8,16 @@ import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.Voucher;
import cn.bootx.platform.daxpay.service.core.channel.voucher.service.VoucherPayService;
import cn.bootx.platform.daxpay.service.core.channel.voucher.service.VoucherQueryService;
import cn.bootx.platform.daxpay.service.core.channel.voucher.service.VoucherRecordService;
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.func.AbsRefundStrategy;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -33,6 +37,8 @@ public class VoucherRefundStrategy extends AbsRefundStrategy {
private Voucher voucher;
private VoucherPayParam voucherPayParam;
/**
* 策略标识
*
@@ -43,17 +49,30 @@ public class VoucherRefundStrategy extends AbsRefundStrategy {
return PayChannelEnum.VOUCHER;
}
/**
* 退款前对处理
*/
@Override
public void initRefundParam(PayOrder payOrder, PayChannelOrder payChannelOrder) {
// 先设置参数
super.initRefundParam(payOrder, payChannelOrder);
// 从通道扩展参数中取出钱包参数
String channelExtra = this.getPayChannelOrder().getChannelExtra();
this.voucherPayParam = JSONUtil.toBean(channelExtra, VoucherPayParam.class);
}
/**
* 退款前对处理
*/
@Override
public void doBeforeRefundHandler() {
// 从通道扩展参数中取出钱包参数
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 不包含异步支付, 则只在支付订单中进行扣减, 等待异步退款完成, 再进行退款
if (!this.getPayOrder().isAsyncPay()) {
String channelExtra = this.getPayChannelOrder().getChannelExtra();
VoucherPayParam voucherPayParam = JSONUtil.toBean(channelExtra, VoucherPayParam.class);
this.voucher = voucherQueryService.getVoucherByCardNo(voucherPayParam.getCardNo());
this.voucher = voucherQueryService.getVoucherByCardNo(this.voucherPayParam.getCardNo());
}
}
@@ -63,7 +82,11 @@ public class VoucherRefundStrategy extends AbsRefundStrategy {
*/
@Override
public void doRefundHandler() {
// 不包含异步支付
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 不包含异步支付, 直接完成退款
if (!this.getPayOrder().isAsyncPay()){
voucherPayService.refund(this.getRefundChannelParam().getAmount(), this.voucher);
voucherRecordService.refund(this.getRefundChannelOrder(), this.getPayOrder().getTitle(), this.voucher);
@@ -75,6 +98,10 @@ public class VoucherRefundStrategy extends AbsRefundStrategy {
*/
@Override
public void doSuccessHandler() {
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 包含异步支付, 变更状态到退款中
if (this.getPayOrder().isAsyncPay()) {
this.getPayChannelOrder().setStatus(PayStatusEnum.REFUNDING.getCode());
@@ -83,6 +110,16 @@ public class VoucherRefundStrategy extends AbsRefundStrategy {
// 同步支付, 直接标识状态为退款完成
super.doSuccessHandler();
}
}
/**
* 生成通道退款订单对象
*/
@Override
public void generateChannelOrder() {
// 先生成通用的通道退款订单对象
super.generateChannelOrder();
// 设置扩展参数
this.getRefundChannelOrder().setChannelExtra(JSONUtil.toJsonStr(this.voucherPayParam));
}
}

View File

@@ -8,12 +8,16 @@ import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.Wallet;
import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayService;
import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletQueryService;
import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletRecordService;
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.func.AbsRefundStrategy;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -32,6 +36,8 @@ public class WalletRefundStrategy extends AbsRefundStrategy {
private final WalletQueryService walletQueryService;
private WalletPayParam walletPayParam;
private Wallet wallet;
/**
@@ -44,16 +50,31 @@ public class WalletRefundStrategy extends AbsRefundStrategy {
return PayChannelEnum.WALLET;
}
/**
* 退款前对处理
*/
@Override
public void initRefundParam(PayOrder payOrder, PayChannelOrder payChannelOrder) {
// 先设置参数
super.initRefundParam(payOrder, payChannelOrder);
// 从通道扩展参数中取出钱包参数
String channelExtra = this.getPayChannelOrder().getChannelExtra();
this.walletPayParam = JSONUtil.toBean(channelExtra, WalletPayParam.class);
}
/**
* 退款前对处理
*/
@Override
public void doBeforeRefundHandler() {
// 从通道扩展参数中取出钱包参数
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
//
if (!this.getPayOrder().isAsyncPay()) {
String channelExtra = this.getPayChannelOrder().getChannelExtra();
WalletPayParam walletPayParam = JSONUtil.toBean(channelExtra, WalletPayParam.class);
this.wallet = walletQueryService.getWallet(walletPayParam);
this.wallet = walletQueryService.getWallet(this.walletPayParam);
}
}
@@ -62,7 +83,11 @@ public class WalletRefundStrategy extends AbsRefundStrategy {
*/
@Override
public void doRefundHandler() {
// 不包含异步支付
// 如果任务执行完成, 则跳过
if (Objects.equals(this.getRefundChannelOrder().getStatus(), RefundStatusEnum.SUCCESS.getCode())){
return;
}
// 不包含异步支付, 则只在支付订单中进行扣减, 等待异步退款完成, 再进行退款
if (!this.getPayOrder().isAsyncPay()){
walletPayService.refund(this.wallet, this.getRefundChannelParam().getAmount());
walletRecordService.refund(this.getRefundChannelOrder(), this.getPayOrder().getTitle(), this.wallet);
@@ -83,4 +108,15 @@ public class WalletRefundStrategy extends AbsRefundStrategy {
super.doSuccessHandler();
}
}
/**
* 生成通道退款订单对象
*/
@Override
public void generateChannelOrder() {
// 先生成通用的通道退款订单对象
super.generateChannelOrder();
// 设置扩展参数
this.getRefundChannelOrder().setChannelExtra(JSONUtil.toJsonStr(this.walletPayParam));
}
}

View File

@@ -4,7 +4,6 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
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.RefundChannelOrder;
@@ -33,9 +32,6 @@ public abstract class AbsRefundStrategy implements PayStrategy{
/** 当前通道的订单 */
private PayChannelOrder payChannelOrder = null;
/** 退款参数 */
private RefundParam refundParam = null;
/** 当前通道的退款参数 退款参数中的与这个不一致, 以这个为准 */
private RefundChannelParam refundChannelParam = null;
@@ -45,15 +41,14 @@ public abstract class AbsRefundStrategy implements PayStrategy{
/**
* 初始化参数
*/
public void initRefundParam(PayOrder payOrder, RefundParam refundParam, PayChannelOrder payChannelOrder) {
public void initRefundParam(PayOrder payOrder, PayChannelOrder payChannelOrder) {
this.payOrder = payOrder;
this.payChannelOrder = payChannelOrder;
this.refundParam = refundParam;
}
/**
* 退款前预扣通道支付订单的金额
* 退款前预扣通道支付订单的金额
*/
public void doPreDeductOrderHandler(){
PayChannelOrder payChannelOrder = this.getPayChannelOrder();