mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-26 13:47:59 +00:00
feat 支付退款同步
This commit is contained in:
@@ -90,8 +90,8 @@
|
|||||||
- [x] 支付宝对账单下载异常排查-支付宝每日都会生成对账单, 哪怕为空, 也会生成
|
- [x] 支付宝对账单下载异常排查-支付宝每日都会生成对账单, 哪怕为空, 也会生成
|
||||||
- [x] 订单修复记录前端显示调整
|
- [x] 订单修复记录前端显示调整
|
||||||
- 2044-01-29:
|
- 2044-01-29:
|
||||||
- [ ] 支付通道对出现疑似退款的订单进行报错提醒通过退款同步进行补偿
|
|
||||||
- [ ] 增加退款同步策略, 对退款中的状态的退款订单进行处理
|
- [ ] 增加退款同步策略, 对退款中的状态的退款订单进行处理
|
||||||
|
- [ ] 支付通道对出现疑似退款的订单进行**报错提醒**, 通过退款同步进行补偿
|
||||||
- **任务池**
|
- **任务池**
|
||||||
- [ ] 微信退款状态不一致补偿
|
- [ ] 微信退款状态不一致补偿
|
||||||
- [ ] 支付SDK编写
|
- [ ] 支付SDK编写
|
||||||
|
@@ -6,7 +6,8 @@ import cn.bootx.platform.common.core.rest.ResResult;
|
|||||||
import cn.bootx.platform.common.core.rest.param.PageParam;
|
import cn.bootx.platform.common.core.rest.param.PageParam;
|
||||||
import cn.bootx.platform.common.spring.util.WebServletUtil;
|
import cn.bootx.platform.common.spring.util.WebServletUtil;
|
||||||
import cn.bootx.platform.daxpay.param.pay.RefundParam;
|
import cn.bootx.platform.daxpay.param.pay.RefundParam;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.service.PayRefundQueryService;
|
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.refund.service.PayRefundService;
|
||||||
import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto;
|
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.dto.order.refund.RefundChannelOrderDto;
|
||||||
@@ -33,8 +34,9 @@ import java.util.Optional;
|
|||||||
@RequestMapping("/order/refund")
|
@RequestMapping("/order/refund")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PayRefundOrderController {
|
public class PayRefundOrderController {
|
||||||
private final PayRefundQueryService payRefundQueryService;
|
private final PayRefundOrderQueryService payRefundQueryService;
|
||||||
private final PayRefundService payRefundService;
|
private final PayRefundService payRefundService;
|
||||||
|
private final PayRefundOrderService payRefundOrderService;
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "分页查询")
|
@Operation(summary = "分页查询")
|
||||||
@@ -78,4 +80,11 @@ public class PayRefundOrderController {
|
|||||||
payRefundService.refund(refundParam);
|
payRefundService.refund(refundParam);
|
||||||
return Res.ok();
|
return Res.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "退款同步")
|
||||||
|
@PostMapping("/syncById")
|
||||||
|
public ResResult<Void> syncById(Long ID){
|
||||||
|
payRefundOrderService.syncById(ID);
|
||||||
|
return Res.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
package cn.bootx.platform.daxpay.code;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款同步状态枚举
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum PayRefundSyncStatusEnum {
|
||||||
|
SUCCESS("success","成功"),
|
||||||
|
FAIL("fail","失败"),
|
||||||
|
REFUNDING("refunding","退款中");
|
||||||
|
|
||||||
|
/** 编码 */
|
||||||
|
private final String code;
|
||||||
|
/** 名称 */
|
||||||
|
private final String name;
|
||||||
|
}
|
@@ -20,11 +20,7 @@ public enum PaySyncStatusEnum {
|
|||||||
PAY_SUCCESS("pay_success", "支付成功"),
|
PAY_SUCCESS("pay_success", "支付成功"),
|
||||||
PAY_WAIT("pay_wait", "待支付"),
|
PAY_WAIT("pay_wait", "待支付"),
|
||||||
CLOSED("closed", "已关闭"),
|
CLOSED("closed", "已关闭"),
|
||||||
REFUNDING("refunding", "退款中"),
|
REFUND("refund", "退款"),
|
||||||
/** 部分退款 */
|
|
||||||
PARTIAL_REFUND("partial_refund","部分退款"),
|
|
||||||
/** 全部退款 */
|
|
||||||
REFUND("refund", "已退款"),
|
|
||||||
NOT_FOUND("not_found", "交易不存在"),
|
NOT_FOUND("not_found", "交易不存在"),
|
||||||
/**
|
/**
|
||||||
* 未查询到订单(具体类型未知)
|
* 未查询到订单(具体类型未知)
|
||||||
|
@@ -15,31 +15,15 @@ import lombok.EqualsAndHashCode;
|
|||||||
public class RefundSyncParam extends PayCommonParam {
|
public class RefundSyncParam extends PayCommonParam {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部分退款时 refundId 和 refundNo 必传一个
|
* 部分退款时 refundId 和 refundNo 必传一个, 同时传输时,以 refundId 为准
|
||||||
* 如果与 businessNo同时传输,以本参数为准
|
|
||||||
*/
|
*/
|
||||||
@Schema(description = "退款订单ID")
|
@Schema(description = "退款订单ID")
|
||||||
private Long refundId;
|
private Long refundId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款订单号,部分退款时 refundId 和 refundNo 必传一个,
|
* 退款订单号,部分退款时 refundId 和 refundNo 必传一个,同时传输时,以 refundId 为准
|
||||||
* 如果与 paymentId 和 businessNo 同时传输,以本参数为准
|
|
||||||
*/
|
*/
|
||||||
@Schema(description = "退款订单号")
|
@Schema(description = "退款订单号")
|
||||||
private String refundNo;
|
private String refundNo;
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付单ID, 通常为商户的业务订单号,全部退款时,
|
|
||||||
* paymentId 和 businessNo可传其中一个,同时传输以前者为准,部分退款可以不进行传输
|
|
||||||
*/
|
|
||||||
@Schema(description = "支付单ID")
|
|
||||||
private Long paymentId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付订单的业务号, 通常为商户的业务订单号,全部退款时,
|
|
||||||
* paymentId 和 businessNo可传其中一个,同时传输以前者为准,部分退款可以不进行传输
|
|
||||||
*/
|
|
||||||
@Schema(description = "业务号")
|
|
||||||
private String businessNo;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
package cn.bootx.platform.daxpay.result.pay;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款同步结果
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Schema(title = "退款同步结果")
|
||||||
|
public class RefundSyncResult {
|
||||||
|
}
|
@@ -11,7 +11,7 @@ import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
|
|||||||
import cn.bootx.platform.daxpay.result.pay.RefundResult;
|
import cn.bootx.platform.daxpay.result.pay.RefundResult;
|
||||||
import cn.bootx.platform.daxpay.service.annotation.PaymentApi;
|
import cn.bootx.platform.daxpay.service.annotation.PaymentApi;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
|
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.service.PayRefundQueryService;
|
import cn.bootx.platform.daxpay.service.core.order.refund.service.PayRefundOrderQueryService;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.close.service.PayCloseService;
|
import cn.bootx.platform.daxpay.service.core.payment.close.service.PayCloseService;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.pay.service.PayService;
|
import cn.bootx.platform.daxpay.service.core.payment.pay.service.PayService;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.refund.service.PayRefundService;
|
import cn.bootx.platform.daxpay.service.core.payment.refund.service.PayRefundService;
|
||||||
@@ -38,7 +38,7 @@ public class UniPayController {
|
|||||||
private final PaySyncService paySyncService;
|
private final PaySyncService paySyncService;
|
||||||
private final PayCloseService payCloseService;
|
private final PayCloseService payCloseService;
|
||||||
private final PayOrderQueryService PayOrderQueryService;
|
private final PayOrderQueryService PayOrderQueryService;
|
||||||
private final PayRefundQueryService payRefundQueryService;
|
private final PayRefundOrderQueryService payRefundQueryService;
|
||||||
|
|
||||||
|
|
||||||
@CountTime
|
@CountTime
|
||||||
|
@@ -53,9 +53,6 @@ public interface AliPayCode {
|
|||||||
/** 退款业务号 */
|
/** 退款业务号 */
|
||||||
String OUT_BIZ_NO = "out_biz_no";
|
String OUT_BIZ_NO = "out_biz_no";
|
||||||
|
|
||||||
/** 退款流水号 */
|
|
||||||
|
|
||||||
|
|
||||||
/** 退款金额 */
|
/** 退款金额 */
|
||||||
String REFUND_FEE = "refund_fee";
|
String REFUND_FEE = "refund_fee";
|
||||||
|
|
||||||
@@ -78,6 +75,10 @@ public interface AliPayCode {
|
|||||||
/** 交易结束,不可退款 */
|
/** 交易结束,不可退款 */
|
||||||
String NOTIFY_TRADE_FINISHED = "TRADE_FINISHED";
|
String NOTIFY_TRADE_FINISHED = "TRADE_FINISHED";
|
||||||
|
|
||||||
|
// 退款状态
|
||||||
|
/** 退款成功 */
|
||||||
|
String REFUND_SUCCESS = "REFUND_SUCCESS";
|
||||||
|
|
||||||
|
|
||||||
// 错误提示
|
// 错误提示
|
||||||
/** 交易不存在 */
|
/** 交易不存在 */
|
||||||
|
@@ -143,7 +143,7 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
|
|||||||
public PayCallbackTypeEnum getCallbackType() {
|
public PayCallbackTypeEnum getCallbackType() {
|
||||||
CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo();
|
CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo();
|
||||||
Map<String, String> callbackParam = callback.getCallbackParam();
|
Map<String, String> callbackParam = callback.getCallbackParam();
|
||||||
String refundFee = callbackParam.get("refund_fee");
|
String refundFee = callbackParam.get(REFUND_FEE);
|
||||||
// 如果有退款金额,说明是退款回调
|
// 如果有退款金额,说明是退款回调
|
||||||
if (StrUtil.isNotBlank(refundFee)){
|
if (StrUtil.isNotBlank(refundFee)){
|
||||||
return PayCallbackTypeEnum.REFUND;
|
return PayCallbackTypeEnum.REFUND;
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.channel.alipay.service;
|
package cn.bootx.platform.daxpay.service.core.channel.alipay.service;
|
||||||
|
|
||||||
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.service.code.AliPayCode;
|
import cn.bootx.platform.daxpay.service.code.AliPayCode;
|
||||||
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
|
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.entity.PayOrder;
|
||||||
|
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.PayGatewaySyncResult;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.alipay.api.AlipayApiException;
|
import com.alipay.api.AlipayApiException;
|
||||||
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
|
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
|
||||||
@@ -45,7 +48,6 @@ public class AliPaySyncService {
|
|||||||
AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
|
AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
|
||||||
queryModel.setOutTradeNo(String.valueOf(payOrder.getId()));
|
queryModel.setOutTradeNo(String.valueOf(payOrder.getId()));
|
||||||
// queryModel.setQueryOptions(Collections.singletonList("trade_settle_info"));
|
// queryModel.setQueryOptions(Collections.singletonList("trade_settle_info"));
|
||||||
// 查询参数
|
|
||||||
AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel);
|
AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel);
|
||||||
String tradeStatus = response.getTradeStatus();
|
String tradeStatus = response.getTradeStatus();
|
||||||
syncResult.setSyncInfo(JSONUtil.toJsonStr(response));
|
syncResult.setSyncInfo(JSONUtil.toJsonStr(response));
|
||||||
@@ -77,7 +79,7 @@ public class AliPaySyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (AlipayApiException e) {
|
catch (AlipayApiException e) {
|
||||||
log.error("查询订单失败:", e);
|
log.error("支付订单同步失败:", e);
|
||||||
syncResult.setErrorMsg(e.getErrMsg());
|
syncResult.setErrorMsg(e.getErrMsg());
|
||||||
}
|
}
|
||||||
return syncResult;
|
return syncResult;
|
||||||
@@ -86,10 +88,23 @@ public class AliPaySyncService {
|
|||||||
/**
|
/**
|
||||||
* 退款同步查询
|
* 退款同步查询
|
||||||
*/
|
*/
|
||||||
private void syncRefundStatus(PayOrder payOrder) throws AlipayApiException {
|
public RefundGatewaySyncResult syncRefundStatus(PayRefundOrder refundOrder) {
|
||||||
AlipayTradeFastpayRefundQueryModel queryModel = new AlipayTradeFastpayRefundQueryModel();
|
RefundGatewaySyncResult syncResult = new RefundGatewaySyncResult().setSyncStatus(PayRefundSyncStatusEnum.FAIL);
|
||||||
queryModel.setOutTradeNo(String.valueOf(payOrder.getId()));
|
try {
|
||||||
AlipayTradeFastpayRefundQueryResponse response = AliPayApi.tradeRefundQueryToResponse(queryModel);
|
AlipayTradeFastpayRefundQueryModel queryModel = new AlipayTradeFastpayRefundQueryModel();
|
||||||
response.getRefundStatus();
|
queryModel.setOutTradeNo(String.valueOf(refundOrder.getId()));
|
||||||
|
AlipayTradeFastpayRefundQueryResponse response = AliPayApi.tradeRefundQueryToResponse(queryModel);
|
||||||
|
|
||||||
|
syncResult.setSyncInfo(JSONUtil.toJsonStr(response));
|
||||||
|
String tradeStatus = response.getRefundStatus();
|
||||||
|
// 成功
|
||||||
|
if (Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_SUCCESS)){
|
||||||
|
return syncResult.setSyncStatus(PayRefundSyncStatusEnum.SUCCESS);
|
||||||
|
}
|
||||||
|
} catch (AlipayApiException e) {
|
||||||
|
log.error("退款订单同步失败:", e);
|
||||||
|
syncResult.setErrorMsg(e.getErrMsg());
|
||||||
|
}
|
||||||
|
return syncResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,15 +2,18 @@ package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
|
|||||||
|
|
||||||
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
||||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||||
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
|
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
|
||||||
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
|
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.channel.wechat.entity.WeChatPayConfig;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
|
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
|
|
||||||
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
|
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.bootx.platform.daxpay.service.core.payment.sync.result.PayGatewaySyncResult;
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.PayGatewaySyncResult;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.ijpay.core.enums.SignType;
|
import com.ijpay.core.enums.SignType;
|
||||||
@@ -82,7 +85,8 @@ public class WeChatPaySyncService {
|
|||||||
|
|
||||||
// 已退款/退款中 触发一下退款记录的查询
|
// 已退款/退款中 触发一下退款记录的查询
|
||||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_REFUND)) {
|
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_REFUND)) {
|
||||||
this.syncRefundStatus(order, weChatPayConfig);
|
// this.syncRefundStatus(order, weChatPayConfig);
|
||||||
|
// TODO 特殊处理, 提示用户走退款同步
|
||||||
}
|
}
|
||||||
// 已关闭
|
// 已关闭
|
||||||
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_CLOSED)
|
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_CLOSED)
|
||||||
@@ -101,28 +105,25 @@ public class WeChatPaySyncService {
|
|||||||
/**
|
/**
|
||||||
* 退款查询
|
* 退款查询
|
||||||
*/
|
*/
|
||||||
private PayGatewaySyncResult syncRefundStatus(PayOrder order, WeChatPayConfig weChatPayConfig){
|
public RefundGatewaySyncResult syncRefundStatus(PayRefundOrder refundOrder, WeChatPayConfig weChatPayConfig){
|
||||||
PayChannelOrder orderChannel = payChannelOrderManager.findByPaymentIdAndChannel(order.getId(), PayChannelEnum.WECHAT.getCode())
|
PayChannelOrder orderChannel = payChannelOrderManager.findByPaymentIdAndChannel(refundOrder.getId(), PayChannelEnum.WECHAT.getCode())
|
||||||
.orElseThrow(() -> new PayFailureException("支付订单通道信息不存在"));
|
.orElseThrow(() -> new PayFailureException("支付订单通道信息不存在"));
|
||||||
|
|
||||||
Map<String, String> params = UnifiedOrderModel.builder()
|
Map<String, String> params = UnifiedOrderModel.builder()
|
||||||
.appid(weChatPayConfig.getWxAppId())
|
.appid(weChatPayConfig.getWxAppId())
|
||||||
.mch_id(weChatPayConfig.getWxMchId())
|
.mch_id(weChatPayConfig.getWxMchId())
|
||||||
.nonce_str(WxPayKit.generateStr())
|
.nonce_str(WxPayKit.generateStr())
|
||||||
.out_trade_no(String.valueOf(order.getId()))
|
.out_trade_no(String.valueOf(refundOrder.getId()))
|
||||||
.build()
|
.build()
|
||||||
.createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256);
|
.createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256);
|
||||||
String xmlResult = WxPayApi.orderRefundQuery(false, params);
|
String xmlResult = WxPayApi.orderRefundQuery(false, params);
|
||||||
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
|
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
|
||||||
// 获取
|
// TODO 处理退款同步的情况
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 判断是否全部退款
|
|
||||||
Integer refundFee = Integer.valueOf(result.get(WeChatPayCode.REFUND_FEE));
|
Integer refundFee = Integer.valueOf(result.get(WeChatPayCode.REFUND_FEE));
|
||||||
if (Objects.equals(refundFee, orderChannel.getAmount())){
|
if (Objects.equals(refundFee, orderChannel.getAmount())){
|
||||||
return new PayGatewaySyncResult().setSyncStatus(PaySyncStatusEnum.REFUND);
|
return new RefundGatewaySyncResult().setSyncStatus(PayRefundSyncStatusEnum.REFUNDING);
|
||||||
}
|
}
|
||||||
return new PayGatewaySyncResult().setSyncInfo(JSONUtil.toJsonStr(result));
|
return new RefundGatewaySyncResult().setSyncInfo(JSONUtil.toJsonStr(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,14 +38,14 @@ public class PayOrder extends MpBaseEntity implements EntityBaseFunction<PayOrde
|
|||||||
@DbColumn(comment = "标题")
|
@DbColumn(comment = "标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
/** 是否是异步支付 */
|
|
||||||
@DbColumn(comment = "是否是异步支付")
|
|
||||||
private boolean asyncPay;
|
|
||||||
|
|
||||||
/** 是否是组合支付 */
|
/** 是否是组合支付 */
|
||||||
@DbColumn(comment = "是否是组合支付")
|
@DbColumn(comment = "是否是组合支付")
|
||||||
private boolean combinationPay;
|
private boolean combinationPay;
|
||||||
|
|
||||||
|
/** 是否是异步支付 */
|
||||||
|
@DbColumn(comment = "是否是异步支付")
|
||||||
|
private boolean asyncPay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步支付通道
|
* 异步支付通道
|
||||||
* @see PayChannelEnum#ASYNC_TYPE_CODE
|
* @see PayChannelEnum#ASYNC_TYPE_CODE
|
||||||
|
@@ -48,15 +48,15 @@ public class PayChannelOrderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换支付订单关联的异步支付通道
|
* 切换支付订单关联的异步支付通道, 同时会设置是否支付完成状态
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void switchAsyncPayChannel(PayOrder payOrder, PayChannelParam payChannelParam){
|
public void switchAsyncPayChannel(PayOrder payOrder, PayChannelParam payChannelParam){
|
||||||
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();
|
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();
|
||||||
// 是否支付完成
|
// 是否支付完成
|
||||||
PayStatusEnum payStatus = asyncPayInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS;
|
PayStatusEnum payStatus = asyncPayInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS;
|
||||||
Optional<PayChannelOrder> payOrderChannelOpt =
|
// 判断新发起的
|
||||||
channelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payChannelParam.getChannel());
|
Optional<PayChannelOrder> payOrderChannelOpt = channelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payChannelParam.getChannel());
|
||||||
if (!payOrderChannelOpt.isPresent()){
|
if (!payOrderChannelOpt.isPresent()){
|
||||||
PayChannelOrder payChannelOrder = new PayChannelOrder();
|
PayChannelOrder payChannelOrder = new PayChannelOrder();
|
||||||
// 替换原有的的支付通道信息
|
// 替换原有的的支付通道信息
|
||||||
@@ -70,7 +70,7 @@ public class PayChannelOrderService {
|
|||||||
.setPayTime(LocalDateTime.now())
|
.setPayTime(LocalDateTime.now())
|
||||||
.setChannelExtra(payChannelParam.getChannelExtra())
|
.setChannelExtra(payChannelParam.getChannelExtra())
|
||||||
.setStatus(payStatus.getCode());
|
.setStatus(payStatus.getCode());
|
||||||
channelOrderManager.deleteByPaymentIdAndAsync(payChannelOrder.getId());
|
channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId());
|
||||||
channelOrderManager.save(payChannelOrder);
|
channelOrderManager.save(payChannelOrder);
|
||||||
} else {
|
} else {
|
||||||
// 更新支付通道信息
|
// 更新支付通道信息
|
||||||
@@ -94,6 +94,7 @@ public class PayChannelOrderService {
|
|||||||
if (Objects.equals(refundChannelOrder.getStatus(), PayRefundStatusEnum.SUCCESS.getCode())){
|
if (Objects.equals(refundChannelOrder.getStatus(), PayRefundStatusEnum.SUCCESS.getCode())){
|
||||||
PayStatusEnum status = refundableBalance == 0 ? PayStatusEnum.REFUNDED : PayStatusEnum.PARTIAL_REFUND;
|
PayStatusEnum status = refundableBalance == 0 ? PayStatusEnum.REFUNDED : PayStatusEnum.PARTIAL_REFUND;
|
||||||
payChannelOrder.setStatus(status.getCode());
|
payChannelOrder.setStatus(status.getCode());
|
||||||
|
refundChannelOrder.setRefundTime(LocalDateTime.now());
|
||||||
} else {
|
} else {
|
||||||
payChannelOrder.setStatus(PayStatusEnum.REFUNDING.getCode());
|
payChannelOrder.setStatus(PayStatusEnum.REFUNDING.getCode());
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.order.refund.entity;
|
package cn.bootx.platform.daxpay.service.core.order.refund.entity;
|
||||||
|
|
||||||
import cn.bootx.platform.common.core.function.EntityBaseFunction;
|
import cn.bootx.platform.common.core.function.EntityBaseFunction;
|
||||||
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
|
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
|
||||||
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderChannelConvert;
|
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderChannelConvert;
|
||||||
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundChannelOrderDto;
|
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundChannelOrderDto;
|
||||||
@@ -12,6 +12,8 @@ import lombok.Data;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付退款订单关联通道信息
|
* 支付退款订单关联通道信息
|
||||||
* @author xxm
|
* @author xxm
|
||||||
@@ -22,7 +24,7 @@ import lombok.experimental.Accessors;
|
|||||||
@DbTable(comment = "支付退款通道订单")
|
@DbTable(comment = "支付退款通道订单")
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@TableName("pay_refund_channel_order")
|
@TableName("pay_refund_channel_order")
|
||||||
public class PayRefundChannelOrder extends MpBaseEntity implements EntityBaseFunction<RefundChannelOrderDto> {
|
public class PayRefundChannelOrder extends MpCreateEntity implements EntityBaseFunction<RefundChannelOrderDto> {
|
||||||
|
|
||||||
@DbColumn(comment = "关联退款id")
|
@DbColumn(comment = "关联退款id")
|
||||||
private Long refundId;
|
private Long refundId;
|
||||||
@@ -49,6 +51,8 @@ public class PayRefundChannelOrder extends MpBaseEntity implements EntityBaseFun
|
|||||||
@DbColumn(comment = "退款状态")
|
@DbColumn(comment = "退款状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@DbColumn(comment = "退款时间")
|
||||||
|
private LocalDateTime refundTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换
|
* 转换
|
||||||
|
@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.core.order.refund.entity;
|
|||||||
|
|
||||||
import cn.bootx.platform.common.core.function.EntityBaseFunction;
|
import cn.bootx.platform.common.core.function.EntityBaseFunction;
|
||||||
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
|
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.convert.PayRefundOrderConvert;
|
import cn.bootx.platform.daxpay.service.core.order.refund.convert.PayRefundOrderConvert;
|
||||||
import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto;
|
import cn.bootx.platform.daxpay.service.dto.order.refund.PayRefundOrderDto;
|
||||||
@@ -41,6 +42,17 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<P
|
|||||||
@DbColumn(comment = "退款号")
|
@DbColumn(comment = "退款号")
|
||||||
private String refundNo;
|
private String refundNo;
|
||||||
|
|
||||||
|
/** 退款时是否是含有异步通道 */
|
||||||
|
@DbColumn(comment = "是否含有异步通道")
|
||||||
|
private boolean asyncPay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步通道
|
||||||
|
* @see PayChannelEnum#ASYNC_TYPE_CODE
|
||||||
|
*/
|
||||||
|
@DbColumn(comment = "异步通道")
|
||||||
|
private String asyncChannel;
|
||||||
|
|
||||||
/** 如果有异步通道, 保存关联的网关订单号 */
|
/** 如果有异步通道, 保存关联的网关订单号 */
|
||||||
@DbColumn(comment = "网关订单号")
|
@DbColumn(comment = "网关订单号")
|
||||||
private String gatewayOrderNo;
|
private String gatewayOrderNo;
|
||||||
|
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PayRefundQueryService {
|
public class PayRefundOrderQueryService {
|
||||||
|
|
||||||
private final PayRefundOrderManager refundOrderManager;
|
private final PayRefundOrderManager refundOrderManager;
|
||||||
private final PayRefundChannelOrderManager refundOrderChannelManager;
|
private final PayRefundChannelOrderManager refundOrderChannelManager;
|
@@ -0,0 +1,33 @@
|
|||||||
|
package cn.bootx.platform.daxpay.service.core.order.refund.service;
|
||||||
|
|
||||||
|
import cn.bootx.platform.common.core.exception.DataNotExistException;
|
||||||
|
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 void syncById(Long id){
|
||||||
|
PayRefundOrder refundOrder = refundOrderManager.findById(id)
|
||||||
|
.orElseThrow(() -> new DataNotExistException("退款订单不存在"));
|
||||||
|
refundSyncService.syncPayOrder(refundOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -192,12 +192,11 @@ public class PayAssistService {
|
|||||||
// 退款类型状态
|
// 退款类型状态
|
||||||
tradesStatus = Arrays.asList(REFUNDED.getCode(), PARTIAL_REFUND.getCode(), REFUNDING.getCode());
|
tradesStatus = Arrays.asList(REFUNDED.getCode(), PARTIAL_REFUND.getCode(), REFUNDING.getCode());
|
||||||
if (tradesStatus.contains(payOrder.getStatus())) {
|
if (tradesStatus.contains(payOrder.getStatus())) {
|
||||||
throw new PayFailureException("退款中");
|
throw new PayFailureException("该订单处于退款状态");
|
||||||
}
|
}
|
||||||
// 其他状态直接抛出兜底异常
|
// 其他状态直接抛出兜底异常
|
||||||
throw new PayFailureException("订单不是待支付状态,请重新确认订单状态");
|
throw new PayFailureException("订单不是待支付状态,请重新确认订单状态");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package cn.bootx.platform.daxpay.service.core.payment.refund.service;
|
|||||||
|
|
||||||
import cn.bootx.platform.common.core.exception.ValidationFailedException;
|
import cn.bootx.platform.common.core.exception.ValidationFailedException;
|
||||||
import cn.bootx.platform.common.core.util.CollUtil;
|
import cn.bootx.platform.common.core.util.CollUtil;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||||
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
|
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
|
||||||
@@ -27,6 +29,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付退款支撑服务
|
* 支付退款支撑服务
|
||||||
@@ -84,9 +87,9 @@ public class PayRefundAssistService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据退款参数获取支付订单, 并进行检查
|
* 检查并处理退款参数
|
||||||
*/
|
*/
|
||||||
public void checkByRefundParam(RefundParam param, PayOrder payOrder){
|
public void checkAndDisposeParam(RefundParam param, PayOrder payOrder){
|
||||||
// 全额退款和部分退款校验
|
// 全额退款和部分退款校验
|
||||||
if (!param.isRefundAll()) {
|
if (!param.isRefundAll()) {
|
||||||
if (CollUtil.isEmpty(param.getRefundChannels())) {
|
if (CollUtil.isEmpty(param.getRefundChannels())) {
|
||||||
@@ -112,6 +115,13 @@ public class PayRefundAssistService {
|
|||||||
throw new PayFailureException("当前状态["+statusEnum.getName()+"]不允许状态非法, 无法退款");
|
throw new PayFailureException("当前状态["+statusEnum.getName()+"]不允许状态非法, 无法退款");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过滤掉金额为0的退款参数
|
||||||
|
List<RefundChannelParam> channelParams = param.getRefundChannels()
|
||||||
|
.stream()
|
||||||
|
.filter(r -> r.getAmount() > 0)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
param.setRefundChannels(channelParams);
|
||||||
|
|
||||||
// 退款号唯一校验
|
// 退款号唯一校验
|
||||||
if (StrUtil.isNotBlank(param.getRefundNo())
|
if (StrUtil.isNotBlank(param.getRefundNo())
|
||||||
&& payRefundOrderManager.existsByRefundNo(param.getRefundNo())){
|
&& payRefundOrderManager.existsByRefundNo(param.getRefundNo())){
|
||||||
@@ -140,11 +150,28 @@ public class PayRefundAssistService {
|
|||||||
.setRefundTime(LocalDateTime.now())
|
.setRefundTime(LocalDateTime.now())
|
||||||
.setTitle(payOrder.getTitle())
|
.setTitle(payOrder.getTitle())
|
||||||
.setGatewayOrderNo(asyncRefundInfo.getGatewayOrderNo())
|
.setGatewayOrderNo(asyncRefundInfo.getGatewayOrderNo())
|
||||||
.setErrorCode(asyncRefundInfo.getErrorCode())
|
|
||||||
.setErrorMsg(asyncRefundInfo.getErrorMsg())
|
|
||||||
.setStatus(asyncRefundInfo.getStatus().getCode())
|
.setStatus(asyncRefundInfo.getStatus().getCode())
|
||||||
.setClientIp(refundParam.getClientIp())
|
.setClientIp(refundParam.getClientIp())
|
||||||
.setReqId(PaymentContextLocal.get().getRequestInfo().getReqId());
|
.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()
|
||||||
|
.stream()
|
||||||
|
.filter(r -> PayChannelEnum.ASYNC_TYPE_CODE.contains(r.getChannel()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (Objects.nonNull(asyncChannel)){
|
||||||
|
refundOrder.setAsyncChannel(asyncChannel.getChannel());
|
||||||
|
refundOrder.setAsyncPay(true);
|
||||||
|
}
|
||||||
|
|
||||||
// 主键使用预先生成的ID, 如果有异步通道, 关联的退款号就是这个ID
|
// 主键使用预先生成的ID, 如果有异步通道, 关联的退款号就是这个ID
|
||||||
long refundId = asyncRefundInfo.getRefundId();
|
long refundId = asyncRefundInfo.getRefundId();
|
||||||
refundOrder.setId(refundId);
|
refundOrder.setId(refundId);
|
||||||
|
@@ -86,7 +86,7 @@ public class PayRefundService {
|
|||||||
// 获取支付订单
|
// 获取支付订单
|
||||||
PayOrder payOrder = payRefundAssistService.getPayOrder(param);
|
PayOrder payOrder = payRefundAssistService.getPayOrder(param);
|
||||||
// 第一次检查退款参数, 校验一些特殊情况
|
// 第一次检查退款参数, 校验一些特殊情况
|
||||||
payRefundAssistService.checkByRefundParam(param, payOrder);
|
payRefundAssistService.checkAndDisposeParam(param, payOrder);
|
||||||
|
|
||||||
// 组装退款参数, 处理全部退款和简单退款情况
|
// 组装退款参数, 处理全部退款和简单退款情况
|
||||||
List<PayChannelOrder> payChannelOrders = payChannelOrderManager.findAllByPaymentId(payOrder.getId());
|
List<PayChannelOrder> payChannelOrders = payChannelOrderManager.findAllByPaymentId(payOrder.getId());
|
||||||
@@ -126,6 +126,7 @@ public class PayRefundService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 分支付通道进行退款
|
* 分支付通道进行退款
|
||||||
|
* TODO 增加错误处理, 目前出现错误后存储的数据不全
|
||||||
*/
|
*/
|
||||||
public RefundResult refundByChannel(RefundParam refundParam, PayOrder payOrder, List<PayChannelOrder> payChannelOrders){
|
public RefundResult refundByChannel(RefundParam refundParam, PayOrder payOrder, List<PayChannelOrder> payChannelOrders){
|
||||||
// 0.基础数据准备, 并比对通道支付单是否与可退款记录数量一致
|
// 0.基础数据准备, 并比对通道支付单是否与可退款记录数量一致
|
||||||
|
@@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,6 +70,7 @@ public class WeChatPayRefundStrategy extends AbsRefundStrategy {
|
|||||||
.getRefundInfo()
|
.getRefundInfo()
|
||||||
.getStatus();
|
.getStatus();
|
||||||
this.getRefundChannelOrder().setStatus(refundStatusEnum.getCode());
|
this.getRefundChannelOrder().setStatus(refundStatusEnum.getCode());
|
||||||
|
|
||||||
// 更新支付通道订单中的属性
|
// 更新支付通道订单中的属性
|
||||||
payChannelOrderService.updateAsyncPayRefund(this.getPayChannelOrder(), this.getRefundChannelOrder());
|
payChannelOrderService.updateAsyncPayRefund(this.getPayChannelOrder(), this.getRefundChannelOrder());
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.repair.service;
|
|||||||
|
|
||||||
import cn.bootx.platform.common.core.function.CollectorsFunction;
|
import cn.bootx.platform.common.core.function.CollectorsFunction;
|
||||||
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
import cn.bootx.platform.daxpay.code.PayStatusEnum;
|
||||||
|
import cn.bootx.platform.daxpay.service.code.PayRepairPayTypeEnum;
|
||||||
import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum;
|
import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum;
|
||||||
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
|
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
|
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
|
||||||
@@ -160,6 +161,7 @@ public class PayRepairService {
|
|||||||
.setOrderNo(order.getBusinessNo())
|
.setOrderNo(order.getBusinessNo())
|
||||||
.setBeforeStatus(repairResult.getBeforeStatus().getCode())
|
.setBeforeStatus(repairResult.getBeforeStatus().getCode())
|
||||||
.setAfterStatus(afterStatus)
|
.setAfterStatus(afterStatus)
|
||||||
|
.setRepairType(PayRepairPayTypeEnum.PAY.getCode())
|
||||||
.setRepairSource(source)
|
.setRepairSource(source)
|
||||||
.setRepairWay(recordType.getCode());
|
.setRepairWay(recordType.getCode());
|
||||||
payRepairRecord.setId(repairResult.getRepairId());
|
payRepairRecord.setId(repairResult.getRepairId());
|
||||||
|
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -106,7 +107,8 @@ public class RefundRepairService {
|
|||||||
}
|
}
|
||||||
// 设置退款为完成状态
|
// 设置退款为完成状态
|
||||||
refundOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
refundOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
||||||
refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode())
|
||||||
|
.setRefundTime(LocalDateTime.now());
|
||||||
payOrder.setStatus(afterPayRefundStatus.getCode());
|
payOrder.setStatus(afterPayRefundStatus.getCode());
|
||||||
// 更新订单和退款相关订单
|
// 更新订单和退款相关订单
|
||||||
payChannelOrderManager.updateById(payChannelOrder);
|
payChannelOrderManager.updateById(payChannelOrder);
|
||||||
@@ -175,6 +177,8 @@ public class RefundRepairService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付订单的修复记录
|
* 支付订单的修复记录
|
||||||
|
* 支付完成 -> 退款
|
||||||
|
* 退款 -> 全部退款
|
||||||
*/
|
*/
|
||||||
private PayRepairRecord payRepairRecord(PayOrder order, RefundRepairWayEnum repairType, RefundRepairResult repairResult){
|
private PayRepairRecord payRepairRecord(PayOrder order, RefundRepairWayEnum repairType, RefundRepairResult repairResult){
|
||||||
// 修复后的状态
|
// 修复后的状态
|
||||||
@@ -184,18 +188,20 @@ public class RefundRepairService {
|
|||||||
.getRepairInfo()
|
.getRepairInfo()
|
||||||
.getSource().getCode();
|
.getSource().getCode();
|
||||||
return new PayRepairRecord()
|
return new PayRepairRecord()
|
||||||
|
.setRepairId(repairResult.getRepairId())
|
||||||
.setOrderId(order.getId())
|
.setOrderId(order.getId())
|
||||||
.setRepairType(PayRepairPayTypeEnum.PAY.getCode())
|
.setRepairType(PayRepairPayTypeEnum.PAY.getCode())
|
||||||
|
.setRepairSource(source)
|
||||||
|
.setRepairWay(repairType.getCode())
|
||||||
.setAsyncChannel(order.getAsyncChannel())
|
.setAsyncChannel(order.getAsyncChannel())
|
||||||
.setOrderNo(order.getBusinessNo())
|
.setOrderNo(order.getBusinessNo())
|
||||||
.setBeforeStatus(repairResult.getAfterPayStatus().getCode())
|
.setBeforeStatus(repairResult.getAfterPayStatus().getCode())
|
||||||
.setAfterStatus(afterStatus)
|
.setAfterStatus(afterStatus);
|
||||||
.setRepairSource(source)
|
|
||||||
.setRepairWay(repairType.getCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款订单的修复记录
|
* 退款订单的修复记录
|
||||||
|
* 退款中 -> 退款成功
|
||||||
*/
|
*/
|
||||||
private PayRepairRecord refundRepairRecord(PayRefundOrder refundOrder, RefundRepairWayEnum repairType, RefundRepairResult repairResult){
|
private PayRepairRecord refundRepairRecord(PayRefundOrder refundOrder, RefundRepairWayEnum repairType, RefundRepairResult repairResult){
|
||||||
// 修复后的状态
|
// 修复后的状态
|
||||||
@@ -206,6 +212,7 @@ public class RefundRepairService {
|
|||||||
.getSource().getCode();
|
.getSource().getCode();
|
||||||
return new PayRepairRecord()
|
return new PayRepairRecord()
|
||||||
.setOrderId(refundOrder.getId())
|
.setOrderId(refundOrder.getId())
|
||||||
|
.setRepairId(repairResult.getRepairId())
|
||||||
.setOrderNo(refundOrder.getRefundNo())
|
.setOrderNo(refundOrder.getRefundNo())
|
||||||
.setRepairType(PayRepairPayTypeEnum.REFUND.getCode())
|
.setRepairType(PayRepairPayTypeEnum.REFUND.getCode())
|
||||||
.setBeforeStatus(repairResult.getBeforeRefundStatus().getCode())
|
.setBeforeStatus(repairResult.getBeforeRefundStatus().getCode())
|
||||||
|
@@ -3,8 +3,8 @@ package cn.bootx.platform.daxpay.service.core.payment.sync.factory;
|
|||||||
|
|
||||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
import cn.bootx.platform.daxpay.service.func.AbsPaySyncStrategy;
|
import cn.bootx.platform.daxpay.service.func.AbsPaySyncStrategy;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.AliPaySyncStrategy;
|
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.pay.AliPaySyncStrategy;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.WeChatPaySyncStrategy;
|
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.pay.WeChatPaySyncStrategy;
|
||||||
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
|
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
package cn.bootx.platform.daxpay.service.core.payment.sync.factory;
|
||||||
|
|
||||||
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
|
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.Refund.AliRefundSyncStrategy;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.strategy.Refund.WeChatRefundSyncStrategy;
|
||||||
|
import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付退款同步策略工厂
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public class RefundSyncStrategyFactory {
|
||||||
|
/**
|
||||||
|
* 获取支付同步策略, 只有异步支付方式才需要这个功能
|
||||||
|
* @param channelCode 支付通道编码
|
||||||
|
* @return 支付同步策略类
|
||||||
|
*/
|
||||||
|
public static AbsRefundSyncStrategy create(String channelCode) {
|
||||||
|
AbsRefundSyncStrategy strategy;
|
||||||
|
PayChannelEnum channelEnum = PayChannelEnum.findByCode(channelCode);
|
||||||
|
switch (channelEnum) {
|
||||||
|
case ALI:
|
||||||
|
strategy = SpringUtil.getBean(AliRefundSyncStrategy.class);
|
||||||
|
break;
|
||||||
|
case WECHAT:
|
||||||
|
strategy = SpringUtil.getBean(WeChatRefundSyncStrategy.class);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new PayUnsupportedMethodException();
|
||||||
|
}
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,9 +22,12 @@ public class PayGatewaySyncResult {
|
|||||||
*/
|
*/
|
||||||
private PaySyncStatusEnum syncStatus = FAIL;
|
private PaySyncStatusEnum syncStatus = FAIL;
|
||||||
|
|
||||||
/** 同步支付时网关返回的对象, 序列化为json字符串 */
|
/** 同步时网关返回的对象, 序列化为json字符串 */
|
||||||
private String syncInfo;
|
private String syncInfo;
|
||||||
|
|
||||||
|
/** 错误提示码 */
|
||||||
|
private String errorCode;
|
||||||
|
|
||||||
/** 错误提示 */
|
/** 错误提示 */
|
||||||
private String errorMsg;
|
private String errorMsg;
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.payment.sync.result;
|
package cn.bootx.platform.daxpay.service.core.payment.sync.result;
|
||||||
|
|
||||||
|
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import static cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum.FAIL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付退款同步结果
|
* 支付退款同步结果
|
||||||
* @author xxm
|
* @author xxm
|
||||||
@@ -11,4 +14,19 @@ import lombok.experimental.Accessors;
|
|||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class RefundGatewaySyncResult {
|
public class RefundGatewaySyncResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付网关订单状态
|
||||||
|
* @see PayRefundSyncStatusEnum
|
||||||
|
*/
|
||||||
|
private PayRefundSyncStatusEnum syncStatus = FAIL;
|
||||||
|
|
||||||
|
/** 同步时网关返回的对象, 序列化为json字符串 */
|
||||||
|
private String syncInfo;
|
||||||
|
|
||||||
|
/** 错误提示码 */
|
||||||
|
private String errorCode;
|
||||||
|
|
||||||
|
/** 错误提示 */
|
||||||
|
private String errorMsg;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,108 @@
|
|||||||
|
package cn.bootx.platform.daxpay.service.core.payment.sync.service;
|
||||||
|
|
||||||
|
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.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.factory.RefundSyncStrategyFactory;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||||
|
import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy;
|
||||||
|
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.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付退款同步服务类
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PayRefundSyncService {
|
||||||
|
private final PayRefundOrderManager refundOrderManager;
|
||||||
|
|
||||||
|
private final LockTemplate lockTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款同步, 开启一个新的事务, 不受外部抛出异常的影响
|
||||||
|
*/
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public void sync(RefundSyncParam param){
|
||||||
|
// 先获取退款单
|
||||||
|
PayRefundOrder requestOrder;
|
||||||
|
if (Objects.nonNull(param.getRefundId())){
|
||||||
|
requestOrder = refundOrderManager.findById(param.getRefundId())
|
||||||
|
.orElseThrow(() -> new PayFailureException("未查询到退款订单"));
|
||||||
|
} else {
|
||||||
|
requestOrder = refundOrderManager.findByRefundNo(param.getRefundNo())
|
||||||
|
.orElseThrow(() -> new PayFailureException("未查询到退款订单"));
|
||||||
|
}
|
||||||
|
// 如果不是异步支付, 直接返回返回
|
||||||
|
if (!requestOrder.isAsyncPay()){
|
||||||
|
// TODO 需要限制同步的请求不进行同步
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.syncPayOrder(requestOrder);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public void syncPayOrder(PayRefundOrder refundOrder) {
|
||||||
|
// 加锁
|
||||||
|
LockInfo lock = lockTemplate.lock("sync:refund:" + refundOrder.getId());
|
||||||
|
if (Objects.isNull(lock)) {
|
||||||
|
throw new RepetitiveOperationException("退款同步处理中,请勿重复操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取支付同步策略类
|
||||||
|
AbsRefundSyncStrategy syncPayStrategy = RefundSyncStrategyFactory.create(refundOrder.getAsyncChannel());
|
||||||
|
syncPayStrategy.initRefundParam(refundOrder);
|
||||||
|
// 执行操作, 获取支付网关同步的结果
|
||||||
|
RefundGatewaySyncResult syncResult = syncPayStrategy.doSyncStatus();
|
||||||
|
|
||||||
|
// 判断是否同步成功
|
||||||
|
if (Objects.equals(syncResult.getSyncStatus(), PayRefundSyncStatusEnum.FAIL)) {
|
||||||
|
// 同步失败, 返回失败响应, 同时记录失败的日志
|
||||||
|
// return new PaySyncResult().setErrorMsg(syncResult.getErrorMsg());
|
||||||
|
log.error("同步失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态
|
||||||
|
// boolean statusSync = this.checkAndAdjustSyncStatus(syncResult, payOrder);
|
||||||
|
// PayRepairResult repairResult = new PayRepairResult();
|
||||||
|
// 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());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 同步成功记录日志
|
||||||
|
// this.saveRecord(payOrder, syncResult, !statusSync, repairResult.getRepairId(), null);
|
||||||
|
// return new PaySyncResult()
|
||||||
|
// .setGatewayStatus(syncResult.getSyncStatus()
|
||||||
|
// .getCode())
|
||||||
|
// .setSuccess(true)
|
||||||
|
// .setRepair(!statusSync)
|
||||||
|
// .setRepairId(repairResult.getRepairId());
|
||||||
|
} finally {
|
||||||
|
lockTemplate.releaseLock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -79,11 +79,12 @@ public class PaySyncService {
|
|||||||
* 同步支付状态, 开启一个新的事务, 不受外部抛出异常的影响
|
* 同步支付状态, 开启一个新的事务, 不受外部抛出异常的影响
|
||||||
* 1. 如果状态一致, 不进行处理
|
* 1. 如果状态一致, 不进行处理
|
||||||
* 2. 如果状态不一致, 调用修复逻辑进行修复
|
* 2. 如果状态不一致, 调用修复逻辑进行修复
|
||||||
|
* todo 需要进行异常处理, 现在会有 Transaction rolled back because it has been marked as rollback-only 问题
|
||||||
*/
|
*/
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public PaySyncResult syncPayOrder(PayOrder payOrder) {
|
public PaySyncResult syncPayOrder(PayOrder payOrder) {
|
||||||
// 加锁
|
// 加锁
|
||||||
LockInfo lock = lockTemplate.lock("payment:refund:" + payOrder.getId());
|
LockInfo lock = lockTemplate.lock("sync:payment" + payOrder.getId());
|
||||||
if (Objects.isNull(lock)){
|
if (Objects.isNull(lock)){
|
||||||
throw new RepetitiveOperationException("支付同步处理中,请勿重复操作");
|
throw new RepetitiveOperationException("支付同步处理中,请勿重复操作");
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
package cn.bootx.platform.daxpay.service.core.payment.sync.strategy.Refund;
|
||||||
|
|
||||||
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
|
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPaySyncService;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||||
|
import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝退款同步策略
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@Scope(SCOPE_PROTOTYPE)
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AliRefundSyncStrategy extends AbsRefundSyncStrategy {
|
||||||
|
|
||||||
|
private final AliPayConfigService alipayConfigService;
|
||||||
|
|
||||||
|
private final AliPaySyncService aliPaySyncService;;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 策略标识
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PayChannelEnum getChannel() {
|
||||||
|
return PayChannelEnum.ALI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步支付单与支付网关进行状态比对后的结果
|
||||||
|
*
|
||||||
|
* @see PaySyncStatusEnum
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public RefundGatewaySyncResult doSyncStatus() {
|
||||||
|
AliPayConfig config = alipayConfigService.getConfig();
|
||||||
|
alipayConfigService.initConfig(config);
|
||||||
|
return aliPaySyncService.syncRefundStatus(this.getRefundOrder());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
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.payment.sync.result.RefundGatewaySyncResult;
|
||||||
|
import cn.bootx.platform.daxpay.service.func.AbsRefundSyncStrategy;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信退款同步策略
|
||||||
|
* @author xxm
|
||||||
|
* @since 2024/1/29
|
||||||
|
*/
|
||||||
|
@Scope(SCOPE_PROTOTYPE)
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WeChatRefundSyncStrategy extends AbsRefundSyncStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 策略标识
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PayChannelEnum getChannel() {
|
||||||
|
return PayChannelEnum.WECHAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步支付单与支付网关进行状态比对后的结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public RefundGatewaySyncResult doSyncStatus() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.payment.sync.strategy;
|
package cn.bootx.platform.daxpay.service.core.payment.sync.strategy.pay;
|
||||||
|
|
||||||
|
|
||||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
@@ -1,4 +1,4 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.payment.sync.strategy;
|
package cn.bootx.platform.daxpay.service.core.payment.sync.strategy.pay;
|
||||||
|
|
||||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
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.entity.WeChatPayConfig;
|
@@ -1,6 +1,5 @@
|
|||||||
package cn.bootx.platform.daxpay.service.core.timeout.task;
|
package cn.bootx.platform.daxpay.service.core.timeout.task;
|
||||||
|
|
||||||
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
|
|
||||||
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
|
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
|
||||||
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
|
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
|
||||||
import cn.bootx.platform.daxpay.service.core.timeout.dao.PayExpiredTimeRepository;
|
import cn.bootx.platform.daxpay.service.core.timeout.dao.PayExpiredTimeRepository;
|
||||||
@@ -8,6 +7,7 @@ import com.baomidou.lock.LockInfo;
|
|||||||
import com.baomidou.lock.LockTemplate;
|
import com.baomidou.lock.LockTemplate;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -29,14 +29,19 @@ public class PayExpiredTimeTask {
|
|||||||
|
|
||||||
private final LockTemplate lockTemplate;
|
private final LockTemplate lockTemplate;
|
||||||
|
|
||||||
// @Scheduled(cron = "*/5 * * * * ?")
|
/**
|
||||||
|
* 先使用定时任务实现, 五秒轮训一下
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "*/5 * * * * ?")
|
||||||
public void task(){
|
public void task(){
|
||||||
log.debug("执行超时取消任务....");
|
log.debug("执行超时取消任务....");
|
||||||
Set<String> expiredKeys = repository.getExpiredKeys(LocalDateTime.now());
|
Set<String> expiredKeys = repository.getExpiredKeys(LocalDateTime.now());
|
||||||
for (String expiredKey : expiredKeys) {
|
for (String expiredKey : expiredKeys) {
|
||||||
LockInfo lock = lockTemplate.lock("payment:expired:" + expiredKey,10000,0);
|
LockInfo lock = lockTemplate.lock("payment:expired:" + expiredKey,10000,0);
|
||||||
if (Objects.isNull(lock)){
|
if (Objects.isNull(lock)){
|
||||||
throw new RepetitiveOperationException("支付同步处理中,请勿重复操作");
|
log.warn("支付同步处理中,执行下一个");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// 执行同步操作, 网关同步时会对支付的进行状态的处理
|
// 执行同步操作, 网关同步时会对支付的进行状态的处理
|
||||||
|
@@ -14,7 +14,7 @@ import java.util.Objects;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* 待支付订单的状态同步, 先不进行启用
|
||||||
* @author xxm
|
* @author xxm
|
||||||
* @since 2024/1/5
|
* @since 2024/1/5
|
||||||
*/
|
*/
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cn.bootx.platform.daxpay.service.dto.order.refund;
|
package cn.bootx.platform.daxpay.service.dto.order.refund;
|
||||||
|
|
||||||
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||||
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -31,6 +32,16 @@ public class PayRefundOrderDto extends BaseDto {
|
|||||||
@Schema(description = "退款号")
|
@Schema(description = "退款号")
|
||||||
private String refundNo;
|
private String refundNo;
|
||||||
|
|
||||||
|
@Schema(description = "是否含有异步通道")
|
||||||
|
private boolean asyncPay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步通道
|
||||||
|
* @see PayChannelEnum#ASYNC_TYPE_CODE
|
||||||
|
*/
|
||||||
|
@Schema(description = "异步通道")
|
||||||
|
private String asyncChannel;
|
||||||
|
|
||||||
@Schema(description = "支付网关订单号")
|
@Schema(description = "支付网关订单号")
|
||||||
private String gatewayOrderNo;
|
private String gatewayOrderNo;
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cn.bootx.platform.daxpay.service.dto.order.refund;
|
package cn.bootx.platform.daxpay.service.dto.order.refund;
|
||||||
|
|
||||||
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
||||||
|
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
|
||||||
import cn.bootx.table.modify.annotation.DbColumn;
|
import cn.bootx.table.modify.annotation.DbColumn;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -36,4 +37,11 @@ public class RefundChannelOrderDto extends BaseDto {
|
|||||||
|
|
||||||
@Schema(description = "退款金额")
|
@Schema(description = "退款金额")
|
||||||
private Integer amount;
|
private Integer amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款状态
|
||||||
|
* @see PayRefundStatusEnum
|
||||||
|
*/
|
||||||
|
@Schema(description = "退款状态")
|
||||||
|
private String status;
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调处理抽象类, 处理支付回调和退款回调
|
* 回调处理抽象类, 处理支付回调和退款回调
|
||||||
@@ -32,35 +33,42 @@ public abstract class AbsCallbackStrategy implements PayStrategy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调处理入口
|
* 回调处理入口
|
||||||
|
* TODO 需要处理异常情况进行保存
|
||||||
*/
|
*/
|
||||||
public String callback(Map<String, String> params) {
|
public String callback(Map<String, String> params) {
|
||||||
// 将参数写入到上下文中
|
|
||||||
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
||||||
callbackInfo.getCallbackParam().putAll(params);
|
try {
|
||||||
// 验证消息
|
// 将参数写入到上下文中
|
||||||
if (!this.verifyNotify()) {
|
callbackInfo.getCallbackParam().putAll(params);
|
||||||
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
|
// 验证消息
|
||||||
// 消息有问题, 保存记录并返回
|
if (!this.verifyNotify()) {
|
||||||
this.saveCallbackRecord();
|
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
|
||||||
return null;
|
// 消息有问题, 保存记录并返回
|
||||||
}
|
this.saveCallbackRecord();
|
||||||
// 提前设置订单修复的来源
|
return null;
|
||||||
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
|
}
|
||||||
|
// 提前设置订单修复的来源
|
||||||
|
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
|
||||||
|
|
||||||
// 判断回调类型
|
// 判断回调类型
|
||||||
PayCallbackTypeEnum callbackType = this.getCallbackType();
|
PayCallbackTypeEnum callbackType = this.getCallbackType();
|
||||||
if (callbackType == PayCallbackTypeEnum.PAY){
|
if (callbackType == PayCallbackTypeEnum.PAY){
|
||||||
// 解析支付数据并放处理
|
// 解析支付数据并放处理
|
||||||
this.resolvePayData();
|
this.resolvePayData();
|
||||||
payCallbackService.payCallback();
|
payCallbackService.payCallback();
|
||||||
} else {
|
} else {
|
||||||
// 解析退款数据并放处理
|
// 解析退款数据并放处理
|
||||||
this.resolveRefundData();
|
this.resolveRefundData();
|
||||||
refundCallbackService.refundCallback();
|
refundCallbackService.refundCallback();
|
||||||
|
}
|
||||||
|
this.saveCallbackRecord();
|
||||||
|
return this.getReturnMsg();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("回调处理失败", e);
|
||||||
|
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("回调处理失败: "+e.getMessage());
|
||||||
|
this.saveCallbackRecord();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
// 记录回调记录
|
|
||||||
this.saveCallbackRecord();
|
|
||||||
return this.getReturnMsg();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,12 +102,18 @@ public abstract class AbsCallbackStrategy implements PayStrategy {
|
|||||||
*/
|
*/
|
||||||
public void saveCallbackRecord() {
|
public void saveCallbackRecord() {
|
||||||
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
|
||||||
|
|
||||||
|
// 回调类型
|
||||||
|
String callbackType = Optional.ofNullable(this.getCallbackType())
|
||||||
|
.map(PayCallbackTypeEnum::getCode)
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
PayCallbackRecord payNotifyRecord = new PayCallbackRecord()
|
PayCallbackRecord payNotifyRecord = new PayCallbackRecord()
|
||||||
.setPayChannel(this.getChannel().getCode())
|
.setPayChannel(this.getChannel().getCode())
|
||||||
.setNotifyInfo(JSONUtil.toJsonStr(callbackInfo.getCallbackParam()))
|
.setNotifyInfo(JSONUtil.toJsonStr(callbackInfo.getCallbackParam()))
|
||||||
.setOrderId(callbackInfo.getOrderId())
|
.setOrderId(callbackInfo.getOrderId())
|
||||||
.setGatewayOrderNo(callbackInfo.getGatewayOrderNo())
|
.setGatewayOrderNo(callbackInfo.getGatewayOrderNo())
|
||||||
.setCallbackType(this.getCallbackType().getCode())
|
.setCallbackType(callbackType)
|
||||||
.setRepairOrderId(callbackInfo.getPayRepairId())
|
.setRepairOrderId(callbackInfo.getPayRepairId())
|
||||||
.setStatus(callbackInfo.getCallbackStatus().getCode())
|
.setStatus(callbackInfo.getCallbackStatus().getCode())
|
||||||
.setMsg(callbackInfo.getMsg());
|
.setMsg(callbackInfo.getMsg());
|
||||||
|
@@ -4,6 +4,7 @@ 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.entity.PayOrder;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder;
|
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.order.refund.entity.PayRefundOrder;
|
||||||
|
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,15 +32,10 @@ public abstract class AbsRefundRepairStrategy implements PayStrategy{
|
|||||||
this.payChannelOrder = payChannelOrder;
|
this.payChannelOrder = payChannelOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 修复前处理
|
|
||||||
*/
|
|
||||||
public void doBeforeHandler(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付成功修复
|
* 异步支付单与支付网关进行状态比对后的结果
|
||||||
*/
|
*/
|
||||||
|
public abstract RefundGatewaySyncResult doSyncStatus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,8 @@ import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChanne
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 抽象支付退款策略基类
|
* 抽象支付退款策略基类
|
||||||
*
|
*
|
||||||
@@ -59,7 +61,8 @@ public abstract class AbsRefundStrategy implements PayStrategy{
|
|||||||
*/
|
*/
|
||||||
public void doSuccessHandler() {
|
public void doSuccessHandler() {
|
||||||
// 更新退款订单数据状态
|
// 更新退款订单数据状态
|
||||||
this.refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode());
|
this.refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode())
|
||||||
|
.setRefundTime(LocalDateTime.now());
|
||||||
|
|
||||||
// 支付通道订单客可退余额
|
// 支付通道订单客可退余额
|
||||||
int refundableBalance = this.getPayChannelOrder().getRefundableBalance() - this.refundChannelOrder.getAmount();
|
int refundableBalance = this.getPayChannelOrder().getRefundableBalance() - this.refundChannelOrder.getAmount();
|
||||||
|
@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.func;
|
|||||||
|
|
||||||
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
||||||
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder;
|
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;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +18,7 @@ public abstract class AbsRefundSyncStrategy implements PayStrategy{
|
|||||||
/**
|
/**
|
||||||
* 初始化参数
|
* 初始化参数
|
||||||
*/
|
*/
|
||||||
public void initParam(PayRefundOrder refundOrder){
|
public void initRefundParam(PayRefundOrder refundOrder){
|
||||||
this.refundOrder = refundOrder;
|
this.refundOrder = refundOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,5 +26,5 @@ public abstract class AbsRefundSyncStrategy implements PayStrategy{
|
|||||||
* 异步支付单与支付网关进行状态比对后的结果
|
* 异步支付单与支付网关进行状态比对后的结果
|
||||||
* @see PaySyncStatusEnum
|
* @see PaySyncStatusEnum
|
||||||
*/
|
*/
|
||||||
public abstract PayGatewaySyncResult doSyncStatus();
|
public abstract RefundGatewaySyncResult doSyncStatus();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user