mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 22:25:32 +08:00
feat(pay): pay_trade 冗余 channelMchNo 与 storeNo
资金凭证增加通道商户号、门店号轻量组织冗余,建单时从容器/路由结果拷贝,便于资金列表与店级汇总免 JOIN;同步 DDL 与查询返回字段,并补齐普通支付门店校验落库。
This commit is contained in:
@@ -206,3 +206,15 @@ CREATE INDEX IF NOT EXISTS idx_normal_order_mch_store ON pay_normal_order (mch_n
|
||||
CREATE INDEX IF NOT EXISTS idx_gateway_order_mch_store ON pay_gateway_order (mch_no, store_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_refund_order_mch_store ON pay_refund_order (mch_no, store_no);
|
||||
|
||||
-- ============================================================
|
||||
-- pay_trade 轻量组织冗余: channel_mch_no + store_no
|
||||
-- ============================================================
|
||||
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS channel_mch_no varchar(64);
|
||||
COMMENT ON COLUMN pay_trade.channel_mch_no IS '通道商户号(冗余自业务容器, 路由确定后写入)';
|
||||
|
||||
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS store_no varchar(32);
|
||||
COMMENT ON COLUMN pay_trade.store_no IS '门店号(冗余自业务容器, 可空; 权威在容器)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pay_trade_mch_channel ON pay_trade (mch_no, channel_mch_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_pay_trade_mch_store ON pay_trade (mch_no, store_no);
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ import java.time.OffsetDateTime;
|
||||
///
|
||||
/// 统一资金交易表,记录每一笔资金动作(普通支付/预授权冻结/预授权捕获/周期代扣/合单子单)
|
||||
/// 与容器层(业务单/协议)分离,通过 trade_type + 容器关联字段建立联系。
|
||||
/// 仅保留资金动作固有属性与通道反查命脉字段;业务上下文/路由/payBody/回执统一归容器。
|
||||
/// 保留资金动作固有属性、通道反查命脉字段,以及 **轻量组织冗余**
|
||||
///(source / channelMchNo / storeNo),便于资金列表与汇总免 JOIN 容器;
|
||||
/// 完整业务上下文/路由细节/payBody/回执仍以容器为准。
|
||||
/// 过期时间只在容器,本表不存 expiredTime。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@@ -64,6 +66,12 @@ public class PayTrade extends MchBaseEntity {
|
||||
/// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum
|
||||
private String source;
|
||||
|
||||
/// 通道商户号(冗余自业务容器, 路由确定后写入; 权威在容器 channelMchNo)
|
||||
private String channelMchNo;
|
||||
|
||||
/// 门店号(冗余自业务容器, 可空; 权威在容器 storeNo)
|
||||
private String storeNo;
|
||||
|
||||
/// 通道订单号(三方通道返回的订单号,通道 close/sync 调用参数 + 回调反查索引)
|
||||
private String outOrderNo;
|
||||
|
||||
|
||||
@@ -63,6 +63,16 @@ public class PayTradeQuery {
|
||||
@Schema(description = "支付产品")
|
||||
private String product;
|
||||
|
||||
/// 通道商户号
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
/// 门店号
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
@Schema(description = "门店号")
|
||||
private String storeNo;
|
||||
|
||||
/// 关联容器ID
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
@Schema(description = "关联容器ID")
|
||||
|
||||
@@ -103,6 +103,14 @@ public class PayTradeResult extends BaseResult {
|
||||
@Schema(description = "订单来源")
|
||||
private String source;
|
||||
|
||||
/// 通道商户号(冗余自业务容器)
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
/// 门店号(冗余自业务容器, 可空)
|
||||
@Schema(description = "门店号")
|
||||
private String storeNo;
|
||||
|
||||
/// 通道订单号
|
||||
@Schema(description = "通道订单号")
|
||||
private String outOrderNo;
|
||||
|
||||
@@ -143,6 +143,7 @@ public class GatewayPayHandleService {
|
||||
}
|
||||
|
||||
// 默认上送网关业务单号; 特殊通道返回后可覆盖
|
||||
// channelMchNo 取自路由后的 payParam; storeNo 冗余自预下单容器
|
||||
PayTrade trade = PayTradeInitUtil.initProcessing(
|
||||
order.getAppId(),
|
||||
TradeNoGenerateUtil.pay(),
|
||||
@@ -150,7 +151,9 @@ public class GatewayPayHandleService {
|
||||
order.getId(),
|
||||
order.getAmount(),
|
||||
order.getOrderNo(),
|
||||
source);
|
||||
source,
|
||||
payParam.getChannelMchNo(),
|
||||
order.getStoreNo());
|
||||
payTradeManager.save(trade);
|
||||
|
||||
this.fillRouteOnOrder(order, payParam, clientEnv, device);
|
||||
|
||||
@@ -9,11 +9,13 @@ import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.util.DateTimeUtil;
|
||||
import cn.daxpay.open.platform.core.util.TradeNoGenerateUtil;
|
||||
import cn.daxpay.open.payment.common.context.PaymentContext;
|
||||
import cn.daxpay.open.payment.common.util.PayUtil;
|
||||
import cn.daxpay.open.payment.merchant.service.store.MchStoreInfoService;
|
||||
import cn.daxpay.open.payment.strategy.pay.PayStrategyContext;
|
||||
import cn.daxpay.open.payment.trade.enums.NormalPayOrderStatusEnum;
|
||||
import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum;
|
||||
import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum;
|
||||
import cn.daxpay.open.payment.common.util.PayUtil;
|
||||
import cn.daxpay.open.payment.strategy.pay.PayStrategyContext;
|
||||
import cn.daxpay.open.payment.trade.order.dao.NormalPayOrderManager;
|
||||
import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.payment.trade.order.dao.PayTradeManager;
|
||||
@@ -44,6 +46,8 @@ public class NormalPayAssistService {
|
||||
private final NormalPayOrderManager payNormalOrderManager;
|
||||
private final PayTradeManager payTradeManager;
|
||||
private final ArtemisTemplateService artemisTemplateService;
|
||||
private final MchStoreInfoService mchStoreInfoService;
|
||||
private final PaymentContext paymentContext;
|
||||
|
||||
/// 创建支付订单(容器 + 资金交易),填充到 context
|
||||
/// 调用方需保证 appId 和 product 已解析完毕
|
||||
@@ -51,12 +55,15 @@ public class NormalPayAssistService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createOrder(NormalPayParam payParam, PayStrategyContext context) {
|
||||
OffsetDateTime expiredTime = this.getExpiredTime(payParam.getExpiredTime());
|
||||
// 门店号(线下归属, 可空; 有值则校验存在/归属/启用)
|
||||
String storeNo = payParam.getTerminal() != null ? payParam.getTerminal().getStoreNo() : null;
|
||||
mchStoreInfoService.validateStoreForPay(storeNo, paymentContext.getMchNo());
|
||||
// 双号独立生成
|
||||
String orderNo = TradeNoGenerateUtil.order();
|
||||
String tradeNo = TradeNoGenerateUtil.pay();
|
||||
String source = resolveSource(payParam);
|
||||
|
||||
NormalPayOrder normalOrder = buildNormalOrder(payParam, orderNo, expiredTime, source);
|
||||
NormalPayOrder normalOrder = buildNormalOrder(payParam, orderNo, expiredTime, source, storeNo);
|
||||
payNormalOrderManager.save(normalOrder);
|
||||
|
||||
PayTrade trade = buildPayTrade(payParam, normalOrder, tradeNo, orderNo, source);
|
||||
@@ -77,7 +84,7 @@ public class NormalPayAssistService {
|
||||
|
||||
/// 组装普通支付业务容器(未落库)
|
||||
private NormalPayOrder buildNormalOrder(NormalPayParam payParam, String orderNo,
|
||||
OffsetDateTime expiredTime, String source) {
|
||||
OffsetDateTime expiredTime, String source, String storeNo) {
|
||||
// 从产品编码派生通道编码
|
||||
var productEnum = ProductEnum.findByCode(payParam.getProduct());
|
||||
String channel = productEnum.getChannel();
|
||||
@@ -111,8 +118,10 @@ public class NormalPayAssistService {
|
||||
normalOrder.setCapability(payParam.getCapability());
|
||||
// 通道应用 AppId: doBeforePay 已将解析后的实际值回填到 payParam
|
||||
normalOrder.setChannelAppId(payParam.getChannelAppId());
|
||||
// --- 终端与客户端 ---
|
||||
// --- 终端 / 门店 / 客户端 ---
|
||||
normalOrder.setTerminalNo(terminalNo);
|
||||
// 门店号: 线下经营归属权威落容器
|
||||
normalOrder.setStoreNo(storeNo);
|
||||
// 客户端IP(支付入口已兜底, 作为单一事实源供退款/关单等后续流程取用)
|
||||
normalOrder.setClientIp(payParam.getClientIp());
|
||||
// --- 回调与扩展 ---
|
||||
@@ -126,6 +135,7 @@ public class NormalPayAssistService {
|
||||
}
|
||||
|
||||
/// 组装资金交易(未落库); relationOrderNo 默认=orderNo, 特殊通道支付返回后可覆盖
|
||||
/// channelMchNo / storeNo 冗余自容器,资金列表免 JOIN
|
||||
private PayTrade buildPayTrade(NormalPayParam payParam, NormalPayOrder normalOrder,
|
||||
String tradeNo, String orderNo, String source) {
|
||||
return PayTradeInitUtil.initProcessing(
|
||||
@@ -135,7 +145,9 @@ public class NormalPayAssistService {
|
||||
normalOrder.getId(),
|
||||
payParam.getAmount(),
|
||||
orderNo,
|
||||
source);
|
||||
source,
|
||||
normalOrder.getChannelMchNo(),
|
||||
normalOrder.getStoreNo());
|
||||
}
|
||||
|
||||
/// 注册超时关单延时消息
|
||||
|
||||
@@ -7,7 +7,8 @@ import cn.daxpay.open.platform.core.enums.pay.channel.CurrencyEnum;
|
||||
/// # 资金交易初始化工具
|
||||
///
|
||||
/// PROCESSING 态资金凭证公共字段的单一事实源。
|
||||
/// 调用方负责 tradeType / container / source 等业务差异,本工具只填「待处理结算类交易」公共规则:
|
||||
/// 调用方负责 tradeType / container / source / channelMchNo / storeNo 等业务差异,本工具只填
|
||||
/// 「待处理结算类交易」公共规则:
|
||||
/// - currency = CNY
|
||||
/// - postedAmount = 0(成功后由 [cn.daxpay.open.payment.trade.runtime.service.pay.common.PayUniHandleService]
|
||||
/// / [PayTradeAmountUtil] 按规则回写)
|
||||
@@ -27,6 +28,8 @@ public final class PayTradeInitUtil {
|
||||
/// @param amount 交易金额
|
||||
/// @param relationOrderNo 实际上送串(默认与业务单号对齐,特殊通道可后覆盖)
|
||||
/// @param source 订单来源(冗余自容器)
|
||||
/// @param channelMchNo 通道商户号(冗余自容器/路由结果,可空仅当路由未定时)
|
||||
/// @param storeNo 门店号(冗余自容器,可空)
|
||||
public static PayTrade initProcessing(
|
||||
String appId,
|
||||
String tradeNo,
|
||||
@@ -34,7 +37,9 @@ public final class PayTradeInitUtil {
|
||||
Long containerId,
|
||||
Long amount,
|
||||
String relationOrderNo,
|
||||
String source) {
|
||||
String source,
|
||||
String channelMchNo,
|
||||
String storeNo) {
|
||||
return new PayTrade()
|
||||
.setAppId(appId)
|
||||
.setTradeNo(tradeNo)
|
||||
@@ -47,6 +52,9 @@ public final class PayTradeInitUtil {
|
||||
.setRefundableBalance(amount)
|
||||
.setStatus(PayFundStatusEnum.PROCESSING.getCode())
|
||||
.setRelationOrderNo(relationOrderNo)
|
||||
.setSource(source);
|
||||
.setSource(source)
|
||||
// 轻量组织冗余: 权威在业务容器
|
||||
.setChannelMchNo(channelMchNo)
|
||||
.setStoreNo(storeNo);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user