mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-04 03:30:36 +00:00
feat 退款回调联调
This commit is contained in:
@@ -45,8 +45,8 @@ public class PayRefundOrderController {
|
||||
|
||||
@Operation(summary = "查询单条")
|
||||
@GetMapping("/findById")
|
||||
public ResResult<PayRefundOrderDto> findById(Long paymentId){
|
||||
return Res.ok(payRefundQueryService.findById(paymentId));
|
||||
public ResResult<PayRefundOrderDto> findById(Long id){
|
||||
return Res.ok(payRefundQueryService.findById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "通道退款订单列表查询")
|
||||
|
@@ -20,6 +20,8 @@ public enum PayRefundStatusEnum {
|
||||
* 接口调用成功不代表成功
|
||||
*/
|
||||
PROGRESS("progress","退款中"),
|
||||
/** 部分成功 */
|
||||
PART_SUCCESS("part_success","部分成功"),
|
||||
SUCCESS("success","成功"),
|
||||
FAIL("fail","失败");
|
||||
|
||||
|
@@ -20,9 +20,6 @@ public class RefundChannelOrderResult {
|
||||
@Schema(description = "通道支付单id")
|
||||
private Long payChannelId;
|
||||
|
||||
@Schema(description = "支付网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@Schema(description = "异步支付方式")
|
||||
private boolean async;
|
||||
|
||||
|
@@ -36,15 +36,21 @@ public class RefundOrderResult {
|
||||
@Schema(description = "剩余可退")
|
||||
private BigDecimal refundableBalance;
|
||||
|
||||
/**
|
||||
* 异步支付通道发给网关的退款号, 用与将记录关联起来
|
||||
*/
|
||||
@Schema(description = "支付网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@Schema(description = "通道退款订单")
|
||||
private List<RefundChannelOrderResult> channels;
|
||||
|
||||
@Schema(description = "退款终端ip")
|
||||
private String clientIp;
|
||||
|
||||
@Schema(description = "退款时间")
|
||||
private LocalDateTime refundTime;
|
||||
|
||||
@Schema(description = "通道退款订单")
|
||||
private List<RefundChannelOrderResult> channels;
|
||||
|
||||
/**
|
||||
* @see PayRefundStatusEnum
|
||||
*/
|
||||
|
@@ -92,28 +92,37 @@ public interface WeChatPayCode {
|
||||
|
||||
// 交易状态
|
||||
/** 支付成功 */
|
||||
String TRADE_SUCCESS = "SUCCESS";
|
||||
String PAY_SUCCESS = "SUCCESS";
|
||||
|
||||
/** 支付失败 */
|
||||
String TRADE_FAIL = "FAIL";
|
||||
String PAY_FAIL = "FAIL";
|
||||
|
||||
/** 退款 */
|
||||
String TRADE_REFUND = "REFUND";
|
||||
String PAY_REFUND = "REFUND";
|
||||
|
||||
/** 未支付 */
|
||||
String TRADE_NOTPAY = "NOTPAY";
|
||||
String PAY_NOTPAY = "NOTPAY";
|
||||
|
||||
/** 已关闭 */
|
||||
String TRADE_CLOSED = "CLOSED";
|
||||
String PAY_CLOSED = "CLOSED";
|
||||
|
||||
/** 已接收,等待扣款 */
|
||||
String TRADE_ACCEPT = "ACCEPT";
|
||||
String PAY_ACCEPT = "ACCEPT";
|
||||
|
||||
/** 已撤销(刷卡支付) */
|
||||
String TRADE_REVOKED = "REVOKED";
|
||||
String PAY_REVOKED = "REVOKED";
|
||||
|
||||
/** 用户支付中(刷卡支付) */
|
||||
String TRADE_USERPAYING = "USERPAYING";
|
||||
String PAY_USERPAYING = "USERPAYING";
|
||||
|
||||
/** 退款成功 */
|
||||
String REFUND_USERPAYING = "SUCCESS";
|
||||
|
||||
/** 退款异常 */
|
||||
String REFUND_CHANGE = "CHANGE";
|
||||
|
||||
/** 退款关闭 */
|
||||
String REFUND_REFUNDCLOSE = "REFUNDCLOSE";
|
||||
|
||||
/** 支付失败(刷卡支付) */
|
||||
String TRADE_PAYERROR = "PAYERROR";
|
||||
|
@@ -44,7 +44,7 @@ public class CallbackLocal {
|
||||
private LocalDateTime finishTime;
|
||||
|
||||
/** 支付修复ID */
|
||||
private Long payRepairOrderId;
|
||||
private Long payRepairId;
|
||||
|
||||
/**
|
||||
* 回调处理状态
|
||||
|
@@ -57,14 +57,15 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
|
||||
public boolean verifyNotify() {
|
||||
Map<String, String> params =PaymentContextLocal.get().getCallbackInfo().getCallbackParam();
|
||||
String callReq = JSONUtil.toJsonStr(params);
|
||||
log.info("支付宝发起回调 报文: {}", callReq);
|
||||
String appId = params.get(APP_ID);
|
||||
if (StrUtil.isBlank(appId)) {
|
||||
log.error("支付宝回调报文 appId 为空 {}", callReq);
|
||||
log.error("支付宝回调报文appId为空");
|
||||
return false;
|
||||
}
|
||||
AliPayConfig alipayConfig = aliasConfigService.getConfig();
|
||||
if (Objects.isNull(alipayConfig)) {
|
||||
log.error("支付宝支付配置不存在: {}", callReq);
|
||||
log.error("支付宝支付配置不存在");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
@@ -43,7 +43,7 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.ALI;
|
||||
return PayChannelEnum.WECHAT;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
|
||||
String appId = params.get(APPID);
|
||||
|
||||
if (StrUtil.isBlank(appId)) {
|
||||
log.warn("微信回调报文 appId 为空 {}", callReq);
|
||||
log.warn("微信回调报文 appId 为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
|
||||
// 支付回调信息校验
|
||||
WeChatPayConfig weChatPayConfig = weChatPayConfigService.getConfig();
|
||||
if (Objects.isNull(weChatPayConfig)) {
|
||||
log.warn("微信支付配置不存在: {}", callReq);
|
||||
log.warn("微信支付配置不存在");
|
||||
return false;
|
||||
}
|
||||
return WxPayKit.verifyNotify(params, weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256, null);
|
||||
@@ -121,11 +121,13 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
|
||||
callbackInfo.setGatewayOrderNo(callbackParam.get(REFUND_ID));
|
||||
// 退款订单Id
|
||||
callbackInfo.setOrderId(Long.valueOf(callbackParam.get(OUT_REFUND_NO)));
|
||||
// 交易状态
|
||||
callbackInfo.setGatewayStatus(callbackParam.get(REFUND_STATUS));
|
||||
// 退款金额
|
||||
callbackInfo.setAmount(callbackParam.get(REFUND_FEE));
|
||||
|
||||
// 交易状态
|
||||
PayStatusEnum payStatus = Objects.equals(callbackParam.get(REFUND_STATUS), REFUND_USERPAYING) ? PayStatusEnum.SUCCESS : PayStatusEnum.FAIL;
|
||||
callbackInfo.setGatewayStatus(payStatus.getCode());
|
||||
|
||||
// 退款时间
|
||||
String timeEnd = callbackParam.get(SUCCESS_TIME);
|
||||
if (StrUtil.isNotBlank(timeEnd)) {
|
||||
|
@@ -202,25 +202,25 @@ public class WeChatPayService {
|
||||
String resultCode = result.get(WeChatPayCode.RESULT_CODE);
|
||||
String errCode = result.get(WeChatPayCode.ERR_CODE);
|
||||
// 支付成功处理,
|
||||
if (Objects.equals(resultCode, WeChatPayCode.TRADE_SUCCESS)) {
|
||||
if (Objects.equals(resultCode, WeChatPayCode.PAY_SUCCESS)) {
|
||||
asyncPayInfo.setGatewayOrderNo(result.get(WeChatPayCode.TRANSACTION_ID))
|
||||
.setPayComplete(true);
|
||||
return;
|
||||
}
|
||||
// 支付中, 发起轮训同步
|
||||
if (Objects.equals(resultCode, WeChatPayCode.TRADE_FAIL)
|
||||
&& Objects.equals(errCode, WeChatPayCode.TRADE_USERPAYING)) {
|
||||
if (Objects.equals(resultCode, WeChatPayCode.PAY_FAIL)
|
||||
&& Objects.equals(errCode, WeChatPayCode.PAY_USERPAYING)) {
|
||||
SpringUtil.getBean(this.getClass()).rotationSync(payment);
|
||||
asyncPayInfo.setGatewayOrderNo(result.get(WeChatPayCode.TRANSACTION_ID));
|
||||
return;
|
||||
}
|
||||
// 支付撤销
|
||||
if (Objects.equals(resultCode, WeChatPayCode.TRADE_REVOKED)) {
|
||||
if (Objects.equals(resultCode, WeChatPayCode.PAY_REVOKED)) {
|
||||
throw new PayFailureException("用户已撤销支付");
|
||||
}
|
||||
// 支付失败
|
||||
if (Objects.equals(resultCode, WeChatPayCode.TRADE_PAYERROR)
|
||||
|| Objects.equals(resultCode, WeChatPayCode.TRADE_FAIL)) {
|
||||
|| Objects.equals(resultCode, WeChatPayCode.PAY_FAIL)) {
|
||||
String errorMsg = result.get(WeChatPayCode.ERR_CODE_DES);
|
||||
throw new PayFailureException(errorMsg);
|
||||
}
|
||||
|
@@ -68,25 +68,25 @@ public class WeChatPaySyncService {
|
||||
// 查询到订单的状态
|
||||
String tradeStatus = result.get(WeChatPayCode.TRADE_STATE);
|
||||
// 支付完成
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_SUCCESS) || Objects.equals(tradeStatus, WeChatPayCode.TRADE_ACCEPT)) {
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_SUCCESS) || Objects.equals(tradeStatus, WeChatPayCode.PAY_ACCEPT)) {
|
||||
String timeEnd = result.get(WeChatPayCode.TIME_END);
|
||||
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.PURE_DATETIME_PATTERN);
|
||||
PaymentContextLocal.get().getPaySyncInfo().setPayTime(time);
|
||||
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS);
|
||||
}
|
||||
// 待支付
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_NOTPAY)
|
||||
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_USERPAYING)) {
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_NOTPAY)
|
||||
|| Objects.equals(tradeStatus, WeChatPayCode.PAY_USERPAYING)) {
|
||||
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT);
|
||||
}
|
||||
|
||||
// 已退款/退款中 触发一下退款记录的查询
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_REFUND)) {
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_REFUND)) {
|
||||
this.syncRefundStatus(order, weChatPayConfig);
|
||||
}
|
||||
// 已关闭
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_CLOSED)
|
||||
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_REVOKED)
|
||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_CLOSED)
|
||||
|| Objects.equals(tradeStatus, WeChatPayCode.PAY_REVOKED)
|
||||
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_PAYERROR)) {
|
||||
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED);
|
||||
}
|
||||
|
@@ -54,9 +54,9 @@ public class PayOrder extends MpBaseEntity implements EntityBaseFunction<PayOrde
|
||||
private String asyncChannel;
|
||||
|
||||
/**
|
||||
* 如果有异步支付的情况下, 保存关联网关支付号
|
||||
* 如果有异步支付的情况下, 保存关联网关订单号
|
||||
*/
|
||||
@DbColumn(comment = "关联网关支付号")
|
||||
@DbColumn(comment = "网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
/** 金额 */
|
||||
|
@@ -30,12 +30,6 @@ public class PayRefundChannelOrder extends MpBaseEntity implements EntityBaseFun
|
||||
@DbColumn(comment = "通道支付单id")
|
||||
private Long payChannelId;
|
||||
|
||||
/**
|
||||
* 异步支付通道发给网关的退款号, 用与将记录关联起来
|
||||
*/
|
||||
@DbColumn(comment = "关联网关退款号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@DbColumn(comment = "通道")
|
||||
private String channel;
|
||||
|
||||
|
@@ -41,6 +41,10 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<P
|
||||
@DbColumn(comment = "退款号")
|
||||
private String refundNo;
|
||||
|
||||
/** 如果有异步通道, 保存关联的网关订单号 */
|
||||
@DbColumn(comment = "网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
/** 标题 */
|
||||
@DbColumn(comment = "标题")
|
||||
private String title;
|
||||
|
@@ -8,6 +8,7 @@ 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.order.pay.entity.PayOrder;
|
||||
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
|
||||
import cn.bootx.platform.daxpay.service.core.payment.repair.result.PayRepairResult;
|
||||
import cn.bootx.platform.daxpay.service.core.payment.repair.service.PayRepairService;
|
||||
import com.baomidou.lock.LockInfo;
|
||||
import com.baomidou.lock.LockTemplate;
|
||||
@@ -50,21 +51,16 @@ public class PayCallbackService {
|
||||
try {
|
||||
// 获取支付单
|
||||
PayOrder payOrder = payOrderQueryService.findById(callbackInfo.getOrderId()).orElse(null);
|
||||
// 支付单不存在,记录回调记录, TODO 取消支付网关的订单支付情况
|
||||
// 本地支付单不存在,记录回调记录, TODO 需要补单或进行退款
|
||||
if (Objects.isNull(payOrder)) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.NOT_FOUND)
|
||||
.setMsg("支付单不存在,记录回调记录");
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.NOT_FOUND).setMsg("支付单不存在,记录回调记录");
|
||||
return;
|
||||
}
|
||||
// 设置订单关联网关订单号
|
||||
payOrder.setGatewayOrderNo(callbackInfo.getGatewayOrderNo());
|
||||
|
||||
// 成功状态
|
||||
if (Objects.equals(PayCallbackStatusEnum.SUCCESS.getCode(), callbackInfo.getGatewayStatus())) {
|
||||
// 回调时间超出了支付单超时时间, 记录一下, 不做处理 TODO 这块应该把订单给当成正常结束给处理了,
|
||||
if (Objects.nonNull(payOrder.getExpiredTime())
|
||||
&& LocalDateTimeUtil.ge(LocalDateTime.now(), payOrder.getExpiredTime())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.EXCEPTION).setMsg("回调时间超出了支付单支付有效时间");
|
||||
return;
|
||||
}
|
||||
// 支付成功处理
|
||||
this.success(payOrder);
|
||||
} else {
|
||||
@@ -81,20 +77,25 @@ public class PayCallbackService {
|
||||
*/
|
||||
private void success(PayOrder payOrder) {
|
||||
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
||||
|
||||
// 回调时间超出了支付单超时时间, 记录一下, 不做处理 TODO 这块应该把订单给当成正常结束给处理了,
|
||||
if (Objects.nonNull(payOrder.getExpiredTime())
|
||||
&& LocalDateTimeUtil.ge(LocalDateTime.now(), payOrder.getExpiredTime())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.EXCEPTION).setMsg("回调时间超出了支付单支付有效时间");
|
||||
return;
|
||||
}
|
||||
// 支付单已经被支付,不需要重复处理
|
||||
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.IGNORE).setMsg("支付单已经是支付成功状态,不进行处理");
|
||||
return;
|
||||
}
|
||||
|
||||
// 支付单已被取消,记录回调记录 TODO 考虑不全, 需要做退款or人工处理
|
||||
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.EXCEPTION).setMsg("支付单不是待支付状态,记录回调记录");
|
||||
return;
|
||||
}
|
||||
// 执行支付完成修复逻辑
|
||||
payRepairService.repair(payOrder, PayRepairTypeEnum.SUCCESS);
|
||||
PayRepairResult repair = payRepairService.repair(payOrder, PayRepairTypeEnum.SUCCESS);
|
||||
callbackInfo.setPayRepairId(repair.getRepairId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,21 +103,19 @@ public class PayCallbackService {
|
||||
*/
|
||||
private void fail(PayOrder payOrder) {
|
||||
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
||||
|
||||
// payment已被取消,记录回调记录,后期处理 TODO 考虑不完善, 后续优化
|
||||
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.IGNORE).setMsg("支付单已经取消,记录回调记录");
|
||||
return;
|
||||
}
|
||||
|
||||
// payment支付成功, 状态非法 TODO 考虑不完善, 后续优化
|
||||
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.EXCEPTION).setMsg("支付单状态非法,支付网关状态为失败,但支付单状态为已完成");
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行支付关闭修复逻辑
|
||||
payRepairService.repair(payOrder, PayRepairTypeEnum.CLOSE_LOCAL);
|
||||
PayRepairResult repair = payRepairService.repair(payOrder, PayRepairTypeEnum.CLOSE_LOCAL);
|
||||
callbackInfo.setPayRepairId(repair.getRepairId());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ 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.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 com.baomidou.lock.LockInfo;
|
||||
import com.baomidou.lock.LockTemplate;
|
||||
@@ -46,18 +47,24 @@ public class PayRefundCallbackService {
|
||||
}
|
||||
try {
|
||||
// 获取退款单
|
||||
PayRefundOrder refundOrder = refundOrderManager.findById(callbackInfo.getPayRepairOrderId()).orElse(null);
|
||||
PayRefundOrder refundOrder = refundOrderManager.findById(callbackInfo.getOrderId()).orElse(null);
|
||||
// 退款单不存在,记录回调记录
|
||||
if (Objects.isNull(refundOrder)) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.NOT_FOUND).setMsg("退款单不存在,记录回调记录");
|
||||
return;
|
||||
}
|
||||
// 退款单已经被处理, 记录回调记录
|
||||
if (!Objects.equals(PayRefundStatusEnum.PROGRESS.getCode(), refundOrder.getStatus())) {
|
||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.IGNORE).setMsg("退款单状态已处理,记录回调记录");
|
||||
}
|
||||
|
||||
// 退款成功还是失败
|
||||
if (Objects.equals(PayRefundStatusEnum.SUCCESS.getCode(), callbackInfo.getGatewayStatus())) {
|
||||
reflectionService.repair(refundOrder, RefundRepairTypeEnum.SUCCESS);
|
||||
RefundRepairResult repair = reflectionService.repair(refundOrder, RefundRepairTypeEnum.SUCCESS);
|
||||
callbackInfo.setPayRepairId(repair.getRepairId());
|
||||
} else {
|
||||
reflectionService.repair(refundOrder, RefundRepairTypeEnum.FAIL);
|
||||
RefundRepairResult repair = reflectionService.repair(refundOrder, RefundRepairTypeEnum.FAIL);
|
||||
callbackInfo.setPayRepairId(repair.getRepairId());
|
||||
}
|
||||
|
||||
} finally {
|
||||
@@ -65,22 +72,4 @@ public class PayRefundCallbackService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功处理
|
||||
*/
|
||||
private void success(PayRefundOrder refundOrder) {
|
||||
// 支付退款订单修复
|
||||
|
||||
// 支付退款订单成功修复
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败处理
|
||||
*/
|
||||
private void fail(PayRefundOrder refundOrder) {
|
||||
// 退款订单失败修复
|
||||
|
||||
// 支付订单退款失败修复
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -120,7 +120,7 @@ public class PayRefundAssistService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存退款记录 成不成功都记录
|
||||
* 保存退款订单 成不成功都记录
|
||||
*/
|
||||
public PayRefundOrder generateRefundOrder(RefundParam refundParam, PayOrder payOrder){
|
||||
RefundLocal asyncRefundInfo = PaymentContextLocal.get().getRefundInfo();
|
||||
@@ -139,6 +139,7 @@ public class PayRefundAssistService {
|
||||
.setRefundableBalance(payOrder.getRefundableBalance())
|
||||
.setRefundTime(LocalDateTime.now())
|
||||
.setTitle(payOrder.getTitle())
|
||||
.setGatewayOrderNo(asyncRefundInfo.getGatewayOrderNo())
|
||||
.setErrorCode(asyncRefundInfo.getErrorCode())
|
||||
.setErrorMsg(asyncRefundInfo.getErrorMsg())
|
||||
.setStatus(asyncRefundInfo.getStatus().getCode())
|
||||
|
@@ -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.pay.*;
|
||||
import cn.bootx.platform.daxpay.service.core.payment.repair.strategy.*;
|
||||
import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
@@ -1,114 +0,0 @@
|
||||
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.ASYNC_TYPE_CODE;
|
||||
|
||||
/**
|
||||
* 支付退款修复策略工厂
|
||||
* @author xxm
|
||||
* @since 2024/1/26
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
@@ -59,7 +59,7 @@ public class RefundRepairService {
|
||||
PayChannelOrder payChannelOrder = payChannelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payOrder.getAsyncChannel())
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
// 异步通道退款单
|
||||
PayRefundChannelOrder refundChannelOrder = refundChannelOrderManager.findByPaymentIdAndChannel(refundOrder.getPaymentId(), payOrder.getAsyncChannel())
|
||||
PayRefundChannelOrder refundChannelOrder = refundChannelOrderManager.findByPaymentIdAndChannel(refundOrder.getId(), payOrder.getAsyncChannel())
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
|
||||
// 根据不同的类型执行对应的修复逻辑
|
||||
@@ -106,7 +106,7 @@ public class RefundRepairService {
|
||||
// 设置退款为完成状态
|
||||
refundOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
||||
refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
||||
|
||||
payOrder.setStatus(afterPayRefundStatus.getCode());
|
||||
// 更新订单和退款相关订单
|
||||
payChannelOrderManager.updateById(payChannelOrder);
|
||||
payOrderManager.updateById(payOrder);
|
||||
@@ -124,42 +124,52 @@ public class RefundRepairService {
|
||||
* 退款失败, 将失败的退款金额归还回订单
|
||||
*/
|
||||
private RefundRepairResult closeLocal(PayRefundOrder refundOrder, PayOrder payOrder, PayRefundChannelOrder refundChannelOrder, PayChannelOrder payChannelOrder) {
|
||||
// 要返回的状态
|
||||
RefundRepairResult repairResult = new RefundRepairResult();
|
||||
|
||||
// 更新通道支付单全部退款还是部分退款
|
||||
if (Objects.equals(payChannelOrder.getRefundableBalance(),0)){
|
||||
payChannelOrder.setStatus(PayStatusEnum.REFUNDED.getCode());
|
||||
} else {
|
||||
payChannelOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
|
||||
}
|
||||
|
||||
// 订单相关状态
|
||||
// 订单修复前状态
|
||||
PayStatusEnum beforePayStatus = PayStatusEnum.findByCode(refundOrder.getStatus());
|
||||
PayStatusEnum afterPayRefundStatus;
|
||||
PayRefundStatusEnum beforeRefundStatus = PayRefundStatusEnum.findByCode(refundOrder.getStatus());
|
||||
repairResult.setBeforePayStatus(beforePayStatus)
|
||||
.setBeforeRefundStatus(beforeRefundStatus);
|
||||
|
||||
// 判断订单是支付成功还是部分退款
|
||||
|
||||
|
||||
if (Objects.equals(payOrder.getRefundableBalance(),0)){
|
||||
afterPayRefundStatus = PayStatusEnum.REFUNDED;
|
||||
// 退款失败返还后的余额
|
||||
int payOrderAmount = refundChannelOrder.getAmount() + payOrder.getRefundableBalance();
|
||||
int payChannelOrderAmount = refundChannelOrder.getAmount() + payChannelOrder.getRefundableBalance();
|
||||
// 退款失败返还后的余额+可退余额 == 订单金额 支付订单回退为为支付成功状态
|
||||
if (payOrderAmount == payOrder.getAmount()){
|
||||
payOrder.setStatus(PayStatusEnum.SUCCESS.getCode());
|
||||
// 说明这个退款只有异步这一个支付, 所以也可以直接回退
|
||||
payChannelOrder.setStatus(PayStatusEnum.SUCCESS.getCode());
|
||||
repairResult.setAfterPayStatus(PayStatusEnum.SUCCESS);
|
||||
} else {
|
||||
afterPayRefundStatus = PayStatusEnum.PARTIAL_REFUND;
|
||||
// 回归部分退款状态
|
||||
payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
|
||||
repairResult.setAfterPayStatus(PayStatusEnum.PARTIAL_REFUND);
|
||||
}
|
||||
// 更新支付订单相关的可退款金额
|
||||
payOrder.setRefundableBalance(payOrderAmount);
|
||||
payChannelOrder.setRefundableBalance(payChannelOrderAmount);
|
||||
|
||||
// 设置退款为失败状态
|
||||
refundOrder.setStatus(PayRefundStatusEnum.FAIL.getCode());
|
||||
refundChannelOrder.setStatus(PayRefundStatusEnum.FAIL.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());
|
||||
repairResult.setAfterRefundStatus(PayRefundStatusEnum.PART_SUCCESS);
|
||||
}
|
||||
|
||||
// 更新订单和退款相关订单
|
||||
payChannelOrderManager.updateById(payChannelOrder);
|
||||
payOrderManager.updateById(payOrder);
|
||||
refundOrderManager.updateById(refundOrder);
|
||||
refundChannelOrderManager.updateById(refundChannelOrder);
|
||||
return new RefundRepairResult()
|
||||
.setBeforePayStatus(beforePayStatus)
|
||||
.setAfterPayStatus(afterPayRefundStatus)
|
||||
.setBeforeRefundStatus(beforeRefundStatus)
|
||||
.setAfterRefundStatus(PayRefundStatusEnum.FAIL);
|
||||
return repairResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.service.func.AbsPayRepairStrategy;
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
@@ -1,4 +1,4 @@
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy.pay;
|
||||
package cn.bootx.platform.daxpay.service.core.payment.repair.strategy;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
@@ -1,29 +0,0 @@
|
||||
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/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AliRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.ALI;
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
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/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CashRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.CASH;
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
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/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UnionRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.UNION_PAY;
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
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/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VoucherRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.VOUCHER;
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WalletRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.WALLET;
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
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/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Scope(SCOPE_PROTOTYPE)
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WeChatRefundRepairStrategy extends AbsRefundRepairStrategy {
|
||||
/**
|
||||
* 策略标识
|
||||
*/
|
||||
@Override
|
||||
public PayChannelEnum getChannel() {
|
||||
return PayChannelEnum.WECHAT;
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@ package cn.bootx.platform.daxpay.service.dto.order.refund;
|
||||
|
||||
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
||||
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||
import cn.bootx.platform.daxpay.entity.RefundableInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -10,7 +9,6 @@ import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 退款记录
|
||||
@@ -33,6 +31,9 @@ public class PayRefundOrderDto extends BaseDto {
|
||||
@Schema(description = "退款号")
|
||||
private String refundNo;
|
||||
|
||||
@Schema(description = "支付网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
|
@@ -28,9 +28,6 @@ public class RefundChannelOrderDto extends BaseDto {
|
||||
@Schema(description = "通道支付单id")
|
||||
private Long payChannelId;
|
||||
|
||||
@Schema(description = "支付网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@Schema(description = "异步支付方式")
|
||||
private boolean async;
|
||||
|
||||
|
@@ -30,6 +30,9 @@ public class PayRefundOrderQuery extends QueryOrder {
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
|
||||
private String businessNo;
|
||||
|
||||
@Schema(description = "支付网关订单号")
|
||||
private String gatewayOrderNo;
|
||||
|
||||
@Schema(description = "标题")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
|
||||
private String title;
|
||||
|
Reference in New Issue
Block a user