refactor(pay): NormalPayContext 重构,删除 NormalOrderTrade

- 新建 NormalPayContext/AbsNormalPayStrategy 替代旧的 PayStrategyContext/AbsPayStrategy
- NormalPayContext 持有容器+凭证+请求参数+通道配置,按序填充
- PayAssistService 方法改为填充 NormalPayContext,不再返回中间对象
- 通道策略全部切换为 AbsNormalPayStrategy,doBeforePay 激活通道配置
- 删除 NormalOrderTrade、PayStrategyContext、AbsPayStrategy、旧 PayService
- 删除 old PayCloseService/PaySyncService 中 unused import
This commit is contained in:
DaxPay Dev
2026-07-02 14:06:30 +08:00
parent ce7eaddbd7
commit da8e7b31d0
20 changed files with 181 additions and 312 deletions

View File

@@ -3,7 +3,7 @@ package cn.daxpay.open.channel.alipay.strategy.direct;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.close.AlipayCloseService;
import cn.daxpay.open.payment.pay.order.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.order.entity.NormalPayOrder;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.pay.AbsPayCloseStrategy;
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
/// # 支付宝直连支付关闭策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下的支付关闭策略。
/// 从容器层 NormalPayOrder 读取通道路由参数(channelMchNo / capability),
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [AlipayDirectConfigAssembler]), 关闭执行委托给 [AlipayCloseService]。
@Slf4j
@Service
@@ -25,7 +25,6 @@ public class AlipayDirectCloseStrategy extends AbsPayCloseStrategy {
private final AlipayCloseService alipayCloseService;
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
private final NormalPayOrderManager normalPayOrderManager;
@Override
public ProductEnum getProduct() {
@@ -33,11 +32,12 @@ public class AlipayDirectCloseStrategy extends AbsPayCloseStrategy {
}
@Override
public CloseTypeEnum doClose(PayTrade trade, boolean useCancel) {
// 从容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = normalPayOrderManager.findById(trade.getContainerId()).orElse(null);
public CloseTypeEnum doClose(NormalPayContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
PayTrade trade = context.getTrade();
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(

View File

@@ -1,10 +1,12 @@
package cn.daxpay.open.channel.alipay.strategy.direct;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.pay.AlipayPayService;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.payment.strategy.pay.AbsNormalPayStrategy;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -13,12 +15,12 @@ import org.springframework.stereotype.Service;
/// # 支付宝直连支付策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下发起支付的具体执行策略。
/// 负责直连配置组装(委托 [AlipayDirectConfigAssembler]), 支付执行委托给 [AlipayPayService]。
/// 配置组装在 [doBeforePay] 阶段完成(委托 [AlipayDirectConfigAssembler]), 支付执行委托给 [AlipayPayService]。
///
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayDirectPayStrategy extends AbsPayStrategy {
public class AlipayDirectPayStrategy extends AbsNormalPayStrategy {
private final AlipayPayService alipayPayService;
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
@@ -28,12 +30,21 @@ public class AlipayDirectPayStrategy extends AbsPayStrategy {
return ProductEnum.ALIPAY;
}
/// 支付前预处理: 从容器读取通道路由参数, 组装直连通道凭证写入上下文
/// 配置缺失在此阶段抛异常, fail-fast, 不会进入 doPay
@Override
public PayTradeResultBo doPay(PayStrategyContext context) {
return alipayPayService.pay(context.getTrade(), context.getPayParam(),
alipayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(),
context.getPayParam().getChannelMchNo(),
context.getPayParam().getCapability()));
public void doBeforePay(NormalPayContext context) {
// 组装直连通道凭证(只依赖请求参数, 订单尚未创建)
NormalPayParam payParam = context.getPayParam();
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
payParam.getMchNo(), payParam.getChannelMchNo(), payParam.getCapability());
context.setChannelConfig(credential);
}
@Override
public PayTradeResultBo doPay(NormalPayContext context) {
// 直接使用 doBeforePay 预组装的通道凭证
AlipaySdkCredential credential = context.getChannelConfig(AlipaySdkCredential.class);
return alipayPayService.pay(context.getTrade(), context.getPayParam(), credential);
}
}

View File

@@ -3,10 +3,9 @@ package cn.daxpay.open.channel.alipay.strategy.direct;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.sync.AlipaySyncService;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.bo.PaySyncResultBo;
import cn.daxpay.open.payment.pay.order.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.pay.order.entity.NormalPayOrder;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
@@ -16,7 +15,7 @@ import org.springframework.stereotype.Service;
/// # 支付宝直连支付同步策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下的支付同步策略。
/// 从容器层 NormalPayOrder 读取通道路由参数(channelMchNo / capability),
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [AlipayDirectConfigAssembler]), 同步执行委托给 [AlipaySyncService]。
@Slf4j
@Service
@@ -25,7 +24,6 @@ public class AlipayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
private final AlipaySyncService alipaySyncService;
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
private final NormalPayOrderManager normalPayOrderManager;
@Override
public ProductEnum getProduct() {
@@ -33,16 +31,16 @@ public class AlipayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
}
@Override
public PaySyncResultBo doSync(PayTrade trade) {
// 从容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = normalPayOrderManager.findById(trade.getContainerId()).orElse(null);
public PaySyncResultBo doSync(NormalPayContext context) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), channelMchNo, capability);
return alipaySyncService.sync(trade, credential);
return alipaySyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -3,6 +3,7 @@ package cn.daxpay.open.channel.alipay.strategy.isv;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.isv.AlipayIsvConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.close.AlipayCloseService;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -29,7 +30,8 @@ public class AlipayIsvCloseStrategy extends AbsPayCloseStrategy {
}
@Override
public CloseTypeEnum doClose(PayTrade trade, boolean useCancel) {
public CloseTypeEnum doClose(NormalPayContext context, boolean useCancel) {
PayTrade trade = context.getTrade();
// 组装服务商模式通道调用凭证(含应用授权令牌)
AlipaySdkCredential credential = alipayIsvConfigAssembler.buildConfig(trade.getMchNo());
return alipayCloseService.close(trade, credential, useCancel);

View File

@@ -1,10 +1,11 @@
package cn.daxpay.open.channel.alipay.strategy.isv;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.isv.AlipayIsvConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.pay.AlipayPayService;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.payment.strategy.pay.AbsNormalPayStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -13,11 +14,11 @@ import org.springframework.stereotype.Service;
/// # 支付宝服务商支付策略
///
/// 支付宝服务商模式(ProductEnum.ALIPAY_ISV)下发起支付的具体执行策略。
/// 负责服务商配置组装(含应用授权令牌, 委托 [AlipayIsvConfigAssembler]), 支付执行委托给 [AlipayPayService]。
/// 配置组装(含应用授权令牌)在 [doBeforePay] 阶段完成(委托 [AlipayIsvConfigAssembler]), 支付执行委托给 [AlipayPayService]。
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayIsvPayStrategy extends AbsPayStrategy {
public class AlipayIsvPayStrategy extends AbsNormalPayStrategy {
private final AlipayPayService alipayPayService;
private final AlipayIsvConfigAssembler alipayIsvConfigAssembler;
@@ -27,10 +28,18 @@ public class AlipayIsvPayStrategy extends AbsPayStrategy {
return ProductEnum.ALIPAY_ISV;
}
/// 支付前预处理: 组装服务商模式通道凭证(含应用授权令牌)写入上下文
/// 配置缺失在此阶段抛异常, fail-fast, 不会进入 doPay
@Override
public PayTradeResultBo doPay(PayStrategyContext context) {
// 组装服务商模式通道调用凭证(含应用授权令牌), 支付执行委托 AlipayPayService
return alipayPayService.pay(context.getTrade(), context.getPayParam(),
alipayIsvConfigAssembler.buildConfig(context.getTrade().getMchNo()));
public void doBeforePay(NormalPayContext context) {
AlipaySdkCredential credential = alipayIsvConfigAssembler.buildConfig(context.getPayParam().getMchNo());
context.setChannelConfig(credential);
}
@Override
public PayTradeResultBo doPay(NormalPayContext context) {
// 直接使用 doBeforePay 预组装的通道凭证
AlipaySdkCredential credential = (AlipaySdkCredential) context.getChannelConfig();
return alipayPayService.pay(context.getTrade(), context.getPayParam(), credential);
}
}

View File

@@ -3,6 +3,7 @@ package cn.daxpay.open.channel.alipay.strategy.isv;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.isv.AlipayIsvConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.sync.AlipaySyncService;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.bo.PaySyncResultBo;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.sync.AbsSyncPayOrderStrategy;
@@ -29,7 +30,8 @@ public class AlipayIsvSyncStrategy extends AbsSyncPayOrderStrategy {
}
@Override
public PaySyncResultBo doSync(PayTrade trade) {
public PaySyncResultBo doSync(NormalPayContext context) {
PayTrade trade = context.getTrade();
// 组装服务商模式通道调用凭证(含应用授权令牌)
AlipaySdkCredential credential = alipayIsvConfigAssembler.buildConfig(trade.getMchNo());
return alipaySyncService.sync(trade, credential);

View File

@@ -3,10 +3,10 @@ package cn.daxpay.open.channel.douyin.strategy;
import cn.daxpay.open.channel.douyin.client.DouyinChannelClient;
import cn.daxpay.open.channel.douyin.dto.DouyinPayReq;
import cn.daxpay.open.channel.douyin.dto.DouyinPayResp;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.payment.strategy.pay.AbsNormalPayStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -21,7 +21,7 @@ import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class DouyinDirectPayStrategy extends AbsPayStrategy {
public class DouyinDirectPayStrategy extends AbsNormalPayStrategy {
private final DouyinChannelClient douyinChannelClient;
@@ -31,7 +31,7 @@ public class DouyinDirectPayStrategy extends AbsPayStrategy {
}
@Override
public PayTradeResultBo doPay(PayStrategyContext context) {
public PayTradeResultBo doPay(NormalPayContext context) {
// 构建请求
DouyinPayReq req = new DouyinPayReq();
req.setChannel(ProductEnum.DOUYIN_PAY.getChannel());

View File

@@ -0,0 +1,37 @@
package cn.daxpay.open.payment.common.context;
import cn.daxpay.open.payment.pay.order.entity.NormalPayOrder;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/// # 普通支付策略执行上下文(请求级,每次支付/同步/关闭创建新实例)
///
/// 显式传递,替代原策略实例字段。持有"容器(业务单) + 资金凭证 + 请求参数"三要素,
/// 与 [PayTrade] 的 containerId 形成冗余双链,使策略无需再自查容器表。
/// 通道配置由 doBeforePay 写入、doPay/doSync/doClose 读取,类型各通道不同。
@Getter
@Setter
@Accessors(chain = true)
public class NormalPayContext {
/// 普通支付业务单容器(承载 bizOrderNo / 回调地址 / 通道路由等业务信息)
private NormalPayOrder container;
/// 资金交易凭证(记录本笔资金动作的状态与通道回执)
private PayTrade trade;
/// 普通支付请求参数pay 流程必填sync/close 流程可为空)
private NormalPayParam payParam;
/// 通道配置doBeforePay 写入doPay 读取;类型各通道不同)
private Object channelConfig;
@SuppressWarnings("unchecked")
public <T> T getChannelConfig(Class<T> clazz) {
return (T) channelConfig;
}
}

View File

@@ -1,32 +0,0 @@
package cn.daxpay.open.payment.common.context;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/// # 支付策略执行上下文(请求级,每次支付创建新实例)
///
/// 显式传递,替代原 AbsPayStrategy 实例字段。通道配置由 doBeforePay 写入、doPay 读取。
@Getter
@Setter
@Accessors(chain = true)
public class PayStrategyContext {
/// 支付参数
private NormalPayParam payParam;
/// 资金交易凭证
private PayTrade trade;
/// 通道配置doBeforePay 写入doPay 读取;类型各通道不同)
private Object channelConfig;
public PayStrategyContext() {
}
public PayStrategyContext(NormalPayParam payParam) {
this.payParam = payParam;
}
}

View File

@@ -17,6 +17,7 @@ import cn.daxpay.open.platform.core.enums.pay.pay.PayStatusEnum;
import cn.daxpay.open.payment.old.pay.exception.TradeNotExistException;
import cn.daxpay.open.payment.old.pay.exception.TradeProcessingException;
import cn.daxpay.open.payment.old.pay.exception.TradeStatusErrorException;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.context.PaymentContext;
import cn.daxpay.open.payment.old.pay.service.notice.MerchantNoticeService;
import cn.daxpay.open.payment.old.pay.service.order.pay.PayOrderQueryService;
@@ -95,9 +96,10 @@ public class PayCloseService {
trade.setChannel(payOrder.getChannel());
trade.setMethod(payOrder.getMethod());
// 关闭前准备
strategy.doBeforeClose(trade);
NormalPayContext closeContext = new NormalPayContext().setTrade(trade);
strategy.doBeforeClose(closeContext);
// 执行关闭策略, 返回关闭的方式
closeType = strategy.doClose(trade, useCancel);
closeType = strategy.doClose(closeContext, useCancel);
// 成功处理 关闭或撤销订单
payStatusEnum = useCancel ? PayStatusEnum.CANCEL : PayStatusEnum.CLOSE;
}

View File

@@ -1,178 +0,0 @@
package cn.daxpay.open.payment.old.pay.service.trade.pay;
import cn.daxpay.open.platform.common.i18n.util.I18nUtil;
import cn.daxpay.open.platform.core.exception.ValidationFailedException;
import cn.daxpay.open.platform.core.exception.system.DataErrorException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.old.pay.bo.trade.PayResultBo;
import cn.daxpay.open.payment.old.pay.dao.order.pay.PayOrderExpandManager;
import cn.daxpay.open.payment.old.pay.dao.order.pay.PayOrderManager;
import cn.daxpay.open.payment.old.pay.entity.order.pay.PayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.PayStatusEnum;
import cn.daxpay.open.payment.old.pay.exception.TradeProcessingException;
import cn.daxpay.open.payment.old.pay.service.trade.TradeUniHandleService;
import cn.daxpay.open.payment.masterdata.constants.product.service.PayProductCapabilityService;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
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.Transactional;
import java.util.Objects;
import java.util.Optional;
/// # 支付服务类
///
@Slf4j
@Service("oldPayService")
@RequiredArgsConstructor
public class PayService {
private final PayAssistService payAssistService;
private final LockTemplate lockTemplate;
private final PayOrderManager payOrderManager;
private final PayOrderExpandManager payOrderExpandManager;
private final TradeUniHandleService tradeUniHandleService;
/// 支付入口
public NormalPayResult pay(NormalPayParam payParam){
// 校验超时时间, 不可早于当前
payAssistService.validationExpiredTime(payParam.getExpiredTime());
// 获取商户订单号
String bizOrderNo = payParam.getBizOrderNo();
// 加锁
LockInfo lock = lockTemplate.lock("payment:pay:" + bizOrderNo,10000,200);
if (Objects.isNull(lock)){
log.warn("正在支付中,请勿重复支付");
// 正在支付中,请勿重复支付
throw new TradeProcessingException();
}
try {
// 查询并检查订单
var payOrder = payAssistService.getOrderAndCheck(payParam.getBizOrderNo(), payParam.getAppId());
// 调用支付流程
return this.payHandle(payParam,payOrder);
} catch (Exception e) {
log.error("支付异常",e);
throw e;
} finally {
lockTemplate.releaseLock(lock);
}
}
/// 支付操作 无事务
/// 拆分为多阶段1. 保存订单记录信息 2. 调起支付 3. 支付成功后操作
public NormalPayResult payHandle(NormalPayParam payParam, PayOrder payOrder) {
// 获取支付策略类
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsPayStrategy.class);
PayStrategyContext context = new PayStrategyContext(payParam);
// 检测支付能力是否支持(产品已挂载能力覆盖该支付方式)
var methodEnum = PayMethodEnum.findByCode(payParam.getMethod());
if (!SpringUtil.getBean(PayProductCapabilityService.class).productSupportsMethod(
payParam.getProduct(), methodEnum.getCode())) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.unsupportedPayMethod",
I18nUtil.getEnumName(methodEnum) + "(" + methodEnum.getCode() + ")");
}
// 执行支付前处理动作, 进行各种校验, 校验通过才会进行下面的操作
payStrategy.doBeforePay(context);
// 订单不存在执行支付前的保存动作, 保存支付订单默认状态为支付中
if (Objects.isNull(payOrder)){
payOrder = payAssistService.createPayOrder(payParam);
} else {
// 订单: 支付订单扩展信息不存在
var orderExpand = payOrderExpandManager.findById(payOrder.getId()).orElseThrow(() -> new DataErrorException("error.payment.order.payOrderExtNotExist"));
// 判断是否已经拉起了支付,如果拉起返回保存的支付参数
if (StrUtil.isNotBlank(orderExpand.getPayBody())){
return payAssistService.buildResult(payOrder,orderExpand);
}
}
// 订单待支付状态, 设置支付方式和对应状态
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.WAIT.getCode())){
payAssistService.updatePayOrder(payParam,payOrder);
}
PayTrade trade = new PayTrade();
trade.setId(payOrder.getId());
trade.setProduct(payOrder.getProduct());
trade.setChannel(payOrder.getChannel());
trade.setMethod(payOrder.getMethod());
trade.setAmount(payOrder.getAmount());
context.setTrade(trade);
PayResultBo result;
try {
// 支付操作
var newResult = payStrategy.doPay(context);
result = new PayResultBo();
result.setOutOrderNo(newResult.getOutOrderNo());
result.setComplete(newResult.isComplete());
result.setRealAmount(newResult.getRealAmount());
result.setFinishTime(newResult.getFinishTime());
result.setPayBody(newResult.getPayBody());
result.setPayBodyType(newResult.getPayBodyType());
result.setBuyerId(newResult.getBuyerId());
result.setUserId(newResult.getUserId());
result.setTradeProduct(newResult.getTradeProduct());
result.setTradeWay(newResult.getTradeWay());
result.setBankType(newResult.getBankType());
result.setTransOrderNo(newResult.getTransOrderNo());
result.setRelationOrderNo(newResult.getRelationOrderNo());
result.setPromotionType(newResult.getPromotionType());
} catch (Exception e) {
log.error("支付出现异常",e);
payOrder.setStatus(PayStatusEnum.FAIL.getCode());
if (e instanceof PayFailureException) {
payOrder.setErrorMsg(e.getMessage());
} else {
payOrder.setErrorMsg("支付出现异常: "+ e.getMessage());
}
// 这个方法没有事务, 所以可以正常更新
payOrderManager.updateById(payOrder);
throw e;
}
// 支付调起成功后操作, 使用事务来保证数据一致性
return SpringUtil.getBean(this.getClass()).paySuccess(payOrder, result);
}
/// 支付调用成功后操作, 更新订单信息
@Transactional(rollbackFor = Exception.class)
public NormalPayResult paySuccess(PayOrder payOrder, PayResultBo result){
// 如果支付完成, 进行订单完成处理, 同时发送回调消息
if (result.isComplete()) {
payOrder.setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(result.getFinishTime());
}
// 更新订单信息
payOrder.setOutOrderNo(result.getOutOrderNo())
.setErrorMsg(null)
.setTransOrderNo(result.getTransOrderNo())
.setRelationOrderNo(result.getRelationOrderNo());
// 更新订单扩展信息
var orderExpand = payOrderExpandManager.findById(payOrder.getId()).orElseThrow(() -> new DataErrorException("error.payment.order.payOrderExtNotExist"));
orderExpand.setTradeProduct(result.getTradeProduct())
.setBankType(result.getBankType())
.setTradeWay(result.getTradeWay())
.setPromotionType(result.getPromotionType())
.setRealAmount(Opt.ofNullable(result.getRealAmount()).orElse(payOrder.getAmount()))
.setBuyerId(result.getBuyerId())
.setUserId(result.getUserId())
.setPayBody(result.getPayBody())
.setPayBodyType(Optional.ofNullable(result.getPayBodyType()).map(PayBodyTypeEnum::getCode).orElse(null));
// 统一处理
tradeUniHandleService.payAfterHandel(payOrder,orderExpand);
return payAssistService.buildResult(payOrder,orderExpand);
}
}

View File

@@ -20,6 +20,7 @@ import cn.daxpay.open.platform.core.enums.pay.pay.PayStatusEnum;
import cn.daxpay.open.platform.core.enums.pay.trade.TradeTypeEnum;
import cn.daxpay.open.payment.old.pay.exception.TradeNotExistException;
import cn.daxpay.open.payment.old.pay.exception.TradeStatusErrorException;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.context.PaymentContext;
import cn.daxpay.open.payment.old.pay.service.order.pay.PayOrderQueryService;
import cn.daxpay.open.payment.old.pay.service.record.sync.TradeSyncRecordService;
@@ -104,7 +105,7 @@ public class PaySyncService {
trade.setAmount(payOrder.getAmount());
try {
// 执行操作, 获取支付网关同步的结果
var newResult = syncPayStrategy.doSync(trade);
var newResult = syncPayStrategy.doSync(new NormalPayContext().setTrade(trade));
PaySyncResultBo syncResult = new PaySyncResultBo();
syncResult.setSyncSuccess(newResult.isSyncSuccess());
if (newResult.getPayStatus() != null) {
@@ -251,9 +252,10 @@ public class PaySyncService {
closeTrade.setProduct(order.getProduct());
closeTrade.setChannel(order.getChannel());
closeTrade.setMethod(order.getMethod());
strategy.doBeforeClose(closeTrade);
NormalPayContext closeContext = new NormalPayContext().setTrade(closeTrade);
strategy.doBeforeClose(closeContext);
// 执行策略的关闭方法
strategy.doClose(closeTrade, false);
strategy.doClose(closeContext, false);
// 关闭统一处理
tradeUniHandleService.payClose(order, PayStatusEnum.CLOSE);
}

View File

@@ -3,15 +3,15 @@ package cn.daxpay.open.payment.pay.service;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
import cn.daxpay.open.payment.pay.order.dao.PayTradeManager;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.pay.service.route.PayRouteFacade;
import cn.hutool.core.util.StrUtil;
import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy;
import cn.daxpay.open.payment.strategy.pay.AbsNormalPayStrategy;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
import cn.hutool.extra.spring.SpringUtil;
@@ -46,32 +46,35 @@ public class NormalPayService {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.processing");
}
try {
PayTrade trade = payAssistService.getOrderAndCheck(bizOrderNo);
return this.payHandle(payParam, trade);
return this.payHandle(payParam);
} finally {
lockTemplate.releaseLock(lock);
}
}
/// 支付操作
public NormalPayResult payHandle(NormalPayParam payParam, PayTrade trade) {
/// 拆分为多阶段: 1.校验与配置组装 2.查询已有订单 3.新建订单 4.发起支付 5.支付成功后处理
public NormalPayResult payHandle(NormalPayParam payParam) {
// 解析应用号:空则取商户默认应用
payAssistService.resolveApp(payParam);
// 路由解析:直定模式(已传 channelMchNo)直接解析,否则按 appId+method 策略匹配
payRouteFacade.resolve(payParam);
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsPayStrategy.class);
// 订单不存在,新建支付订单
if (Objects.isNull(trade)) {
trade = payAssistService.createOrder(payParam);
} else {
// 判断是否已经拉起了支付,如果拉起返回保存的支付参数
if (StrUtil.isNotBlank(trade.getPayBody())) {
return payAssistService.buildResult(trade);
}
}
// 显式传递上下文(替代原策略实例字段)
PayStrategyContext context = new PayStrategyContext(payParam).setTrade(trade);
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsNormalPayStrategy.class);
// 支付前处理: 校验与通道配置组装(只依赖请求参数), 失败直接抛出不持久化(订单尚未创建)
NormalPayContext context = new NormalPayContext().setPayParam(payParam);
payStrategy.doBeforePay(context);
// 查询已有订单并校验,结果填充到 context
payAssistService.findAndCheckOrder(payParam.getBizOrderNo(), context);
// 已拉起支付则返回缓存的支付参数
if (Objects.nonNull(context.getTrade()) && StrUtil.isNotBlank(context.getTrade().getPayBody())) {
return payAssistService.buildResult(context.getTrade());
}
// 订单不存在则新建(填充 context
if (Objects.isNull(context.getContainer())) {
payAssistService.createOrder(payParam, context);
}
PayTrade trade = context.getTrade();
// 支付操作, 失败标记 trade 为 FAIL 并持久化
PayTradeResultBo result;
try {
result = payStrategy.doPay(context);

View File

@@ -12,6 +12,7 @@ import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.common.service.MchAppInfoAssistQueryService;
import cn.daxpay.open.payment.common.util.PayUtil;
import cn.hutool.core.util.StrUtil;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.pay.convert.PayTradeConvert;
import cn.daxpay.open.payment.pay.order.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.pay.order.entity.NormalPayOrder;
@@ -48,10 +49,10 @@ public class PayAssistService {
}
}
/// 创建支付订单(容器 + 资金交易)
/// 创建支付订单(容器 + 资金交易),填充到 context
/// 调用方需保证 appId 和 product 已解析完毕
@Transactional(rollbackFor = Exception.class)
public PayTrade createOrder(NormalPayParam payParam) {
public void createOrder(NormalPayParam payParam, NormalPayContext context) {
String appId = payParam.getAppId();
OffsetDateTime expiredTime = this.getExpiredTime(payParam.getExpiredTime());
Long amount = payParam.getAmount();
@@ -108,22 +109,23 @@ public class PayAssistService {
trade.setBarCode(payParam.getAuthCode());
trade.setOpenid(payParam.getOpenId());
payTradeManager.save(trade);
return trade;
context.setContainer(normalOrder).setTrade(trade);
}
/// 根据业务单号查询并检查支付状态(按商户号自动租户隔离)
public PayTrade getOrderAndCheck(String bizOrderNo) {
/// 查询已有订单并校验,结果填充到 context
/// 订单不存在则 context 保持不变(调用方据此判断是否新建)
public void findAndCheckOrder(String bizOrderNo, NormalPayContext context) {
Optional<NormalPayOrder> normalOrderOpt = payNormalOrderManager.findByBizOrderNo(bizOrderNo);
if (normalOrderOpt.isEmpty()) {
return null;
return;
}
NormalPayOrder normalOrder = normalOrderOpt.get();
PayTrade trade = payTradeManager.findByContainerId(normalOrder.getId()).orElse(null);
if (trade == null) {
return null;
return;
}
this.checkOrder(normalOrder, trade);
return trade;
context.setContainer(normalOrder).setTrade(trade);
}
/// 检查订单状态

View File

@@ -6,6 +6,7 @@ import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.platform.core.exception.operation.OperationFailException;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.pay.order.dao.NormalPayOrderManager;
@@ -74,10 +75,13 @@ public class PayCloseService {
if (Objects.equals(PayFundStatusEnum.INIT.getCode(), trade.getStatus())) {
payUniHandleService.payClose(trade, normalOrder);
} else {
NormalPayContext context = new NormalPayContext()
.setContainer(normalOrder)
.setTrade(trade);
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(trade);
strategy.doClose(trade, useCancel);
strategy.doBeforeClose(context);
strategy.doClose(context, useCancel);
payUniHandleService.payClose(trade, normalOrder);
}
} catch (Exception e) {

View File

@@ -7,6 +7,7 @@ import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.platform.core.exception.RepetitiveOperationException;
import cn.daxpay.open.platform.core.exception.system.SystemUnknownErrorException;
import cn.daxpay.open.platform.core.util.DateTimeUtil;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.util.PaymentStrategyFactory;
import cn.daxpay.open.payment.pay.bo.PaySyncResultBo;
@@ -85,9 +86,12 @@ public class PaySyncService {
try {
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId())
.orElse(null);
NormalPayContext context = new NormalPayContext()
.setContainer(normalOrder)
.setTrade(trade);
var syncStrategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsSyncPayOrderStrategy.class);
PaySyncResultBo syncResult = syncStrategy.doSync(trade);
PaySyncResultBo syncResult = syncStrategy.doSync(context);
if (!Objects.equals(syncResult.getOutOrderNo(), trade.getOutOrderNo())) {
trade.setOutOrderNo(syncResult.getOutOrderNo());
payTradeManager.updateById(trade);

View File

@@ -0,0 +1,22 @@
package cn.daxpay.open.payment.strategy.pay;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
/// # 抽象普通支付策略基类
///
/// 策略为单例无状态,运行时数据通过 [NormalPayContext] 显式传递。
/// 上下文同时携带容器(业务单)与资金凭证,策略无需再自查容器表。
public abstract class AbsNormalPayStrategy implements PaymentStrategy {
/// 支付前处理 包含必要的校验以及对当前通道支付配置信息的初始化
/// 出现错误不会保存相关信息
public void doBeforePay(NormalPayContext context) {
}
/// 支付操作
/// 出现错误会保存相关信息
public abstract PayTradeResultBo doPay(NormalPayContext context);
}

View File

@@ -1,22 +1,23 @@
package cn.daxpay.open.payment.strategy.pay;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
/// # 支付关闭策略
///
/// 策略为单例无状态,运行时数据通过方法参数显式传递。
/// 策略为单例无状态,运行时数据通过 [NormalPayContext] 显式传递。
/// 上下文同时携带容器(业务单)与资金凭证,策略无需再自查容器表。
public abstract class AbsPayCloseStrategy implements PaymentStrategy {
/// 关闭前的处理方式
public void doBeforeClose(PayTrade trade) {
public void doBeforeClose(NormalPayContext context) {
}
/// 关闭操作
/// @param trade 资金交易凭证
/// @param context 支付上下文(容器 + 资金凭证
/// @param useCancel 是否使用撤销方式进行订单关闭
/// @return 如果执行成功, 返回使用何种方式关闭的支付订单
public abstract CloseTypeEnum doClose(PayTrade trade, boolean useCancel);
public abstract CloseTypeEnum doClose(NormalPayContext context, boolean useCancel);
}

View File

@@ -1,21 +0,0 @@
package cn.daxpay.open.payment.strategy.pay;
import cn.daxpay.open.payment.common.context.PayStrategyContext;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.payment.pay.bo.PayTradeResultBo;
/// # 抽象支付策略基类
///
/// 策略为单例无状态,运行时数据通过 [PayStrategyContext] 显式传递。
public abstract class AbsPayStrategy implements PaymentStrategy {
/// 支付前处理 包含必要的校验以及对当前通道支付配置信息的初始化
/// 出现错误不会保存相关信息
public void doBeforePay(PayStrategyContext context) {
}
/// 支付操作
/// 出现错误会保存相关信息
public abstract PayTradeResultBo doPay(PayStrategyContext context);
}

View File

@@ -1,15 +1,16 @@
package cn.daxpay.open.payment.strategy.sync;
import cn.daxpay.open.payment.common.context.NormalPayContext;
import cn.daxpay.open.payment.common.strategy.PaymentStrategy;
import cn.daxpay.open.payment.pay.bo.PaySyncResultBo;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
/// # 支付同步抽象类
///
/// 策略为单例无状态,运行时数据通过方法参数显式传递。
/// 策略为单例无状态,运行时数据通过 [NormalPayContext] 显式传递。
/// 上下文同时携带容器(业务单)与资金凭证,策略无需再自查容器表。
public abstract class AbsSyncPayOrderStrategy implements PaymentStrategy {
/// 查询通道网关方的退款订单状态信息
public abstract PaySyncResultBo doSync(PayTrade trade);
/// 查询通道网关方的支付订单状态信息
public abstract PaySyncResultBo doSync(NormalPayContext context);
}