ref 支付同步逻辑, 支付单状态不一致修复逻辑, 单商户单独拆包

This commit is contained in:
nws
2023-12-27 22:14:01 +08:00
parent 21bf0e40a3
commit af34eb9af2
99 changed files with 820 additions and 264 deletions

View File

@@ -4,6 +4,9 @@
- 增加回调机制 - 增加回调机制
- 增加消息通知机制 - 增加消息通知机制
- 支付超时取消支付单 - 支付超时取消支付单
- 支付单回调处理实现
- 支付同步相关逻辑
- 新增支付订单修复逻辑, 用于回调和支付同步后不一致的情况处理
1.0.1 1.0.1
- x 钱包支持多商户和多应用 - x 钱包支持多商户和多应用

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>dax-pay</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>daxpay-common</artifactId>
<description>支付公共的资源包</description>
<dependencies>
<!-- platform核心包 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-core</artifactId>
<version>${dax.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -9,7 +9,7 @@
<version>2.0.0</version> <version>2.0.0</version>
</parent> </parent>
<artifactId>daxpay-core</artifactId> <artifactId>daxpay-single-core</artifactId>
<description>支付核心依赖包</description> <description>支付核心依赖包</description>
<dependencies> <dependencies>

View File

@@ -36,17 +36,12 @@ public enum PayChannelEnum {
* 根据字符编码获取 * 根据字符编码获取
*/ */
public static PayChannelEnum findByCode(String code) { public static PayChannelEnum findByCode(String code) {
return Arrays.stream(PayChannelEnum.values()) return Arrays.stream(values())
.filter(e -> Objects.equals(code, e.getCode())) .filter(e -> Objects.equals(code, e.getCode()))
.findFirst() .findFirst()
.orElseThrow(() -> new PayFailureException("不存在的支付渠道")); .orElseThrow(() -> new PayFailureException("不存在的支付渠道"));
} }
public static boolean existsByCode(String code) {
return Arrays.stream(PayChannelEnum.values())
.anyMatch(payChannelEnum -> Objects.equals(payChannelEnum.getCode(), code));
}
/** 支付宝 UA */ /** 支付宝 UA */
public static final String UA_ALI_PAY = "Alipay"; public static final String UA_ALI_PAY = "Alipay";

View File

@@ -20,7 +20,7 @@ public enum PayStatusEnum {
/** 超时取消 */ /** 超时取消 */
TIMEOUT("timeout","超时取消"), TIMEOUT("timeout","超时取消"),
PARTIAL_REFUND("partial_refund","部分退款"), PARTIAL_REFUND("partial_refund","部分退款"),
REFUNDED("REFUNDED","全部退款"); REFUNDED("refunded","全部退款");
/** 编码 */ /** 编码 */
private final String code; private final String code;

View File

@@ -0,0 +1,39 @@
package cn.bootx.platform.daxpay.code;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 支付同步状态
*
* @author xxm
* @since 2021/4/21
*/
@Getter
@AllArgsConstructor
public enum PaySyncStatusEnum {
NOT_SYNC("not_sync", "不需要同步"),
PAY_SUCCESS("pay_success", "支付成功"),
PAY_WAIT("pay_wait", "等待付款中"),
CLOSED("closed", "已关闭"),
REFUND("refund", "已退款"),
NOT_FOUND("not_found", "未查询到订单"),
FAIL("fail", "查询失败");
/** 编码 */
private final String code;
/** 名称 */
private final String name;
public static PaySyncStatusEnum getByCode(String code) {
return Arrays.stream(values())
.filter(item -> Objects.equals(item.getCode(), code))
.findFirst()
.orElseThrow(() -> new PayFailureException("不存在的支付同步状态"));
}
}

View File

@@ -0,0 +1,28 @@
package cn.bootx.platform.daxpay.result.pay;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付单同步结果
* @author xxm
* @since 2023/12/27
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付单同步结果")
public class PaySyncResult extends PayCommonResult{
@Schema(description = "是否同步成功")
private boolean success;
/**
* @see PaySyncStatusEnum
*/
@Schema(description = "支付单的同步状态")
private String status;
}

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.daxpay.annotation.PaymentApi; import cn.bootx.platform.daxpay.annotation.PaymentApi;
import cn.bootx.platform.daxpay.core.payment.pay.service.PayService; import cn.bootx.platform.daxpay.core.payment.pay.service.PayService;
import cn.bootx.platform.daxpay.core.payment.refund.service.PayRefundService; import cn.bootx.platform.daxpay.core.payment.refund.service.PayRefundService;
import cn.bootx.platform.daxpay.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.param.pay.*; import cn.bootx.platform.daxpay.param.pay.*;
import cn.bootx.platform.daxpay.result.DaxResult; import cn.bootx.platform.daxpay.result.DaxResult;
import cn.bootx.platform.daxpay.result.pay.PayResult; import cn.bootx.platform.daxpay.result.pay.PayResult;
@@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
public class UniPayController { public class UniPayController {
private final PayService payService; private final PayService payService;
private final PayRefundService payRefundService; private final PayRefundService payRefundService;
private final PaySyncService paySyncService;
@PaymentApi("pay") @PaymentApi("pay")
@Operation(summary = "统一下单") @Operation(summary = "统一下单")
@@ -70,14 +72,14 @@ public class UniPayController {
@Operation(summary = "简单退款") @Operation(summary = "简单退款")
@PostMapping("/simpleRefund") @PostMapping("/simpleRefund")
public DaxResult<RefundResult> simpleRefund(@RequestBody SimpleRefundParam param){ public DaxResult<RefundResult> simpleRefund(@RequestBody SimpleRefundParam param){
return DaxRes.ok(); return DaxRes.ok(payRefundService.simpleRefund(param));
} }
@PaymentApi("syncPay") @PaymentApi("syncPay")
@Operation(summary = "支付状态同步") @Operation(summary = "支付状态同步")
@PostMapping("/syncPay") @PostMapping("/syncPay")
public DaxResult<Void> syncPay(){ public DaxResult<Void> syncPay(PaySyncParam param){
return DaxRes.ok(); return DaxRes.ok(paySyncService.sync(param));
} }
@PaymentApi("syncRefund") @PaymentApi("syncRefund")

View File

@@ -77,13 +77,7 @@
<!-- 支付核心包--> <!-- 支付核心包-->
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-core</artifactId> <artifactId>daxpay-single-core</artifactId>
<version>${dax.version}</version>
</dependency>
<!-- 支付公共的资源包 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-common</artifactId>
<version>${dax.version}</version> <version>${dax.version}</version>
</dependency> </dependency>

View File

@@ -1,32 +0,0 @@
package cn.bootx.platform.daxpay.code;
/**
* 支付网关同步状态
*
* @author xxm
* @since 2021/4/21
*/
public interface PaySyncStatus {
/** 不需要同步 */
String NOT_SYNC = "not_sync";
/** 远程支付成功 */
String TRADE_SUCCESS = "trade_success";
/** 交易创建,等待买家付款 */
String WAIT_BUYER_PAY = "wait_buyer_pay";
/** 已关闭 */
String TRADE_CLOSED = "trade_closed";
/** 已退款 */
String TRADE_REFUND = "trade_refund";
/** 查询不到订单 */
String NOT_FOUND = "not_found";
/** 查询失败 */
String FAIL = "fail";
}

View File

@@ -1,5 +1,6 @@
package cn.bootx.platform.daxpay.common.context; package cn.bootx.platform.daxpay.common.context;
import cn.bootx.platform.daxpay.code.PayWayEnum;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@@ -14,12 +15,21 @@ import java.time.LocalDateTime;
@Accessors(chain = true) @Accessors(chain = true)
public class AsyncPayLocal { public class AsyncPayLocal {
/** 异步支付方式 */
private PayWayEnum payWay;
/**
* 第三方支付平台订单号
* 1. 如付款码支付直接成功时会出现
* 2. 回调或者支付同步时也会有这个值
*/
private String tradeNo;
/** 支付参数体(通常用于发起支付的参数) */ /** 支付参数体(通常用于发起支付的参数) */
private String payBody; private String payBody;
/** 第三方支付平台订单号(如付款码支付直接成功时会出现) */
private String tradeNo;
/** 订单失效时间, 优先用这个 */ /** 订单失效时间, 优先用这个 */
private LocalDateTime expiredTime; private LocalDateTime expiredTime;

View File

@@ -21,8 +21,8 @@ public class PaymentContext {
/** 异步支付相关信息 */ /** 异步支付相关信息 */
private final AsyncPayLocal asyncPayInfo = new AsyncPayLocal(); private final AsyncPayLocal asyncPayInfo = new AsyncPayLocal();
/** 退款相关信息 */ /** 异步退款相关信息 */
private final AsyncRefundLocal refundInfo = new AsyncRefundLocal(); private final AsyncRefundLocal asyncRefundInfo = new AsyncRefundLocal();
/** 消息通知相关信息 */ /** 消息通知相关信息 */
private final NoticeLocal noticeInfo = new NoticeLocal(); private final NoticeLocal noticeInfo = new NoticeLocal();

View File

@@ -24,6 +24,9 @@ public class AliPayOrder extends BasePayOrder implements EntityBaseFunction<AliP
/** 支付宝交易号 */ /** 支付宝交易号 */
private String tradeNo; private String tradeNo;
/** 支付方式 */
private String payWay;
@Override @Override
public AliPaymentDto toDto() { public AliPaymentDto toDto() {
AliPaymentDto dto = new AliPaymentDto(); AliPaymentDto dto = new AliPaymentDto();

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.common.context.AsyncPayLocal; import cn.bootx.platform.daxpay.common.entity.OrderRefundableInfo;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.alipay.dao.AliPayOrderManager; import cn.bootx.platform.daxpay.core.channel.alipay.dao.AliPayOrderManager;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AliPayOrder; import cn.bootx.platform.daxpay.core.channel.alipay.entity.AliPayOrder;
@@ -10,8 +10,6 @@ import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager; import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.common.entity.OrderRefundableInfo;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayWayParam; import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -46,8 +44,7 @@ public class AliPayOrderService {
* 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建支付宝支付单 * 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建支付宝支付单
*/ */
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) { public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo(); payOrder.setAsyncPay(true)
payOrder.setAsyncPayMode(true)
.setAsyncPayChannel(PayChannelEnum.ALI.getCode()); .setAsyncPayChannel(PayChannelEnum.ALI.getCode());
// 更新支付宝异步支付类型信息 // 更新支付宝异步支付类型信息
@@ -80,29 +77,31 @@ public class AliPayOrderService {
payOrder.setRefundableInfos(refundableInfos); payOrder.setRefundableInfos(refundableInfos);
// 如果支付完成(付款码情况) 调用 updateSyncSuccess 创建支付宝支付记录 // 如果支付完成(付款码情况) 调用 updateSyncSuccess 创建支付宝支付记录
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) { if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
this.createAliPayment(payOrder, payWayParam, asyncPayInfo.getTradeNo()); this.updateAsyncSuccess(payOrder, payWayParam.getAmount());
} }
} }
/** /**
* 更新异步支付记录成功状态, 并创建支付宝支付记录 * 更新异步支付记录成功状态, 并创建支付宝支付记录
*/ */
public void updateAsyncSuccess(Long id, PayWayParam payWayParam, String tradeNo) { public void updateAsyncSuccess(PayOrder payOrder, Integer amount) {
// 更新支付记录 // 创建支付宝支付订单
PayOrder payOrder = payOrderManager.findById(id).orElseThrow(() -> new PayFailureException("支付记录不存在")); this.createAliPayOrder(payOrder,amount);
this.createAliPayment(payOrder,payWayParam,tradeNo);
} }
/** /**
* 创建支付宝支付记录(支付调起成功后才会创建) * 创建支付宝支付订单(支付成功后才会创建)
*/ */
private void createAliPayment(PayOrder payOrder, PayWayParam payWayParam, String tradeNo) { private void createAliPayOrder(PayOrder payOrder, Integer amount) {
// 创建支付宝支付记录 String tradeNo = PaymentContextLocal.get()
.getAsyncPayInfo()
.getTradeNo();
AliPayOrder aliPayOrder = new AliPayOrder(); AliPayOrder aliPayOrder = new AliPayOrder();
aliPayOrder.setTradeNo(tradeNo) aliPayOrder.setTradeNo(tradeNo)
.setPaymentId(payOrder.getId()) .setPaymentId(payOrder.getId())
.setAmount(payWayParam.getAmount()) .setAmount(amount)
.setRefundableBalance(payWayParam.getAmount()) .setRefundableBalance(amount)
.setBusinessNo(payOrder.getBusinessNo()) .setBusinessNo(payOrder.getBusinessNo())
.setStatus(PayStatusEnum.SUCCESS.getCode()) .setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(LocalDateTime.now()); .setPayTime(LocalDateTime.now());

View File

@@ -37,7 +37,7 @@ public class AliPayRefundService {
refundModel.setRefundAmount(refundAmount); refundModel.setRefundAmount(refundAmount);
// 设置退款信息 // 设置退款信息
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr()); refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr());
refundModel.setOutRequestNo(refundInfo.getRefundNo()); refundModel.setOutRequestNo(refundInfo.getRefundNo());
try { try {

View File

@@ -1,8 +1,8 @@
package cn.bootx.platform.daxpay.core.channel.alipay.service; package cn.bootx.platform.daxpay.core.channel.alipay.service;
import cn.bootx.platform.daxpay.code.AliPayCode; import cn.bootx.platform.daxpay.code.AliPayCode;
import cn.bootx.platform.daxpay.code.PaySyncStatus; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
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.AlipayTradeQueryModel; import com.alipay.api.domain.AlipayTradeQueryModel;
@@ -29,8 +29,8 @@ public class AlipaySyncService {
/** /**
* 与支付宝网关同步状态 1 远程支付成功 2 交易创建,等待买家付款 3 超时关闭 4 查询不到 5 查询失败 * 与支付宝网关同步状态 1 远程支付成功 2 交易创建,等待买家付款 3 超时关闭 4 查询不到 5 查询失败
*/ */
public PaySyncResult syncPayStatus(Long paymentId) { public SyncResult syncPayStatus(Long paymentId) {
PaySyncResult paySyncResult = new PaySyncResult().setPaySyncStatus(PaySyncStatus.FAIL); SyncResult syncResult = new SyncResult().setSyncStatus(PaySyncStatusEnum.FAIL.getCode());
// 查询 // 查询
try { try {
@@ -39,35 +39,35 @@ public class AlipaySyncService {
// 查询退款参数 // 查询退款参数
AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel); AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel);
String tradeStatus = response.getTradeStatus(); String tradeStatus = response.getTradeStatus();
paySyncResult.setJson(JSONUtil.toJsonStr(response)); syncResult.setJson(JSONUtil.toJsonStr(response));
// 支付完成 // 支付完成
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_SUCCESS) if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_SUCCESS)
|| Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_FINISHED)) { || Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_FINISHED)) {
HashMap<String, String> map = new HashMap<>(1); HashMap<String, String> map = new HashMap<>(1);
map.put(AliPayCode.TRADE_NO, response.getTradeNo()); map.put(AliPayCode.TRADE_NO, response.getTradeNo());
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_SUCCESS).setMap(map); return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS.getCode()).setMap(map);
} }
// 待支付 // 待支付
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_WAIT_BUYER_PAY)) { if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_WAIT_BUYER_PAY)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.WAIT_BUYER_PAY); return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT.getCode());
} }
// 已关闭 // 已关闭
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_CLOSED)) { if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_CLOSED)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_CLOSED); return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED.getCode());
} }
// 未找到 // 未找到
if (Objects.equals(response.getSubCode(), AliPayCode.ACQ_TRADE_NOT_EXIST)) { if (Objects.equals(response.getSubCode(), AliPayCode.ACQ_TRADE_NOT_EXIST)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.NOT_FOUND); return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND.getCode());
} }
// 退款 支付宝查不到 // 退款 支付宝查不到
} }
catch (AlipayApiException e) { catch (AlipayApiException e) {
log.error("查询订单失败:", e); log.error("查询订单失败:", e);
paySyncResult.setMsg(e.getErrMsg()); syncResult.setMsg(e.getErrMsg());
} }
return paySyncResult; return syncResult;
} }
} }

View File

@@ -62,7 +62,7 @@ public class WeChatPayCloseService {
errorMsg = result.get(WeChatPayCode.RETURN_MSG); errorMsg = result.get(WeChatPayCode.RETURN_MSG);
} }
log.error("订单关闭失败 {}", errorMsg); log.error("订单关闭失败 {}", errorMsg);
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setErrorMsg(errorMsg); refundInfo.setErrorMsg(errorMsg);
refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode)); refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode));
throw new PayFailureException(errorMsg); throw new PayFailureException(errorMsg);

View File

@@ -42,7 +42,7 @@ public class WeChatPayOrderService {
*/ */
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) { public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();; AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();;
payOrder.setAsyncPayMode(true).setAsyncPayChannel(PayChannelEnum.WECHAT.getCode()); payOrder.setAsyncPay(true).setAsyncPayChannel(PayChannelEnum.WECHAT.getCode());
List<PayOrderChannel> payTypeInfos = new ArrayList<>(); List<PayOrderChannel> payTypeInfos = new ArrayList<>();
List<OrderRefundableInfo> refundableInfos = new ArrayList<>(); List<OrderRefundableInfo> refundableInfos = new ArrayList<>();

View File

@@ -12,7 +12,7 @@ import cn.bootx.platform.daxpay.common.context.AsyncPayLocal;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.core.payment.sync.service.PaySyncService; import cn.bootx.platform.daxpay.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayParam; import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayParam;
@@ -40,7 +40,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static cn.bootx.platform.daxpay.code.PaySyncStatus.WAIT_BUYER_PAY; import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.PAY_WAIT;
import static com.ijpay.wxpay.model.UnifiedOrderModel.UnifiedOrderModelBuilder; import static com.ijpay.wxpay.model.UnifiedOrderModel.UnifiedOrderModelBuilder;
import static com.ijpay.wxpay.model.UnifiedOrderModel.builder; import static com.ijpay.wxpay.model.UnifiedOrderModel.builder;
@@ -272,14 +272,14 @@ public class WeChatPayService {
*/ */
@Async("bigExecutor") @Async("bigExecutor")
@Retryable(value = RetryableException.class, maxAttempts = 10, backoff = @Backoff(value = 5000L)) @Retryable(value = RetryableException.class, maxAttempts = 10, backoff = @Backoff(value = 5000L))
public void rotationSync(PayOrder payment, WeChatPayConfig weChatPayConfig) { public void rotationSync(PayOrder payOrder, WeChatPayConfig weChatPayConfig) {
PaySyncResult paySyncResult = weChatPaySyncService.syncPayStatus(payment.getId(), weChatPayConfig); SyncResult syncResult = weChatPaySyncService.syncPayStatus(payOrder.getId(), weChatPayConfig);
// 不为支付中状态后, 调用系统同步更新状态, 支付状态则继续重试 // 不为支付中状态后, 调用系统同步更新状态, 支付状态则继续重试
if (Objects.equals(WAIT_BUYER_PAY, paySyncResult.getPaySyncStatus())) { if (Objects.equals(PAY_WAIT.getCode(), syncResult.getSyncStatus())) {
throw new RetryableException(); throw new RetryableException();
} }
else { else {
paySyncService.sync(payment); paySyncService.syncPayOrder(payOrder);
} }
} }

View File

@@ -1,9 +1,9 @@
package cn.bootx.platform.daxpay.core.channel.wechat.service; package cn.bootx.platform.daxpay.core.channel.wechat.service;
import cn.bootx.platform.daxpay.code.PaySyncStatus; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.code.WeChatPayCode; import cn.bootx.platform.daxpay.code.WeChatPayCode;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.ijpay.core.enums.SignType; import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit; import com.ijpay.core.kit.WxPayKit;
@@ -30,8 +30,8 @@ public class WeChatPaySyncService {
/** /**
* 同步查询 * 同步查询
*/ */
public PaySyncResult syncPayStatus(Long paymentId, WeChatPayConfig weChatPayConfig) { public SyncResult syncPayStatus(Long paymentId, WeChatPayConfig weChatPayConfig) {
PaySyncResult paySyncResult = new PaySyncResult().setPaySyncStatus(PaySyncStatus.FAIL); SyncResult syncResult = new SyncResult().setSyncStatus(PaySyncStatusEnum.FAIL.getCode());
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())
@@ -42,47 +42,47 @@ public class WeChatPaySyncService {
try { try {
String xmlResult = WxPayApi.orderQuery(params); String xmlResult = WxPayApi.orderQuery(params);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult); Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
paySyncResult.setJson(JSONUtil.toJsonStr(result)); syncResult.setJson(JSONUtil.toJsonStr(result));
// 查询失败 // 查询失败
if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RETURN_CODE))) { if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RETURN_CODE))) {
log.warn("查询微信订单失败:{}", result); log.warn("查询微信订单失败:{}", result);
return paySyncResult; return syncResult;
} }
// 未查到订单 // 未查到订单
if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RESULT_CODE))) { if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RESULT_CODE))) {
log.warn("疑似未查询到订单:{}", result); log.warn("疑似未查询到订单:{}", result);
return paySyncResult.setPaySyncStatus(PaySyncStatus.NOT_FOUND); return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND.getCode());
} }
String tradeStatus = result.get(WeChatPayCode.TRADE_STATE); String tradeStatus = result.get(WeChatPayCode.TRADE_STATE);
// 支付完成 // 支付完成
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_SUCCESS) if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_SUCCESS)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_ACCEPT)) { || Objects.equals(tradeStatus, WeChatPayCode.TRADE_ACCEPT)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_SUCCESS).setMap(result); return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS.getCode()).setMap(result);
} }
// 待支付 // 待支付
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_NOTPAY) if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_NOTPAY)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_USERPAYING)) { || Objects.equals(tradeStatus, WeChatPayCode.TRADE_USERPAYING)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.WAIT_BUYER_PAY); return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT.getCode());
} }
// 已退款/退款中 // 已退款/退款中
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_REFUND)) { if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_REFUND)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_REFUND); return syncResult.setSyncStatus(PaySyncStatusEnum.REFUND.getCode());
} }
// 已关闭 // 已关闭
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_CLOSED) if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_CLOSED)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_REVOKED) || Objects.equals(tradeStatus, WeChatPayCode.TRADE_REVOKED)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_PAYERROR)) { || Objects.equals(tradeStatus, WeChatPayCode.TRADE_PAYERROR)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_CLOSED); return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED.getCode());
} }
} }
catch (RuntimeException e) { catch (RuntimeException e) {
log.error("查询订单失败:", e); log.error("查询订单失败:", e);
paySyncResult.setMsg(e.getMessage()); syncResult.setMsg(e.getMessage());
} }
return paySyncResult; return syncResult;
} }
} }

View File

@@ -47,7 +47,7 @@ public class WechatRefundService {
String refundFee = String.valueOf(amount); String refundFee = String.valueOf(amount);
String totalFee = refundableInfo.getAmount().toString(); String totalFee = refundableInfo.getAmount().toString();
// 设置退款信息 // 设置退款信息
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr()); refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr());
Map<String, String> params = RefundModel.builder() Map<String, String> params = RefundModel.builder()
.appid(weChatPayConfig.getWxAppId()) .appid(weChatPayConfig.getWxAppId())
@@ -86,7 +86,7 @@ public class WechatRefundService {
errorMsg = result.get(WeChatPayCode.RETURN_MSG); errorMsg = result.get(WeChatPayCode.RETURN_MSG);
} }
log.error("订单退款失败 {}", errorMsg); log.error("订单退款失败 {}", errorMsg);
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo(); AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setErrorMsg(errorMsg); refundInfo.setErrorMsg(errorMsg);
refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode)); refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode));
throw new PayFailureException(errorMsg); throw new PayFailureException(errorMsg);

View File

@@ -56,8 +56,8 @@ public class PaymentBuilder {
.setRefundableInfos(refundableInfos) .setRefundableInfos(refundableInfos)
.setStatus(PayStatusEnum.PROGRESS.getCode()) .setStatus(PayStatusEnum.PROGRESS.getCode())
.setAmount(sumAmount) .setAmount(sumAmount)
.setCombinationPayMode(payParam.getPayWays().size() > 1) .setCombinationPay(payParam.getPayWays().size() > 1)
.setAsyncPayMode(asyncPayMode.isPresent()) .setAsyncPay(asyncPayMode.isPresent())
.setAsyncPayChannel(asyncPayMode.orElse(null)) .setAsyncPayChannel(asyncPayMode.orElse(null))
.setRefundableBalance(sumAmount); .setRefundableBalance(sumAmount);
} }
@@ -123,7 +123,7 @@ public class PaymentBuilder {
PayResult paymentResult; PayResult paymentResult;
paymentResult = new PayResult(); paymentResult = new PayResult();
paymentResult.setPaymentId(payOrder.getId()); paymentResult.setPaymentId(payOrder.getId());
paymentResult.setAsyncPayMode(payOrder.isAsyncPayMode()); paymentResult.setAsyncPayMode(payOrder.isAsyncPay());
paymentResult.setAsyncPayChannel(payOrder.getAsyncPayChannel()); paymentResult.setAsyncPayChannel(payOrder.getAsyncPayChannel());
paymentResult.setStatus(payOrder.getStatus()); paymentResult.setStatus(payOrder.getStatus());

View File

@@ -42,11 +42,11 @@ public class PayOrder extends MpBaseEntity {
/** 是否是异步支付 */ /** 是否是异步支付 */
@DbColumn(comment = "是否是异步支付") @DbColumn(comment = "是否是异步支付")
private boolean asyncPayMode; private boolean asyncPay;
/** 是否是组合支付 */ /** 是否是组合支付 */
@DbColumn(comment = "是否是组合支付") @DbColumn(comment = "是否是组合支付")
private boolean combinationPayMode; private boolean combinationPay;
/** /**
* 异步支付渠道 * 异步支付渠道

View File

@@ -49,6 +49,9 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<P
/** 退款终端ip */ /** 退款终端ip */
private String clientIp; private String clientIp;
/** 退款原因 */
private String reason;
/** 退款时间 */ /** 退款时间 */
private LocalDateTime refundTime; private LocalDateTime refundTime;

View File

@@ -0,0 +1,19 @@
package cn.bootx.platform.daxpay.core.order.sync.convert;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 支付同步记录同步
* @author xxm
* @since 2023/7/14
*/
@Mapper
public interface PaySyncOrderConvert {
PaySyncOrderConvert CONVERT = Mappers.getMapper(PaySyncOrderConvert.class);
PaySyncOrderDto convert(PaySyncOrder in);
}

View File

@@ -0,0 +1,35 @@
package cn.bootx.platform.daxpay.core.order.sync.dao;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Objects;
/**
*
* @author xxm
* @since 2023/7/14
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PaySyncOrderManager extends BaseManager<PaySyncOrderMapper, PaySyncOrder> {
public Page<PaySyncOrder> page(PageParam pageParam, PaySyncOrderDto param) {
Page<PaySyncOrder> mpPage = MpUtil.getMpPage(pageParam, PaySyncOrder.class);
return lambdaQuery().orderByDesc(MpIdEntity::getId)
.like(Objects.nonNull(param.getPaymentId()), PaySyncOrder::getPaymentId, param.getPaymentId())
.eq(Objects.nonNull(param.getChannel()), PaySyncOrder::getChannel, param.getChannel())
.eq(Objects.nonNull(param.getStatus()), PaySyncOrder::getStatus, param.getStatus())
.page(mpPage);
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.core.order.sync.dao;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@Mapper
public interface PaySyncOrderMapper extends BaseMapper<PaySyncOrder> {
}

View File

@@ -0,0 +1,75 @@
package cn.bootx.platform.daxpay.core.order.sync.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.sync.convert.PaySyncOrderConvert;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import cn.bootx.table.modify.annotation.DbComment;
import cn.bootx.table.modify.annotation.DbTable;
import cn.bootx.table.modify.mysql.annotation.DbMySqlFieldType;
import cn.bootx.table.modify.mysql.constants.MySqlFieldTypeEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付同步订单
* @author xxm
* @since 2023/7/14
*/
@EqualsAndHashCode(callSuper = true)
@Data
@DbTable(comment = "支付同步订单")
@Accessors(chain = true)
@TableName("pay_sync_order")
public class PaySyncOrder extends MpCreateEntity implements EntityBaseFunction<PaySyncOrderDto> {
/** 支付记录id */
@DbComment("支付记录id")
private Long paymentId;
/**
* 支付渠道
* @see PayChannelEnum#getCode()
*/
@DbComment("支付渠道")
private String channel;
/** 通知消息 */
@DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT)
@DbComment("通知消息")
private String syncInfo;
/**
* 同步状态
* @see PaySyncStatusEnum
*/
@DbComment("同步状态")
private String status;
/**
* 支付单如果状态不一致, 是否修复成功
*/
private boolean repairOrder;
@DbComment("错误消息")
private String msg;
/** 同步时间 */
@DbComment("同步时间")
private LocalDateTime syncTime;
/**
* 转换
*/
@Override
public PaySyncOrderDto toDto() {
return PaySyncOrderConvert.CONVERT.convert(this);
}
}

View File

@@ -0,0 +1,61 @@
package cn.bootx.platform.daxpay.core.order.sync.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.sync.dao.PaySyncOrderManager;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.time.LocalDateTime;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PaySyncOrderService {
private final PaySyncOrderManager orderManager;
/**
* 记录同步记录
*/
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void saveRecord(SyncResult paySyncResult, PayOrder payment){
PaySyncOrder paySyncOrder = new PaySyncOrder()
.setPaymentId(payment.getId())
.setChannel(payment.getAsyncPayChannel())
.setSyncInfo(paySyncResult.getJson())
.setStatus(paySyncResult.getSyncStatus())
.setMsg(paySyncResult.getMsg())
.setSyncTime(LocalDateTime.now());
orderManager.save(paySyncOrder);
}
/**
* 分页查询
*/
public PageResult<PaySyncOrderDto> page(PageParam pageParam, PaySyncOrderDto param) {
Page<PaySyncOrder> page = orderManager.page(pageParam, param);
return MpUtil.convert2DtoPageResult(page);
}
/**
* 根据id查询
*/
public PaySyncOrderDto findById(Long id) {
return orderManager.findById(id).map(PaySyncOrder::toDto).orElseThrow(DataNotExistException::new);
}
}

View File

@@ -75,7 +75,7 @@ public class PayService {
payAssistService.initPayContext(payOrder, payParam); payAssistService.initPayContext(payOrder, payParam);
// 异步支付且非第一次支付 // 异步支付且非第一次支付
if (Objects.nonNull(payOrder) && payOrder.isAsyncPayMode()) { if (Objects.nonNull(payOrder) && payOrder.isAsyncPay()) {
return this.paySyncNotFirst(payParam, payOrder); return this.paySyncNotFirst(payParam, payOrder);
} else { } else {
// 第一次发起支付或同步支付 // 第一次发起支付或同步支付

View File

@@ -3,6 +3,7 @@ package cn.bootx.platform.daxpay.core.payment.pay.strategy;
import cn.bootx.platform.daxpay.code.AliPayCode; import cn.bootx.platform.daxpay.code.AliPayCode;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.common.exception.ExceptionInfo; import cn.bootx.platform.daxpay.common.exception.ExceptionInfo;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig;
import cn.bootx.platform.daxpay.core.channel.alipay.service.*; import cn.bootx.platform.daxpay.core.channel.alipay.service.*;
import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException; import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException;
@@ -106,8 +107,8 @@ public class AliPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doAsyncSuccessHandler(Map<String, String> map) { public void doAsyncSuccessHandler(Map<String, String> map) {
String tradeNo = map.get(AliPayCode.TRADE_NO); PaymentContextLocal.get().getAsyncPayInfo().setTradeNo( map.get(AliPayCode.TRADE_NO));
aliPaymentService.updateAsyncSuccess(this.getOrder().getId(), this.getPayWayParam(), tradeNo); aliPaymentService.updateAsyncSuccess(this.getOrder(), this.getPayWayParam().getAmount());
} }
/** /**

View File

@@ -11,8 +11,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/** /**
@@ -53,7 +51,7 @@ public class VoucherPayStrategy extends AbsPayStrategy {
@Override @Override
public void doPayHandler() { public void doPayHandler() {
VoucherRecord voucherRecord; VoucherRecord voucherRecord;
if (this.getOrder().isAsyncPayMode()){ if (this.getOrder().isAsyncPay()){
voucherRecord = voucherPayService.freezeBalance(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers); voucherRecord = voucherPayService.freezeBalance(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers);
} else { } else {
voucherRecord = voucherPayService.pay(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers); voucherRecord = voucherPayService.pay(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers);
@@ -66,7 +64,7 @@ public class VoucherPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doSuccessHandler() { public void doSuccessHandler() {
if (this.getOrder().isAsyncPayMode()){ if (this.getOrder().isAsyncPay()){
voucherPayService.paySuccess(this.getOrder().getId()); voucherPayService.paySuccess(this.getOrder().getId());
} }
voucherPaymentService.updateSuccess(this.getOrder().getId()); voucherPaymentService.updateSuccess(this.getOrder().getId());

View File

@@ -82,7 +82,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
@Override @Override
public void doPayHandler() { public void doPayHandler() {
// 异步支付方式时使用冻结方式 // 异步支付方式时使用冻结方式
if (this.getOrder().isAsyncPayMode()){ if (this.getOrder().isAsyncPay()){
walletPayService.freezeBalance(getPayWayParam().getAmount(), this.getOrder(), this.wallet); walletPayService.freezeBalance(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
} else { } else {
walletPayService.pay(getPayWayParam().getAmount(), this.getOrder(), this.wallet); walletPayService.pay(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
@@ -95,7 +95,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doSuccessHandler() { public void doSuccessHandler() {
if (this.getOrder().isAsyncPayMode()){ if (this.getOrder().isAsyncPay()){
walletPayService.paySuccess(this.getOrder().getId()); walletPayService.paySuccess(this.getOrder().getId());
} }
walletPaymentService.updateSuccess(this.getOrder().getId()); walletPaymentService.updateSuccess(this.getOrder().getId());

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.core.payment.refund.factory; package cn.bootx.platform.daxpay.core.payment.refund.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.core.payment.refund.strategy.*; import cn.bootx.platform.daxpay.core.payment.refund.strategy.*;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam; import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;

View File

@@ -1,17 +0,0 @@
package cn.bootx.platform.daxpay.core.payment.refund.func;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import java.util.List;
/**
* 支付退款策略接口
* @author xxm
* @since 2023/7/5
*/
@FunctionalInterface
public interface PayRefundStrategyConsumer<T extends List<AbsPayRefundStrategy>, S extends PayOrder> {
void accept(T t, S s);
}

View File

@@ -2,19 +2,27 @@ package cn.bootx.platform.daxpay.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.PayRefundStatusEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.common.context.AsyncRefundLocal;
import cn.bootx.platform.daxpay.common.context.NoticeLocal; import cn.bootx.platform.daxpay.common.context.NoticeLocal;
import cn.bootx.platform.daxpay.common.context.PlatformLocal; import cn.bootx.platform.daxpay.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal; import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager; import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.refund.dao.PayRefundOrderManager;
import cn.bootx.platform.daxpay.core.order.refund.entity.PayRefundOrder;
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.RefundParam; import cn.bootx.platform.daxpay.param.pay.RefundParam;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
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;
@@ -30,14 +38,14 @@ import java.util.Objects;
public class PayRefundAssistService { public class PayRefundAssistService {
private final PayOrderManager payOrderManager; private final PayOrderManager payOrderManager;
private final PayRefundOrderManager payRefundOrderManager;
/** /**
* 初始化上下文 * 初始化上下文
*/ */
public void initRefundContext(RefundParam param, PayOrder payOrder){ public void initRefundContext(RefundParam param){
// 初始化通知相关上下文 // 初始化通知相关上下文
this.initNotice(param); this.initNotice(param);
// 初始化请求相关上下文
this.initRequest(param);
} }
/** /**
@@ -55,10 +63,6 @@ public class PayRefundAssistService {
} }
} }
public void initRequest(RefundParam param){
}
/** /**
* 根据退款参数获取支付订单, 并进行检查 * 根据退款参数获取支付订单, 并进行检查
*/ */
@@ -84,7 +88,7 @@ public class PayRefundAssistService {
} }
// 简单退款校验 // 简单退款校验
if (payOrder.isCombinationPayMode() != simple){ if (payOrder.isCombinationPay() != simple){
throw new PayFailureException("组合支付不可以使用简单退款方式"); throw new PayFailureException("组合支付不可以使用简单退款方式");
} }
// 状态判断, 支付中/失败/取消等不能进行退款 // 状态判断, 支付中/失败/取消等不能进行退款
@@ -99,4 +103,31 @@ public class PayRefundAssistService {
} }
return payOrder; return payOrder;
} }
/**
* 保存退款记录 成不成功都记录
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveRefundOrder(RefundParam refundParam, PayOrder payOrder){
AsyncRefundLocal asyncRefundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
// 退款金额
Integer amount = refundParam.getRefundChannels()
.stream()
.map(RefundChannelParam::getAmount)
.reduce(0, Integer::sum);
PayRefundOrder refundOrder = new PayRefundOrder()
.setRefundRequestNo(asyncRefundInfo.getRefundNo())
.setAmount(amount)
.setClientIp(refundParam.getClientIp())
.setPaymentId(payOrder.getId())
.setBusinessNo(payOrder.getBusinessNo())
.setRefundTime(LocalDateTime.now())
.setTitle(payOrder.getTitle())
.setErrorMsg(asyncRefundInfo.getErrorMsg())
.setErrorCode(asyncRefundInfo.getErrorCode())
.setStatus(Objects.isNull(asyncRefundInfo.getErrorCode()) ? PayRefundStatusEnum.SUCCESS.getCode()
: PayRefundStatusEnum.FAIL.getCode());
payRefundOrderManager.save(refundOrder);
}
} }

View File

@@ -5,8 +5,7 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager; import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.refund.factory.PayRefundStrategyFactory; import cn.bootx.platform.daxpay.core.payment.refund.factory.PayRefundStrategyFactory;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.core.payment.refund.func.PayRefundStrategyConsumer;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam; import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
import cn.bootx.platform.daxpay.param.pay.RefundParam; import cn.bootx.platform.daxpay.param.pay.RefundParam;
@@ -21,8 +20,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -73,7 +70,7 @@ public class PayRefundService {
ValidationUtil.validateParam(param); ValidationUtil.validateParam(param);
PayOrder payOrder = payRefundAssistService.getPayOrderAndCheckByRefundParam(param, simple); PayOrder payOrder = payRefundAssistService.getPayOrderAndCheckByRefundParam(param, simple);
// 退款上下文初始化 // 退款上下文初始化
payRefundAssistService.initRefundContext(param,payOrder); payRefundAssistService.initRefundContext(param);
// 是否全部退款 // 是否全部退款
if (param.isRefundAll()){ if (param.isRefundAll()){
// 全部退款根据支付订单的退款信息构造退款参数 // 全部退款根据支付订单的退款信息构造退款参数
@@ -90,67 +87,42 @@ public class PayRefundService {
/** /**
* 分支付通道进行退款 * 分支付通道进行退款
*/ */
public RefundResult refundByChannel(RefundParam param,PayOrder order){ public RefundResult refundByChannel(RefundParam refundParam, PayOrder payOrder){
List<RefundChannelParam> refundChannels = param.getRefundChannels(); List<RefundChannelParam> refundChannels = refundParam.getRefundChannels();
// 1.获取退款参数方式,通过工厂生成对应的策略组 // 1.获取退款参数方式,通过工厂生成对应的策略组
List<AbsPayRefundStrategy> payRefundStrategies = PayRefundStrategyFactory.create(refundChannels); List<AbsPayRefundStrategy> payRefundStrategies = PayRefundStrategyFactory.create(refundChannels);
if (CollectionUtil.isEmpty(payRefundStrategies)) { if (CollectionUtil.isEmpty(payRefundStrategies)) {
throw new PayUnsupportedMethodException(); throw new PayUnsupportedMethodException();
} }
// 2.初始化支付的参数 // 2.初始化退款策略的参数
for (AbsPayRefundStrategy refundStrategy : payRefundStrategies) { payRefundStrategies.forEach(refundStrategy -> refundStrategy.initPayParam(payOrder, refundParam));
refundStrategy.initPayParam(order, param);
}
// 3.支付前准备
this.doHandler(refundChannels, order, payRefundStrategies, AbsPayRefundStrategy::doBeforeRefundHandler, null);
// 4.执行退款
this.doHandler(refundChannels,order, payRefundStrategies, AbsPayRefundStrategy::doRefundHandler, (strategyList, payOrder) -> {
this.paymentHandler(payOrder, refundChannels);
});
return new RefundResult();
}
/**
* 处理方法
* @param channelParams 退款方式参数
* @param payOrder 支付记录
* @param strategyList 退款策略
* @param refundStrategy 执行方法
* @param successCallback 成功操作
*/
private void doHandler( List<RefundChannelParam> channelParams,
PayOrder payOrder,
List<AbsPayRefundStrategy> strategyList,
Consumer<AbsPayRefundStrategy> refundStrategy,
PayRefundStrategyConsumer<List<AbsPayRefundStrategy>, PayOrder> successCallback) {
try { try {
// 执行策略操作,如退款前/退款时 // 3.退款前准备
// 等同strategyList.forEach(payMethod.accept(PaymentStrategy)) payRefundStrategies.forEach(AbsPayRefundStrategy::doBeforeRefundHandler);
strategyList.forEach(refundStrategy);
// 执行操作成功的处 // 4.执行退款策略
Optional.ofNullable(successCallback).ifPresent(fun -> fun.accept(strategyList, payOrder)); payRefundStrategies.forEach(AbsPayRefundStrategy::doRefundHandler);
} }
catch (Exception e) { catch (Exception e) {
// 记录退款失败的记录 // 记录退款失败的记录
Integer i = channelParams.stream() payRefundAssistService.saveRefundOrder(refundParam,payOrder);
.map(RefundChannelParam::getAmount)
.reduce(0,Integer::sum);
// TODO 保存
// SpringUtil.getBean(this.getClass()).saveRefund(payOrder, amount, channelParams);
throw e; throw e;
} }
// 5.支付成功后处理
this.paymentHandler(refundParam, payOrder);
// 返回结果
return new RefundResult();
} }
/** /**
* 支付订单处理 * 支付订单处理
*/ */
private void paymentHandler(PayOrder payOrder, List<RefundChannelParam> refundModeParams) { private void paymentHandler(RefundParam refundParam,PayOrder payOrder) {
Integer amount = refundModeParams.stream() Integer amount = refundParam.getRefundChannels().stream()
.map(RefundChannelParam::getAmount) .map(RefundChannelParam::getAmount)
.reduce(0, Integer::sum); .reduce(0, Integer::sum);
// 剩余可退款余额 // 剩余可退款余额
@@ -165,7 +137,6 @@ public class PayRefundService {
} }
payOrder.setRefundableBalance(refundableBalance); payOrder.setRefundableBalance(refundableBalance);
payOrderManager.updateById(payOrder); payOrderManager.updateById(payOrder);
// TODO 记录退款成功的记录 payRefundAssistService.saveRefundOrder(refundParam,payOrder);
// SpringUtil.getBean(this.getClass()).saveRefund(payOrder, amount, refundModeParams);
} }
} }

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayRefundService; import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayRefundService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService; import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.core.payment.refund.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.cash.service.CashService; import cn.bootx.platform.daxpay.core.channel.cash.service.CashService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.core.payment.refund.strategy; package cn.bootx.platform.daxpay.core.payment.refund.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPayService; import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPayService;
import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPaymentService; import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPaymentService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPayService; import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPayService;
import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPaymentService; import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPaymentService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayConfigServi
import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayOrderService; import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayOrderService;
import cn.bootx.platform.daxpay.core.channel.wechat.service.WechatRefundService; import cn.bootx.platform.daxpay.core.channel.wechat.service.WechatRefundService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy; import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor; 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;

View File

@@ -0,0 +1,20 @@
package cn.bootx.platform.daxpay.core.payment.repair.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付订单修复参数
* @author xxm
* @since 2023/12/27
*/
@Data
@Accessors(chain = true)
public class PayOrderRepairParam {
@Schema(description = "支付ID")
private Long paymentId;
}

View File

@@ -1,16 +1,21 @@
package cn.bootx.platform.daxpay.core.payment.sync.service; package cn.bootx.platform.daxpay.core.payment.repair.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* 退款状态同步服务 * 支付修复服务
* @author xxm * @author xxm
* @since 2023/12/18 * @since 2023/12/27
*/ */
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class RefundSyncService { public class PayRepairService {
public void repair(){
}
} }

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.core.payment.repair.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.func.AbsPayRepairStrategy;
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 2023/12/27
*/
@Slf4j
@Scope(SCOPE_PROTOTYPE)
@Service
@RequiredArgsConstructor
public class AliPayRepairStrategy extends AbsPayRepairStrategy {
private final AliPayOrderService orderService;
@Override
public PayChannelEnum getType() {
return PayChannelEnum.ALI;
}
/**
* 支付成功处理
*/
@Override
public void successHandler() {
orderService.updateAsyncSuccess(this.getOrder(), 0);
}
/**
* 取消支付
*/
@Override
public void closeHandler() {
orderService.updateClose(this.getOrder().getId());
}
/**
* 退款
*/
@Override
public void refundHandler() {
orderService.updatePayRefund(this.getOrder().getId(), 0);
}
}

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.payment.sync.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy; import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.strategy.AliPaySyncStrategy; import cn.bootx.platform.daxpay.core.payment.sync.strategy.AliPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.strategy.WeChatPaySyncStrategy; import cn.bootx.platform.daxpay.core.payment.sync.strategy.WeChatPaySyncStrategy;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;

View File

@@ -1,28 +1,28 @@
package cn.bootx.platform.daxpay.core.payment.sync.result; package cn.bootx.platform.daxpay.core.payment.sync.result;
import cn.bootx.platform.daxpay.code.PaySyncStatus; import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.Map; import java.util.Map;
import static cn.bootx.platform.daxpay.code.PaySyncStatus.NOT_SYNC; import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.NOT_SYNC;
/** /**
* 支付网关通知状态对象 * 支付网关同步状态记录对象
* *
* @author xxm * @author xxm
* @since 2021/4/21 * @since 2021/4/21
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class PaySyncResult { public class SyncResult {
/** /**
* 支付网关同步状态 * 支付网关同步状态
* @see PaySyncStatus#NOT_SYNC * @see PaySyncStatusEnum#NOT_SYNC
*/ */
private String paySyncStatus = NOT_SYNC; private String syncStatus = NOT_SYNC.getCode();
/** 网关返回参数(会被用到的参数) */ /** 网关返回参数(会被用到的参数) */
private Map<String, String> map; private Map<String, String> map;

View File

@@ -1,10 +1,26 @@
package cn.bootx.platform.daxpay.core.payment.sync.service; package cn.bootx.platform.daxpay.core.payment.sync.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.sync.service.PaySyncOrderService;
import cn.bootx.platform.daxpay.core.payment.sync.factory.PaySyncStrategyFactory;
import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
/** /**
* 支付同步服务 * 支付同步服务
* @author xxm * @author xxm
@@ -14,8 +30,146 @@ import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class PaySyncService { public class PaySyncService {
private final PayOrderManager payOrderManager;
public void sync(PayOrder payment) { private final PaySyncOrderService syncOrderService;
/**
* 支付同步
*/
public PaySyncResult sync(PaySyncParam param) {
PayOrder payOrder = null;
if (Objects.nonNull(param.getPaymentId())){
payOrder = payOrderManager.findById(param.getPaymentId())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
if (Objects.isNull(payOrder)){
payOrder = payOrderManager.findByBusinessNo(param.getBusinessNo())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
// 如果不是异步支付, 直接返回不需要同步的结果
if (!payOrder.isAsyncPay()){
return new PaySyncResult().setSuccess(true).setStatus(PaySyncStatusEnum.NOT_SYNC.getCode());
}
// 执行逻辑
return this.syncPayOrder(payOrder);
}
/**
* 同步支付状态 传入 payment 对象
*/
public PaySyncResult syncPayOrder(PayOrder order) {
// 获取同步策略类
AbsPaySyncStrategy syncPayStrategy = PaySyncStrategyFactory.create(order.getAsyncPayChannel());
syncPayStrategy.initPayParam(order);
// 同步支付状态
SyncResult syncResult = syncPayStrategy.doSyncPayStatusHandler();
// 根据同步记录对支付单进行处理处理
this.resultHandler(syncResult,order);
// 记录同步的结果
syncOrderService.saveRecord(syncResult,order);
return null;
}
/**
* 根据同步的结果对支付单进行处理
*/
public void resultHandler(SyncResult syncResult, PayOrder payment){
PaySyncStatusEnum syncStatusEnum = PaySyncStatusEnum.getByCode(syncResult.getSyncStatus());
// 对同步结果处理
switch (syncStatusEnum) {
// 支付成功 支付宝退款时也是支付成功状态, 除非支付完成
case PaySyncStatusEnum.PAY_SUCCESS: {
this.paymentSuccess(payment, syncPayStrategy, syncResult);
break;
}
// 待付款/ 支付中
case PaySyncStatusEnum.PAY_WAIT: {
log.info("依然是付款状态");
break;
}
// 订单已经关闭超时关闭 和 网关没找到记录, 支付宝退款完成也是这个状态
case PaySyncStatusEnum.CLOSED:
case PaySyncStatusEnum.NOT_FOUND: {
// 判断下是否超时, 同时payment 变更为取消支付
this.paymentCancel(payment, paymentStrategyList);
break;
}
// 交易退款 支付宝没这个状态
case PaySyncStatusEnum.REFUND: {
this.paymentRefund(payment, syncPayStrategy, syncResult);
break;
}
// 调用出错
case PaySyncStatusEnum.FAIL: {
// 不进行处理
log.warn("支付状态同步接口调用出错");
break;
}
case PaySyncStatusEnum.NOT_SYNC:
default: {
throw new BizException("代码有问题");
} }
} }
}
/**
* payment 变更为已支付
*/
private void paymentSuccess(PayOrder payment, AbsPayStrategy syncPayStrategy, SyncResult syncResult) {
// 已支付不在重复处理
if (Objects.equals(payment.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
return;
}
// 退款的不处理
if (Objects.equals(payment.getStatus(), PayStatusEnum.PARTIAL_REFUND.getCode())
|| Objects.equals(payment.getStatus(), PayStatusEnum.REFUNDED.getCode())) {
return;
}
// 修改payment支付状态为成功
syncPayStrategy.doAsyncSuccessHandler(syncResult.getMap());
payment.setStatus(PayStatusEnum.SUCCESS.getCode());
payment.setPayTime(LocalDateTime.now());
paymentService.updateById(payment);
// 发送成功事件
eventSender.sendPayComplete(PayEventBuilder.buildPayComplete(payment));
}
/**
* payment 变更为取消支付
*/
private void paymentCancel(Payment payment, List<AbsPayStrategy> absPayStrategies) {
try {
// 已关闭的不再进行关闭
if (Objects.equals(payment.getPayStatus(), TRADE_CANCEL)) {
return;
}
// 修改payment支付状态为取消, 退款状态则不进行更新
if (Objects.equals(payment.getPayStatus(), TRADE_REFUNDED)
|| Objects.equals(payment.getPayStatus(), TRADE_REFUNDING)) {
return;
}
payment.setPayStatus(TRADE_CANCEL);
// 执行策略的关闭方法
absPayStrategies.forEach(AbsPayStrategy::doCloseHandler);
paymentService.updateById(payment);
// 发送事件
eventSender.sendPayCancel(PayEventBuilder.buildPayCancel(payment));
}
catch (Exception e) {
log.warn("支付状态同步后关闭支付单报错了", e);
throw new PayFailureException("支付状态同步后关闭支付单报错了");
}
}
/**
* payment 退款处理 TODO 需要考虑退款详情的合并处理
*/
private void paymentRefund(Payment payment, AbsPayStrategy syncPayStrategy, PaySyncResult paySyncResult) {
}
}

View File

@@ -4,8 +4,8 @@ package cn.bootx.platform.daxpay.core.payment.sync.strategy;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService; import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipaySyncService; import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipaySyncService;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy; import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.RequiredArgsConstructor; 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;
@@ -30,7 +30,7 @@ public class AliPaySyncStrategy extends AbsPaySyncStrategy {
* 异步支付单与支付网关进行状态比对 * 异步支付单与支付网关进行状态比对
*/ */
@Override @Override
public PaySyncResult doSyncPayStatusHandler() { public SyncResult doSyncPayStatusHandler() {
this.initAlipayConfig(); this.initAlipayConfig();
return alipaySyncService.syncPayStatus(this.getOrder().getId()); return alipaySyncService.syncPayStatus(this.getOrder().getId());
} }

View File

@@ -3,8 +3,8 @@ package cn.bootx.platform.daxpay.core.payment.sync.strategy;
import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager; import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaySyncService; import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaySyncService;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy; import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.RequiredArgsConstructor; 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;
@@ -31,7 +31,7 @@ public class WeChatPaySyncStrategy extends AbsPaySyncStrategy {
* 异步支付单与支付网关进行状态比对 * 异步支付单与支付网关进行状态比对
*/ */
@Override @Override
public PaySyncResult doSyncPayStatusHandler() { public SyncResult doSyncPayStatusHandler() {
// 检查并获取微信支付配置 // 检查并获取微信支付配置
this.initWeChatPayConfig(); this.initWeChatPayConfig();
return weChatPaySyncService.syncPayStatus(this.getOrder().getId(), this.weChatPayConfig); return weChatPaySyncService.syncPayStatus(this.getOrder().getId(), this.weChatPayConfig);

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.dto.order.sync;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付同步订单")
public class PaySyncOrderDto extends BaseDto {
/** 支付记录id */
@Schema(description = "支付记录id")
private Long paymentId;
/**
* 支付渠道
* @see PayChannelEnum#getCode()
*/
@Schema(description = "支付渠道")
private String channel;
/** 通知消息 */
@Schema(description = "通知消息")
private String syncInfo;
/**
* 同步状态
* @see PaySyncStatusEnum
*/
@Schema(description = "同步状态")
private String status;
@Schema(description = "错误消息")
private String msg;
/** 同步时间 */
@Schema(description = "同步时间")
private LocalDateTime syncTime;
}

View File

@@ -22,7 +22,7 @@ import java.util.Map;
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class AbsPayCallbackStrategy { public abstract class AbsPayCallbackStrategy implements PayStrategy {
protected static final ThreadLocal<Map<String, String>> PARAMS = new TransmittableThreadLocal<>(); protected static final ThreadLocal<Map<String, String>> PARAMS = new TransmittableThreadLocal<>();

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.core.payment.refund.func; package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.common.exception.ExceptionInfo; import cn.bootx.platform.daxpay.common.exception.ExceptionInfo;
@@ -16,7 +16,7 @@ import lombok.Setter;
*/ */
@Getter @Getter
@Setter @Setter
public abstract class AbsPayRefundStrategy { public abstract class AbsPayRefundStrategy implements PayStrategy{
/** 支付对象 */ /** 支付对象 */
private PayOrder order = null; private PayOrder order = null;

View File

@@ -0,0 +1,55 @@
package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.repair.param.PayOrderRepairParam;
import lombok.Getter;
import lombok.Setter;
/**
* 支付订单修复策略
* @author xxm
* @since 2023/12/27
*/
@Getter
@Setter
public abstract class AbsPayRepairStrategy {
/** 支付对象 */
private PayOrder order = null;
/** 支付对象 */
private PayOrderRepairParam repairParam = null;
/**
* 初始化修复参数
*/
public void initRepairParam(PayOrder order,PayOrderRepairParam repairParam){
this.order = order;
this.repairParam = repairParam;
}
/**
* 策略标识
* @see PayChannelEnum
*/
public abstract PayChannelEnum getType();
/**
* 支付成功处理
*/
public abstract void successHandler();
/**
* 取消支付
*/
public abstract void closeHandler();
/**
* 退款
*/
public abstract void refundHandler();
}

View File

@@ -18,7 +18,7 @@ import java.util.Map;
*/ */
@Getter @Getter
@Setter @Setter
public abstract class AbsPayStrategy { public abstract class AbsPayStrategy implements PayStrategy{
/** 支付对象 */ /** 支付对象 */
@@ -31,7 +31,7 @@ public abstract class AbsPayStrategy {
private PayWayParam payWayParam = null; private PayWayParam payWayParam = null;
/** /**
* 策略标 * 策略标
* @see PayChannelEnum * @see PayChannelEnum
*/ */
public abstract PayChannelEnum getType(); public abstract PayChannelEnum getType();
@@ -71,12 +71,14 @@ public abstract class AbsPayStrategy {
/** /**
* 异步支付成功的处理方式 * 异步支付成功的处理方式
*/ */
@Deprecated
public void doAsyncSuccessHandler(Map<String, String> map) { public void doAsyncSuccessHandler(Map<String, String> map) {
} }
/** /**
* 异步支付失败的处理方式, 默认使用支付失败的处理方式 同步支付方式调用时同 this#doErrorHandler * 异步支付失败的处理方式, 默认使用支付失败的处理方式 同步支付方式调用时同 this#doErrorHandler
*/ */
@Deprecated
public void doAsyncErrorHandler(ExceptionInfo exceptionInfo) { public void doAsyncErrorHandler(ExceptionInfo exceptionInfo) {
this.doErrorHandler(exceptionInfo); this.doErrorHandler(exceptionInfo);
} }

View File

@@ -1,7 +1,8 @@
package cn.bootx.platform.daxpay.core.payment.sync.func; package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult; import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -12,7 +13,7 @@ import lombok.Setter;
*/ */
@Getter @Getter
@Setter @Setter
public abstract class AbsPaySyncStrategy { public abstract class AbsPaySyncStrategy implements PayStrategy{
/** 支付对象 */ /** 支付对象 */
private PayOrder order = null; private PayOrder order = null;
@@ -27,8 +28,8 @@ public abstract class AbsPaySyncStrategy {
/** /**
* 异步支付单与支付网关进行状态比对 * 异步支付单与支付网关进行状态比对
* @see cn.bootx.platform.daxpay.code.PaySyncStatus * @see PaySyncStatusEnum
*/ */
public abstract PaySyncResult doSyncPayStatusHandler(); public abstract SyncResult doSyncPayStatusHandler();
} }

View File

@@ -0,0 +1,9 @@
package cn.bootx.platform.daxpay.func;
/**
* 支付相关策略标识接口
* @author xxm
* @since 2023/12/27
*/
public interface PayStrategy {
}

View File

@@ -5,14 +5,19 @@ import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import java.util.List; import java.util.List;
/** /**
* 支付策略接口 * 支付策略消费者接口
* *
* @author xxm * @author xxm
* @since 2020/12/9 * @since 2020/12/9
*/ */
@FunctionalInterface @FunctionalInterface
public interface PayStrategyConsumer<T extends List<AbsPayStrategy>, S extends PayOrder> { public interface PayStrategyConsumer<T extends List<? extends PayStrategy>, S extends PayOrder> {
/**
* 消费者接口
* @param t 策略结论
* @param s
*/
void accept(T t, S s); void accept(T t, S s);
} }

View File

@@ -12,6 +12,7 @@
<artifactId>daxpay-single</artifactId> <artifactId>daxpay-single</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>daxpay-single-core</module>
<module>daxpay-single-service</module> <module>daxpay-single-service</module>
<module>daxpay-single-gateway</module> <module>daxpay-single-gateway</module>
<module>daxpay-single-admin</module> <module>daxpay-single-admin</module>

View File

@@ -18,8 +18,6 @@
<version>2.0.0</version> <version>2.0.0</version>
<modules> <modules>
<module>daxpay-core</module>
<module>daxpay-common</module>
<module>daxpay-single</module> <module>daxpay-single</module>
<module>dax-pay-sdk</module> <module>dax-pay-sdk</module>
</modules> </modules>