refactor(pay): 拆分 createOrder 编排并抽取 PROCESSING 资金单初始化

将 NormalPayAssistService#createOrder 拆为 resolveSource/buildNormalOrder/buildPayTrade,
新增 PayTradeInitUtil 作为 normal/gateway 共用的 PROCESSING 态 PayTrade 初始化单一事实源。
This commit is contained in:
DaxPay Dev
2026-07-15 14:33:15 +08:00
parent 7a3d1b4f3c
commit b152ca34c0
3 changed files with 132 additions and 68 deletions

View File

@@ -1,7 +1,6 @@
package cn.daxpay.open.payment.trade.runtime.service.pay.gateway;
import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum;
import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum;
import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.common.context.MerchantContextLoader;
@@ -18,12 +17,11 @@ import cn.daxpay.open.payment.trade.order.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.CurrencyEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.PayFailureException;
import cn.daxpay.open.platform.core.util.TradeNoGenerateUtil;
import cn.daxpay.open.payment.trade.util.PayTradeInitUtil;
import cn.daxpay.open.platform.common.redis.lock.LockExecutor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -135,21 +133,24 @@ public class GatewayPayHandleService {
var productEnum = ProductEnum.findByCode(payParam.getProduct());
String channel = productEnum.getChannel();
PayTrade trade = new PayTrade();
trade.setAppId(order.getAppId());
trade.setTradeNo(TradeNoGenerateUtil.pay());
trade.setTradeType(PayTradeTypeEnum.GATEWAY.getCode());
trade.setContainerId(order.getId());
trade.setAmount(order.getAmount());
trade.setCurrency(CurrencyEnum.CNY.getCode());
// 入账金额: 未成功前为 0, 成功后由 PayUniHandleService 按规则回写
trade.setPostedAmount(0L);
trade.setRefundableBalance(order.getAmount());
trade.setStatus(PayFundStatusEnum.PROCESSING.getCode());
// 来源: 冗余自容器(预下单权威); 历史单无 source 时按 gatewayType 派生
String source = StrUtil.isNotBlank(order.getSource())
? order.getSource()
: GatewayPayAssistService.resolveSourceByGatewayTypeCode(order.getGatewayType());
// 历史预下单可能未写容器 source, 建 trade 时回填
if (StrUtil.isBlank(order.getSource())) {
order.setSource(source);
}
// 默认上送网关业务单号; 特殊通道返回后可覆盖
trade.setRelationOrderNo(order.getOrderNo());
// 来源: 聚合 aggress_pay / 收银台 cashier
trade.setSource(resolveTradeSource(order.getGatewayType()));
PayTrade trade = PayTradeInitUtil.initProcessing(
order.getAppId(),
TradeNoGenerateUtil.pay(),
PayTradeTypeEnum.GATEWAY.getCode(),
order.getId(),
order.getAmount(),
order.getOrderNo(),
source);
payTradeManager.save(trade);
this.fillRouteOnOrder(order, payParam, clientEnv, device);
@@ -236,12 +237,4 @@ public class GatewayPayHandleService {
.setPayBodyType(order.getPayBodyType());
}
/// 按网关产品类型解析交易来源
private static String resolveTradeSource(String gatewayType) {
if (Objects.equals(gatewayType, GatewayPayTypeEnum.CASHIER.getCode())) {
return TradeSourceEnum.CASHIER.getCode();
}
// 默认聚合扫码
return TradeSourceEnum.AGGRESS_PAY.getCode();
}
}

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.platform.common.artemis.service.ArtemisTemplateService;
import cn.daxpay.open.platform.common.json.util.JacksonUtil;
import cn.daxpay.open.platform.core.enums.pay.channel.CurrencyEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.util.DateTimeUtil;
@@ -19,6 +20,7 @@ import cn.daxpay.open.payment.trade.order.dao.PayTradeManager;
import cn.daxpay.open.payment.trade.order.entity.PayTrade;
import cn.daxpay.open.payment.trade.runtime.mq.NormalPayTimeoutMessage;
import cn.daxpay.open.payment.trade.runtime.mq.PayArtemisConstants;
import cn.daxpay.open.payment.trade.util.PayTradeInitUtil;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
import cn.hutool.core.util.StrUtil;
@@ -48,32 +50,55 @@ public class NormalPayAssistService {
/// orderNo 与 tradeNo 独立生成; 普通通道实际上送串 relationOrderNo 默认=orderNo
@Transactional(rollbackFor = Exception.class)
public void createOrder(NormalPayParam payParam, PayStrategyContext context) {
String appId = payParam.getAppId();
OffsetDateTime expiredTime = this.getExpiredTime(payParam.getExpiredTime());
Long amount = payParam.getAmount();
// 从产品编码派生通道编码
var productEnum = ProductEnum.findByCode(payParam.getProduct());
var channel = productEnum.getChannel();
// 终端信息
String terminalNo = payParam.getTerminal() != null ? payParam.getTerminal().getTerminalNo() : null;
// 双号独立生成
String orderNo = TradeNoGenerateUtil.order();
String tradeNo = TradeNoGenerateUtil.pay();
// 创建容器 NormalPayOrder含冗余字段方便查询
String source = resolveSource(payParam);
NormalPayOrder normalOrder = buildNormalOrder(payParam, orderNo, expiredTime, source);
payNormalOrderManager.save(normalOrder);
PayTrade trade = buildPayTrade(payParam, normalOrder, tradeNo, orderNo, source);
payTradeManager.save(trade);
context.setTrade(trade);
context.setNormalOrder(normalOrder);
// 注册超时关单延时消息(按订单过期时间定时投递)
this.registerTimeoutClose(trade.getTradeNo(), normalOrder.getBizOrderNo(), expiredTime);
}
/// 来源: 容器权威; 协议层(易支付/码牌)可显式传入, 默认商户 API
private String resolveSource(NormalPayParam payParam) {
return StrUtil.isNotBlank(payParam.getSource())
? payParam.getSource()
: TradeSourceEnum.MCH_API.getCode();
}
/// 组装普通支付业务容器(未落库)
private NormalPayOrder buildNormalOrder(NormalPayParam payParam, String orderNo,
OffsetDateTime expiredTime, String source) {
// 从产品编码派生通道编码
var productEnum = ProductEnum.findByCode(payParam.getProduct());
String channel = productEnum.getChannel();
// 终端信息
String terminalNo = payParam.getTerminal() != null ? payParam.getTerminal().getTerminalNo() : null;
NormalPayOrder normalOrder = new NormalPayOrder();
// --- 业务身份 ---
normalOrder.setOrderNo(orderNo);
// 普通通道默认上送 orderNo, 特殊通道下单后再覆盖 relation
normalOrder.setRelationOrderNo(orderNo);
normalOrder.setBizOrderNo(payParam.getBizOrderNo());
normalOrder.setTitle(payParam.getTitle());
normalOrder.setDescription(payParam.getDescription());
normalOrder.setSource(source);
// --- 状态与金额 ---
normalOrder.setStatus(NormalPayOrderStatusEnum.WAIT_PAY.getCode());
normalOrder.setNotifyUrl(payParam.getNotifyUrl());
normalOrder.setReturnUrl(payParam.getReturnUrl());
normalOrder.setAttach(payParam.getAttach());
normalOrder.setExpiredTime(expiredTime);
normalOrder.setAmount(amount);
normalOrder.setAmount(payParam.getAmount());
normalOrder.setCurrency(CurrencyEnum.CNY.getCode());
// --- 支付路由 ---
normalOrder.setChannel(channel);
normalOrder.setMethod(payParam.getMethod());
normalOrder.setLimitPay(payParam.getLimitPay() != null
@@ -81,42 +106,36 @@ public class NormalPayAssistService {
normalOrder.setOpenid(payParam.getOpenId());
normalOrder.setAuthCode(payParam.getAuthCode());
normalOrder.setProduct(payParam.getProduct());
normalOrder.setExtraParam(payParam.getExtraParam());
normalOrder.setGoodsDetail(payParam.getGoodsDetail());
normalOrder.setTerminalNo(terminalNo);
// 客户端IP(支付入口已兜底, 作为单一事实源供退款/关单等后续流程取用)
normalOrder.setClientIp(payParam.getClientIp());
// 通道路由参数(同步时用于解析通道应用凭证)
normalOrder.setChannelMchNo(payParam.getChannelMchNo());
normalOrder.setCapability(payParam.getCapability());
// 通道应用 AppId: doBeforePay 已将解析后的实际值回填到 payParam
normalOrder.setChannelAppId(payParam.getChannelAppId());
normalOrder.setAppId(appId);
payNormalOrderManager.save(normalOrder);
// 创建资金交易 PayTrade
PayTrade trade = new PayTrade();
trade.setAppId(appId);
trade.setTradeNo(tradeNo);
trade.setTradeType(PayTradeTypeEnum.NORMAL.getCode());
trade.setContainerId(normalOrder.getId());
trade.setAmount(amount);
trade.setCurrency(CurrencyEnum.CNY.getCode());
// 入账金额: 未成功前为 0, 成功后由 PayUniHandleService 按规则回写
trade.setPostedAmount(0L);
trade.setRefundableBalance(amount);
trade.setStatus(PayFundStatusEnum.PROCESSING.getCode());
// 实际上送串权威: 默认=orderNo, 特殊通道支付返回后可覆盖
trade.setRelationOrderNo(orderNo);
// 来源: 协议层(如易支付)可显式传入, 默认商户 API
String source = StrUtil.isNotBlank(payParam.getSource())
? payParam.getSource()
: cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum.MCH_API.getCode();
trade.setSource(source);
payTradeManager.save(trade);
context.setTrade(trade);
context.setNormalOrder(normalOrder);
// 注册超时关单延时消息(按订单过期时间定时投递)
this.registerTimeoutClose(trade.getTradeNo(), normalOrder.getBizOrderNo(), expiredTime);
// --- 终端与客户端 ---
normalOrder.setTerminalNo(terminalNo);
// 客户端IP(支付入口已兜底, 作为单一事实源供退款/关单等后续流程取用)
normalOrder.setClientIp(payParam.getClientIp());
// --- 回调与扩展 ---
normalOrder.setNotifyUrl(payParam.getNotifyUrl());
normalOrder.setReturnUrl(payParam.getReturnUrl());
normalOrder.setAttach(payParam.getAttach());
normalOrder.setExtraParam(payParam.getExtraParam());
normalOrder.setGoodsDetail(payParam.getGoodsDetail());
normalOrder.setAppId(payParam.getAppId());
return normalOrder;
}
/// 组装资金交易(未落库); relationOrderNo 默认=orderNo, 特殊通道支付返回后可覆盖
private PayTrade buildPayTrade(NormalPayParam payParam, NormalPayOrder normalOrder,
String tradeNo, String orderNo, String source) {
return PayTradeInitUtil.initProcessing(
payParam.getAppId(),
tradeNo,
PayTradeTypeEnum.NORMAL.getCode(),
normalOrder.getId(),
payParam.getAmount(),
orderNo,
source);
}
/// 注册超时关单延时消息

View File

@@ -0,0 +1,52 @@
package cn.daxpay.open.payment.trade.util;
import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.trade.order.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.CurrencyEnum;
/// # 资金交易初始化工具
///
/// PROCESSING 态资金凭证公共字段的单一事实源。
/// 调用方负责 tradeType / container / source 等业务差异,本工具只填「待处理结算类交易」公共规则:
/// - currency = CNY
/// - postedAmount = 0成功后由 [cn.daxpay.open.payment.trade.runtime.service.pay.common.PayUniHandleService]
/// / [PayTradeAmountUtil] 按规则回写)
/// - refundableBalance = amount
/// - status = PROCESSING
public final class PayTradeInitUtil {
private PayTradeInitUtil() {
}
/// 组装未落库的 PROCESSING 资金凭证
///
/// @param appId 应用 ID
/// @param tradeNo 资金交易号(调用方生成)
/// @param tradeType 交易形态normal / gateway 等)
/// @param containerId 关联容器 ID须已落库
/// @param amount 交易金额
/// @param relationOrderNo 实际上送串(默认与业务单号对齐,特殊通道可后覆盖)
/// @param source 订单来源(冗余自容器)
public static PayTrade initProcessing(
String appId,
String tradeNo,
String tradeType,
Long containerId,
Long amount,
String relationOrderNo,
String source) {
return new PayTrade()
.setAppId(appId)
.setTradeNo(tradeNo)
.setTradeType(tradeType)
.setContainerId(containerId)
.setAmount(amount)
.setCurrency(CurrencyEnum.CNY.getCode())
// 入账金额: 未成功前为 0
.setPostedAmount(0L)
.setRefundableBalance(amount)
.setStatus(PayFundStatusEnum.PROCESSING.getCode())
.setRelationOrderNo(relationOrderNo)
.setSource(source);
}
}