refactor(payment): 移除 PayBizContainer 过度抽象, 路由参数冗余至 PayTrade

- PayTrade 新增 channelMchNo/capability/bizOrderNo/clientIp 四个冗余字段
- 删除 PayBizContainer 接口, NormalPayOrder/GatewayPayOrder 去掉 implements
- PayStrategyContext 删除 container 字段, 策略直接从 trade 读路由参数
- PaySyncService/PayCloseService/PayCallbackService 删除 loadContainer, 免查容器
- 30 个通道 Sync/Close 策略统一改写(消除 null 检查)
- AlipayCallbackService 回调凭证从 trade 读, 省掉 NormalPayOrder 查询
This commit is contained in:
DaxPay Dev
2026-07-11 09:18:50 +08:00
parent 2b5ac311ff
commit f8bc2e204a
39 changed files with 300 additions and 335 deletions

View File

@@ -209,3 +209,138 @@ ALTER TABLE device_qr_code ALTER COLUMN mch_no DROP NOT NULL;
COMMENT ON COLUMN device_qr_code.batch_no IS '批次号(批量创建空白码时写入)';
COMMENT ON COLUMN device_qr_code.mch_no IS '所属商户号(空白码为空, 划拨后写入)';
-- ------------------------------------------------------------
-- 网关支付业务单(容器): 预下单时创建, 支付时再挂 pay_trade
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS pay_gateway_order (
id int8 NOT NULL,
mch_no varchar(32) NOT NULL,
app_id varchar(32) NOT NULL,
order_no varchar(64) NOT NULL,
biz_order_no varchar(64) NOT NULL,
gateway_type varchar(32) NOT NULL,
title varchar(128) NOT NULL,
description varchar(512),
amount int8 NOT NULL,
currency varchar(8) NOT NULL DEFAULT 'CNY',
status varchar(32) NOT NULL,
notify_url varchar(256),
return_url varchar(256),
attach varchar(512),
expired_time timestamptz(6),
channel varchar(32),
method varchar(32),
product varchar(32),
capability varchar(64),
channel_mch_no varchar(64),
scene varchar(32),
device varchar(16),
pay_time timestamptz(6),
close_time timestamptz(6),
client_ip varchar(64),
terminal_no varchar(64),
goods_detail jsonb,
creator int8,
create_time timestamptz(6),
last_modifier int8,
last_modified_time timestamptz(6),
version int4 NOT NULL DEFAULT 0,
deleted bool NOT NULL DEFAULT false,
CONSTRAINT pk_pay_gateway_order PRIMARY KEY (id)
);
COMMENT ON TABLE pay_gateway_order IS '网关支付业务单容器(聚合扫码/收银台预下单)';
COMMENT ON COLUMN pay_gateway_order.id IS '主键';
COMMENT ON COLUMN pay_gateway_order.mch_no IS '商户号';
COMMENT ON COLUMN pay_gateway_order.app_id IS '应用号';
COMMENT ON COLUMN pay_gateway_order.order_no IS '平台网关单号(URL用)';
COMMENT ON COLUMN pay_gateway_order.biz_order_no IS '商户业务单号';
COMMENT ON COLUMN pay_gateway_order.gateway_type IS '网关类型: cashier/aggregate';
COMMENT ON COLUMN pay_gateway_order.title IS '标题';
COMMENT ON COLUMN pay_gateway_order.description IS '描述';
COMMENT ON COLUMN pay_gateway_order.amount IS '金额(最小货币单位)';
COMMENT ON COLUMN pay_gateway_order.currency IS '币种';
COMMENT ON COLUMN pay_gateway_order.status IS '业务状态: wait_pay/paying/paid/closed/expired';
COMMENT ON COLUMN pay_gateway_order.notify_url IS '异步通知地址';
COMMENT ON COLUMN pay_gateway_order.return_url IS '同步跳转地址';
COMMENT ON COLUMN pay_gateway_order.attach IS '商户附加参数';
COMMENT ON COLUMN pay_gateway_order.expired_time IS '过期时间';
COMMENT ON COLUMN pay_gateway_order.channel IS '支付通道(支付后冗余)';
COMMENT ON COLUMN pay_gateway_order.method IS '支付方式(支付后冗余)';
COMMENT ON COLUMN pay_gateway_order.product IS '支付产品(支付后冗余)';
COMMENT ON COLUMN pay_gateway_order.capability IS '支付能力(路由回填)';
COMMENT ON COLUMN pay_gateway_order.channel_mch_no IS '通道商户号(路由回填)';
COMMENT ON COLUMN pay_gateway_order.scene IS '收银场景 wechat_pay/alipay/union_pay';
COMMENT ON COLUMN pay_gateway_order.device IS '最后发起设备 mobile/pc';
COMMENT ON COLUMN pay_gateway_order.pay_time IS '支付成功时间';
COMMENT ON COLUMN pay_gateway_order.close_time IS '关闭时间';
COMMENT ON COLUMN pay_gateway_order.client_ip IS '客户端IP';
COMMENT ON COLUMN pay_gateway_order.terminal_no IS '终端设备编码';
COMMENT ON COLUMN pay_gateway_order.goods_detail IS '商品明细(jsonb)';
COMMENT ON COLUMN pay_gateway_order.creator IS '创建者ID';
COMMENT ON COLUMN pay_gateway_order.create_time IS '创建时间';
COMMENT ON COLUMN pay_gateway_order.last_modifier IS '最后修改者ID';
COMMENT ON COLUMN pay_gateway_order.last_modified_time IS '最后修改时间';
COMMENT ON COLUMN pay_gateway_order.version IS '版本号(乐观锁)';
COMMENT ON COLUMN pay_gateway_order.deleted IS '逻辑删除标记';
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_gateway_order_no ON pay_gateway_order (order_no) WHERE deleted = false;
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_gateway_biz ON pay_gateway_order (app_id, biz_order_no) WHERE deleted = false;
CREATE INDEX IF NOT EXISTS idx_pay_gateway_status_expired ON pay_gateway_order (status, expired_time);
CREATE INDEX IF NOT EXISTS idx_pay_gateway_mch_time ON pay_gateway_order (mch_no, create_time);
-- ------------------------------------------------------------
-- 网关聚合扫码配置(应用级一行)
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS pay_gateway_aggregate_config (
id int8 NOT NULL,
mch_no varchar(32) NOT NULL,
app_id varchar(32) NOT NULL,
wx_product varchar(32),
wx_method varchar(32),
alipay_product varchar(32),
alipay_method varchar(32),
union_product varchar(32),
union_method varchar(32),
auto_launch bool NOT NULL DEFAULT false,
creator int8,
create_time timestamptz(6),
last_modifier int8,
last_modified_time timestamptz(6),
version int4 NOT NULL DEFAULT 0,
deleted bool NOT NULL DEFAULT false,
CONSTRAINT pk_pay_gateway_aggregate_config PRIMARY KEY (id)
);
COMMENT ON TABLE pay_gateway_aggregate_config IS '网关聚合扫码支付配置(应用级)';
COMMENT ON COLUMN pay_gateway_aggregate_config.id IS '主键';
COMMENT ON COLUMN pay_gateway_aggregate_config.mch_no IS '商户号';
COMMENT ON COLUMN pay_gateway_aggregate_config.app_id IS '应用号';
COMMENT ON COLUMN pay_gateway_aggregate_config.wx_product IS '微信场景支付产品';
COMMENT ON COLUMN pay_gateway_aggregate_config.wx_method IS '微信场景支付方式';
COMMENT ON COLUMN pay_gateway_aggregate_config.alipay_product IS '支付宝场景支付产品';
COMMENT ON COLUMN pay_gateway_aggregate_config.alipay_method IS '支付宝场景支付方式';
COMMENT ON COLUMN pay_gateway_aggregate_config.union_product IS '云闪付场景支付产品';
COMMENT ON COLUMN pay_gateway_aggregate_config.union_method IS '云闪付场景支付方式';
COMMENT ON COLUMN pay_gateway_aggregate_config.auto_launch IS '是否自动拉起支付';
COMMENT ON COLUMN pay_gateway_aggregate_config.creator IS '创建者ID';
COMMENT ON COLUMN pay_gateway_aggregate_config.create_time IS '创建时间';
COMMENT ON COLUMN pay_gateway_aggregate_config.last_modifier IS '最后修改者ID';
COMMENT ON COLUMN pay_gateway_aggregate_config.last_modified_time IS '最后修改时间';
COMMENT ON COLUMN pay_gateway_aggregate_config.version IS '版本号(乐观锁)';
COMMENT ON COLUMN pay_gateway_aggregate_config.deleted IS '逻辑删除标记';
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_gateway_aggregate_app ON pay_gateway_aggregate_config (app_id) WHERE deleted = false;
-- ------------------------------------------------------------
-- pay_trade: 冗余路由参数 + 业务单号 + 客户端IP, 消除 sync/close/callback 对容器的依赖
-- ------------------------------------------------------------
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS biz_order_no varchar(64);
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS channel_mch_no varchar(64);
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS capability varchar(64);
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS client_ip varchar(64);
COMMENT ON COLUMN pay_trade.biz_order_no IS '商户业务单号(冗余自容器, 供同步/关闭/回调等流程直接读取)';
COMMENT ON COLUMN pay_trade.channel_mch_no IS '通道商户号(路由回填, 冗余自容器, 供策略层直接读取)';
COMMENT ON COLUMN pay_trade.capability IS '支付能力编码(路由回填, 冗余自容器, 供策略层直接读取)';
COMMENT ON COLUMN pay_trade.client_ip IS '客户端IP(冗余自容器, 供关闭/同步等策略读取)';

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.adapay.service.direct.AdapayDirectConfigAssembler;
import cn.daxpay.open.channel.adapay.service.payment.close.AdapayCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
@@ -29,13 +28,10 @@ public class AdapayDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext 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();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
AdapaySdkCredential credential = adapayDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return adapayCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.adapay.service.payment.sync.AdapaySyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class AdapayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
AdapaySdkCredential credential = adapayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return adapaySyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -161,16 +161,12 @@ public class AlipayCallbackService {
if (trade == null) {
return null;
}
NormalPayOrder normal = normalPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (normal == null) {
return null;
}
// 按支付产品分发: 服务商 / 直连
if (ProductEnum.ALIPAY_ISV.getCode().equals(trade.getProduct())) {
return alipayIsvConfigAssembler.buildConfig(trade.getMchNo());
}
return alipayDirectConfigAssembler.buildConfig(
trade.getMchNo(), normal.getChannelMchNo(), normal.getCapability());
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
}
/// 提取 request 全部表单参数为 Map<String,String>(多值取首项)

View File

@@ -4,7 +4,6 @@ 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.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -16,7 +15,7 @@ import org.springframework.stereotype.Service;
/// # 支付宝直连支付关闭策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下的支付关闭策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [AlipayDirectConfigAssembler]), 关闭执行委托给 [AlipayCloseService]。
@Slf4j
@Service
@@ -33,15 +32,12 @@ public class AlipayDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return alipayCloseService.close(trade, credential, useCancel);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.sync.AlipaySyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 支付宝直连支付同步策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下的支付同步策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [AlipayDirectConfigAssembler]), 同步执行委托给 [AlipaySyncService]。
@Slf4j
@Service
@@ -32,14 +31,10 @@ public class AlipayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return alipaySyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.dougong.service.isv.DougongIsvConfigAssembler;
import cn.daxpay.open.channel.dougong.service.payment.DougongCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -31,15 +30,12 @@ public class DougongCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
DougongSdkCredential credential = dougongIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
String clientIp = normalOrder != null ? normalOrder.getClientIp() : null;
return dougongCloseService.close(trade, credential, useCancel, clientIp);
return dougongCloseService.close(trade, credential, useCancel, trade.getClientIp());
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.dougong.service.payment.DougongSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class DougongSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DougongSdkCredential credential = dougongIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return dougongSyncService.sync(context.getTrade(), credential);
}

View File

@@ -4,7 +4,6 @@ import cn.daxpay.open.channel.douyin.client.credential.DouyinSdkCredential;
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectConfigAssembler;
import cn.daxpay.open.channel.douyin.service.payment.close.DouyinCloseService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -16,7 +15,7 @@ import org.springframework.stereotype.Service;
/// # 抖音直连支付关闭策略
///
/// 抖音直连模式(ProductEnum.DOUYIN_PAY)下的支付关闭策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从交易凭证读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [DouyinDirectConfigAssembler]), 关闭执行委托给 [DouyinCloseService]。
@Slf4j
@Service
@@ -33,13 +32,10 @@ public class DouyinDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext 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();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DouyinSdkCredential credential = douyinDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return douyinCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectConfigAssembler;
import cn.daxpay.open.channel.douyin.service.payment.sync.DouyinSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 抖音直连支付同步策略
///
/// 抖音直连模式(ProductEnum.DOUYIN_PAY)下的支付同步策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从交易凭证读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [DouyinDirectConfigAssembler]), 同步执行委托给 [DouyinSyncService]。
@Slf4j
@Service
@@ -32,12 +31,9 @@ public class DouyinDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DouyinSdkCredential credential = douyinDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return douyinSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.fuyou.service.isv.FuyouIsvConfigAssembler;
import cn.daxpay.open.channel.fuyou.service.payment.FuyouCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -31,13 +30,11 @@ public class FuyouCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
FuyouSdkCredential credential = fuyouIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return fuyouCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.fuyou.service.payment.FuyouSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class FuyouSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
FuyouSdkCredential credential = fuyouIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return fuyouSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.hkrt.service.isv.HkrtIsvConfigAssembler;
import cn.daxpay.open.channel.hkrt.service.payment.HkrtCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -34,18 +33,13 @@ public class HkrtCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
// 组装通道调用凭证
HkrtSdkCredential credential = hkrtIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
// 客户端IP取自原下单订单(超时关单等非HTTP场景亦可用)
String clientIp = normalOrder != null ? normalOrder.getClientIp() : null;
return hkrtCloseService.close(trade, credential, useCancel, clientIp);
return hkrtCloseService.close(trade, credential, useCancel, trade.getClientIp());
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.hkrt.service.payment.HkrtSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -31,14 +30,11 @@ public class HkrtSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
HkrtSdkCredential credential = hkrtIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return hkrtSyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.hmpay.service.isv.HmpayIsvConfigAssembler;
import cn.daxpay.open.channel.hmpay.service.payment.HmpayCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -31,13 +30,10 @@ public class HmpayCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext 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();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
HmpaySdkCredential credential = hmpayIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return hmpayCloseService.close(trade, credential);
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.hmpay.service.payment.HmpaySyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class HmpaySyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
HmpaySdkCredential credential = hmpayIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return hmpaySyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.lakala.service.isv.LakalaIsvConfigAssembler;
import cn.daxpay.open.channel.lakala.service.payment.LakalaCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 拉卡拉服务商支付关闭策略
///
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [LakalaIsvConfigAssembler]), 关闭执行委托给 [LakalaCloseService]。
///
/// 注意: 拉卡拉仅提供关单接口, 无撤销接口(useCancel 参数由 service 层忽略)。
@@ -34,18 +33,13 @@ public class LakalaCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
// 组装通道调用凭证
LakalaSdkCredential credential = lakalaIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
// 客户端IP取自原下单订单(超时关单等非HTTP场景亦可用)
String clientIp = normalOrder != null ? normalOrder.getClientIp() : null;
return lakalaCloseService.close(trade, credential, useCancel, clientIp);
return lakalaCloseService.close(trade, credential, useCancel, trade.getClientIp());
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.lakala.service.payment.LakalaSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -14,7 +13,7 @@ import org.springframework.stereotype.Service;
/// # 拉卡拉服务商支付同步策略
///
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [LakalaIsvConfigAssembler]), 同步执行委托给 [LakalaSyncService]。
@Slf4j
@Service
@@ -31,14 +30,10 @@ public class LakalaSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
LakalaSdkCredential credential = lakalaIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return lakalaSyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.leshua.service.isv.LeshuaIsvConfigAssembler;
import cn.daxpay.open.channel.leshua.service.payment.LeshuaCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -31,14 +30,10 @@ public class LeshuaCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext 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();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
LeshuaSdkCredential credential = leshuaIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
String clientIp = normalOrder != null ? normalOrder.getClientIp() : null;
return leshuaCloseService.close(trade, credential, useCancel, clientIp);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return leshuaCloseService.close(trade, credential, useCancel, trade.getClientIp());
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.leshua.service.payment.LeshuaSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -30,12 +29,9 @@ public class LeshuaSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
LeshuaSdkCredential credential = leshuaIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return leshuaSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.ums.service.direct.UmsDirectConfigAssembler;
import cn.daxpay.open.channel.ums.service.payment.close.UmsCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
@@ -29,13 +28,11 @@ public class UmsDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
UmsSdkCredential credential = umsDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return umsCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.ums.service.payment.sync.UmsSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class UmsDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
UmsSdkCredential credential = umsDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return umsSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.vbill.service.isv.VbillIsvConfigAssembler;
import cn.daxpay.open.channel.vbill.service.payment.VbillCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 随行付服务商支付关闭策略
///
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从交易凭证读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [VbillIsvConfigAssembler]), 关闭执行委托给 [VbillCloseService]。
///
/// 注意: 随行付仅提供关单接口(`/query/close`), 无撤销接口(useCancel 参数由 service 层忽略)。
@@ -34,15 +33,12 @@ public class VbillCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext 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();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
VbillSdkCredential credential = vbillIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return vbillCloseService.close(trade, credential, useCancel);
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.vbill.service.payment.VbillSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -14,7 +13,7 @@ import org.springframework.stereotype.Service;
/// # 随行付服务商支付同步策略
///
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从交易凭证读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [VbillIsvConfigAssembler]), 同步执行委托给 [VbillSyncService]。
@Slf4j
@Service
@@ -31,14 +30,11 @@ public class VbillSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
VbillSdkCredential credential = vbillIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return vbillSyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.wechat.service.direct.WechatDirectConfigAssembler;
import cn.daxpay.open.channel.wechat.service.payment.close.WechatCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -16,7 +15,7 @@ import org.springframework.stereotype.Service;
/// # 微信直连支付关闭策略
///
/// 微信直连模式(ProductEnum.WECHAT_PAY)下的支付关闭策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [WechatDirectConfigAssembler]), 关闭执行委托给 [WechatCloseService]。
///
/// 注意: 微信 V3 仅提供关单接口, 无撤销接口(useCancel 参数由 service 层忽略)。
@@ -35,15 +34,12 @@ public class WechatDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
// 组装通道调用凭证
WechatSdkCredential credential = wechatDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return wechatCloseService.close(trade, credential, useCancel);
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.wechat.service.payment.sync.WechatSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 微信直连支付同步策略
///
/// 微信直连模式(ProductEnum.WECHAT_PAY)下的支付同步策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [WechatDirectConfigAssembler]), 同步执行委托给 [WechatSyncService]。
@Slf4j
@Service
@@ -32,14 +31,10 @@ public class WechatDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
WechatSdkCredential credential = wechatDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return wechatSyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
@@ -16,7 +15,7 @@ import org.springframework.stereotype.Service;
/// # 微信服务商支付关闭策略
///
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的支付关闭策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 关闭执行委托给 [WechatCloseService]。
///
/// 注意: 微信 V3 仅提供关单接口, 无撤销接口(useCancel 参数由 service 层忽略)。
@@ -35,15 +34,12 @@ public class WechatIsvCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
// 组装服务商通道调用凭证
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return wechatIsvCloseService.close(trade, credential, useCancel);
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvSyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Service;
/// # 微信服务商支付同步策略
///
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的支付同步策略。
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
/// 从 trade 读取通道路由参数(channelMchNo / capability),
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 同步执行委托给 [WechatSyncService]。
@Slf4j
@Service
@@ -32,14 +31,10 @@ public class WechatIsvSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
// 从上下文容器读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装服务商通道调用凭证
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return wechatIsvSyncService.sync(context.getTrade(), credential);
}

View File

@@ -5,7 +5,6 @@ import cn.daxpay.open.channel.yeepay.service.direct.YeepayDirectConfigAssembler;
import cn.daxpay.open.channel.yeepay.service.payment.close.YeepayCloseService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
@@ -29,13 +28,11 @@ public class YeepayDirectCloseStrategy extends AbsPayCloseStrategy {
@Override
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
PayTrade trade = context.getTrade();
YeepaySdkCredential credential = yeepayDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return yeepayCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -6,7 +6,6 @@ import cn.daxpay.open.channel.yeepay.service.payment.sync.YeepaySyncService;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,12 +27,9 @@ public class YeepayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
@Override
public PaySyncResultBo doSync(PayStrategyContext context) {
NormalPayOrder normalOrder = context.getContainer();
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 直接从 trade 读取路由参数, 不再需要 container 中间层
YeepaySdkCredential credential = yeepayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), channelMchNo, capability);
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
return yeepaySyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -1,6 +1,5 @@
package cn.daxpay.open.payment.core.strategy.pay;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import lombok.Getter;
@@ -9,8 +8,9 @@ import lombok.experimental.Accessors;
/// # 支付策略执行上下文(请求级,显式传参)
///
/// 持有单笔支付策略管线的"容器(业务单) + 资金凭证 + 请求参数 + 通道配置"要素,
/// 持有单笔支付策略管线的"资金凭证 + 请求参数 + 通道配置"要素,
/// 每次支付/同步/关闭创建新实例,显式传递,不进 ThreadLocal。
/// 路由参数(channelMchNo / capability)已冗余到 [PayTrade],策略直接从 trade 读取。
///
/// 与线程级身份上下文严格区分:
/// - 本类 = 请求级数据载体(函数传参)
@@ -20,10 +20,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class PayStrategyContext {
/// 普通支付业务单容器(承载 bizOrderNo / 回调地址 / 通道路由等业务信息)
private NormalPayOrder container;
/// 资金交易凭证(记录本笔资金动作的状态与通道回执)
/// 资金交易凭证(记录本笔资金动作的状态与通道回执, 含路由参数)
private PayTrade trade;
/// 普通支付请求参数(pay 流程必填;sync/close 流程可为空)

View File

@@ -1,6 +1,5 @@
package cn.daxpay.open.payment.core.trade.convert;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
import org.mapstruct.Mapper;
@@ -9,17 +8,15 @@ import org.mapstruct.factory.Mappers;
/// # 支付交易转换器
///
/// 仅从 PayTrade 映射对外支付响应;同名字段(status/bizOrderNo/payBody 等)自动映射,
/// 仅声明改名字段
@Mapper
public interface PayTradeConvert {
PayTradeConvert CONVERT = Mappers.getMapper(PayTradeConvert.class);
/// PayTrade + NormalPayOrder → NormalPayResult
@Mapping(target = "orderId", source = "trade.id")
@Mapping(target = "orderNo", source = "trade.tradeNo")
@Mapping(target = "status", source = "trade.status")
@Mapping(target = "bizOrderNo", source = "normalOrder.bizOrderNo")
@Mapping(target = "payBody", source = "trade.payBody")
@Mapping(target = "payBodyType", source = "trade.payBodyType")
NormalPayResult toResult(PayTrade trade, NormalPayOrder normalOrder);
/// PayTrade → NormalPayResult
@Mapping(target = "orderId", source = "id")
@Mapping(target = "orderNo", source = "tradeNo")
NormalPayResult toResult(PayTrade trade);
}

View File

@@ -39,6 +39,9 @@ public class PayTrade extends MchBaseEntity {
/// normal → pay_normal_order / authorize/capture → pay_auth / recurring → pay_recurring / combine_sub → pay_combine
private Long containerId;
/// 商户业务单号(冗余自容器, 供同步/关闭/回调等流程直接读取, 免查容器)
private String bizOrderNo;
/// 支付产品编码,策略工厂通过此字段创建策略
/// @see cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum
private String product;
@@ -125,6 +128,16 @@ public class PayTrade extends MchBaseEntity {
/// 支付参数体类型jsapi/sdk/app
private String payBodyType;
/// 通道商户号(路由回填, 冗余自容器, 供策略层直接读取)
private String channelMchNo;
/// 支付能力编码(路由回填, 冗余自容器, 供策略层直接读取)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum
private String capability;
/// 客户端 IP(冗余自容器, 供关闭/同步等策略读取)
private String clientIp;
/// 错误信息
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorMsg;

View File

@@ -78,7 +78,7 @@ public class NormalPayService {
return payAssistService.buildResult(context.getTrade());
}
// 订单不存在则新建(填充 context
if (Objects.isNull(context.getContainer())) {
if (Objects.isNull(context.getTrade())) {
payAssistService.createOrder(payParam, context);
}
PayTrade trade = context.getTrade();

View File

@@ -87,9 +87,13 @@ public class PayAssistService {
trade.setTradeNo(TradeNoGenerateUtil.pay());
trade.setTradeType(PayTradeTypeEnum.NORMAL.getCode());
trade.setContainerId(normalOrder.getId());
trade.setBizOrderNo(payParam.getBizOrderNo());
trade.setProduct(payParam.getProduct());
trade.setChannel(channel);
trade.setMethod(payParam.getMethod());
trade.setChannelMchNo(payParam.getChannelMchNo());
trade.setCapability(payParam.getCapability());
trade.setClientIp(payParam.getClientIp());
trade.setLimitPay(payParam.getLimitPay() != null
? String.join(",", payParam.getLimitPay()) : null);
trade.setAmount(amount);
@@ -101,7 +105,7 @@ public class PayAssistService {
trade.setBarCode(payParam.getAuthCode());
trade.setOpenid(payParam.getOpenId());
payTradeManager.save(trade);
context.setContainer(normalOrder).setTrade(trade);
context.setTrade(trade);
// 注册超时关单延时消息(按订单过期时间定时投递)
this.registerTimeoutClose(trade.getTradeNo(), normalOrder.getBizOrderNo(), expiredTime);
}
@@ -130,12 +134,12 @@ public class PayAssistService {
return;
}
NormalPayOrder normalOrder = normalOrderOpt.get();
PayTrade trade = payTradeManager.findByContainerId(normalOrder.getId()).orElse(null);
PayTrade trade = payTradeManager.findByContainerId(normalOrder.getId(), PayTradeTypeEnum.NORMAL.getCode()).orElse(null);
if (trade == null) {
return;
}
this.checkOrder(normalOrder, trade);
context.setContainer(normalOrder).setTrade(trade);
context.setTrade(trade);
}
/// 检查订单状态
@@ -167,8 +171,7 @@ public class PayAssistService {
/// 根据 PayTrade 构建支付结果
public NormalPayResult buildResult(PayTrade trade) {
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
return PayTradeConvert.CONVERT.toResult(trade, normalOrder);
return PayTradeConvert.CONVERT.toResult(trade);
}
/// 获取支付超时时间

View File

@@ -1,13 +1,10 @@
package cn.daxpay.open.payment.core.trade.service;
import cn.daxpay.open.platform.core.enums.pay.notice.CallbackStatusEnum;
import cn.daxpay.open.platform.core.exception.system.DataErrorException;
import cn.daxpay.open.payment.common.callback.CallbackData;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.notice.CallbackStatusEnum;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
@@ -26,7 +23,6 @@ import java.util.Objects;
public class PayCallbackService {
private final PayTradeManager payTradeManager;
private final NormalPayOrderManager payNormalOrderManager;
private final PayUniHandleService payUniHandleService;
private final LockTemplate lockTemplate;
@@ -55,12 +51,10 @@ public class PayCallbackService {
if (Objects.nonNull(callbackData.getOutTradeNo())) {
trade.setOutOrderNo(callbackData.getOutTradeNo());
}
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId())
.orElseThrow(() -> new DataErrorException("error.payment.order.payOrderNotExist"));
if (Objects.equals(CallbackStatusEnum.SUCCESS.getCode(), callbackData.getTradeStatus())) {
this.success(trade, normalOrder, callbackData);
this.success(trade, callbackData);
} else {
this.fail(trade, normalOrder, callbackData);
this.fail(trade, callbackData);
}
return trade.getProduct();
} finally {
@@ -68,8 +62,7 @@ public class PayCallbackService {
}
}
/// 成功处理
private void success(PayTrade trade, NormalPayOrder normalOrder, CallbackData callbackData) {
private void success(PayTrade trade, CallbackData callbackData) {
trade.setStatus(PayFundStatusEnum.SUCCESS.getCode());
trade.setPayTime(callbackData.getFinishTime());
trade.setCloseTime(null);
@@ -79,13 +72,12 @@ public class PayCallbackService {
payUniHandleService.paySuccess(trade);
}
/// 失败处理
private void fail(PayTrade trade, NormalPayOrder normalOrder, CallbackData callbackData) {
private void fail(PayTrade trade, CallbackData callbackData) {
if (!Objects.equals(trade.getStatus(), PayFundStatusEnum.PROCESSING.getCode())) {
callbackData.setCallbackStatus(CallbackStatusEnum.IGNORE)
.setCallbackErrorMsg("订单不是支付中状态,忽略");
return;
}
payUniHandleService.payClose(trade, normalOrder, false);
payUniHandleService.payClose(trade, false);
}
}

View File

@@ -1,23 +1,24 @@
package cn.daxpay.open.payment.core.trade.service;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
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.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.core.strategy.PaymentStrategyFactory;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.record.entity.PayCloseRecord;
import cn.daxpay.open.payment.core.trade.record.service.PayCloseRecordService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayCloseParam;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
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.hutool.core.util.StrUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
@@ -29,8 +30,6 @@ import java.util.List;
import java.util.Objects;
/// # 支付关闭和撤销服务
///
/// 关闭(close): 本地置 CLOSE; 撤销(cancel): 本地置 CANCEL, 并通过 useCancel 触发通道撤销
@Slf4j
@Service
@RequiredArgsConstructor
@@ -42,18 +41,17 @@ public class PayCloseService {
private final PayCloseRecordService payCloseRecordService;
private final LockTemplate lockTemplate;
/// 关闭支付
/// 关闭支付(商户 API)
public void close(NormalPayCloseParam param) {
if (StrUtil.isBlank(param.getOrderNo()) && Objects.isNull(param.getBizOrderNo())) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.orderNoRequired");
}
PayTrade trade = payTradeManager.findByTradeNo(param.getOrderNo())
.orElse(null);
PayTrade trade = payTradeManager.findByTradeNo(param.getOrderNo()).orElse(null);
if (Objects.isNull(trade) && Objects.nonNull(param.getBizOrderNo())) {
NormalPayOrder normalOrder = payNormalOrderManager.findByBizOrderNo(param.getBizOrderNo())
.orElse(null);
if (Objects.nonNull(normalOrder)) {
trade = payTradeManager.findByContainerId(normalOrder.getId())
trade = payTradeManager.findByContainerId(normalOrder.getId(), PayTradeTypeEnum.NORMAL.getCode())
.orElse(null);
}
}
@@ -74,29 +72,24 @@ public class PayCloseService {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.closeProcessing");
}
try {
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId())
.orElse(null);
CloseTypeEnum closeType;
if (Objects.equals(PayFundStatusEnum.INIT.getCode(), trade.getStatus())) {
// 待支付无通道交易, 仅本地关闭
closeType = CloseTypeEnum.CLOSE;
payUniHandleService.payClose(trade, normalOrder, false);
payUniHandleService.payClose(trade, false);
} else {
// 处理中: 调用通道关闭/撤销策略
closeType = useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE;
var context = new PayStrategyContext()
.setContainer(normalOrder)
.setTrade(trade);
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(context);
strategy.doClose(context, useCancel);
payUniHandleService.payClose(trade, normalOrder, useCancel);
payUniHandleService.payClose(trade, useCancel);
}
this.saveRecord(trade, normalOrder, closeType, null);
this.saveRecord(trade, closeType, null);
} catch (Exception e) {
log.error("关闭订单失败, id: {}:", trade.getId(), e);
this.saveRecord(trade, null, useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE, e.getMessage());
this.saveRecord(trade, useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE, e.getMessage());
if (e instanceof PayFailureException) {
throw e;
}
@@ -107,75 +100,54 @@ public class PayCloseService {
}
/// 超时自动关单(幂等)
///
/// 供 MQ 延时消息消费者 [cn.daxpay.open.payment.core.trade.mq.NormalPayTimeoutConsumer]
/// 与兜底定时任务 [cn.daxpay.open.payment.core.trade.job.NormalPayTimeoutJob] 调用。
///
/// 与 [#closeOrder] 的区别:
/// - 仅接受 tradeNo 定位, 非 PROCESSING 状态静默返回(幂等, 不抛异常)
/// - closeType 固定 TIMEOUT, 容器态置 EXPIRED(见 [PayUniHandleService#payTimeout])
/// - 不接受 useCancel, 超时统一走 close
/// - 通道关单失败不阻断本地关闭, 仅记录失败原因, 通道侧由后续同步兜底
public void closeForTimeout(String tradeNo) {
PayTrade trade = payTradeManager.findByTradeNo(tradeNo).orElse(null);
if (Objects.isNull(trade)) {
// 订单不存在, 静默返回
return;
}
// 幂等校验: 仅处理中需超时关闭; 已成功/失败/关闭/撤销 直接返回
if (!Objects.equals(PayFundStatusEnum.PROCESSING.getCode(), trade.getStatus())) {
return;
}
// 复用与手动关单相同的锁键, 保证两条路径互斥
LockInfo lock = lockTemplate.lock("payment:close:" + trade.getId(), 10000, 50);
if (Objects.isNull(lock)) {
// 并发关单进行中, 当前触发交由兜底任务后续补救
log.warn("超时关单获取锁失败(并发关单进行中), 交由兜底任务处理, tradeNo={}", tradeNo);
return;
}
try {
// 加锁后再次校验状态(防止加锁期间已被其他路径处理)
trade = payTradeManager.findByTradeNo(tradeNo).orElse(null);
if (Objects.isNull(trade)
|| !Objects.equals(PayFundStatusEnum.PROCESSING.getCode(), trade.getStatus())) {
return;
}
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId())
.orElse(null);
String errMsg = null;
try {
// 处理中: 调用通道关闭策略(超时统一走 close, 不走 cancel)
var context = new PayStrategyContext()
.setContainer(normalOrder)
.setTrade(trade);
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(context);
strategy.doClose(context, false);
} catch (Exception e) {
// 通道关单失败不阻断本地关闭, 通道侧状态由后续同步兜底
errMsg = e.getMessage();
log.warn("超时关单调用通道关闭失败, 仅本地关闭, tradeNo={}", tradeNo, e);
}
payUniHandleService.payTimeout(trade, normalOrder);
this.saveRecord(trade, normalOrder, CloseTypeEnum.TIMEOUT, errMsg);
payUniHandleService.payTimeout(trade);
this.saveRecord(trade, CloseTypeEnum.TIMEOUT, errMsg);
} finally {
lockTemplate.releaseLock(lock);
}
}
/// 保存关闭记录
private void saveRecord(PayTrade trade, NormalPayOrder normalOrder, CloseTypeEnum closeType, String errMsg) {
private void saveRecord(PayTrade trade, CloseTypeEnum closeType, String errMsg) {
PayCloseRecord record = new PayCloseRecord()
.setAppId(trade.getAppId())
.setTradeNo(trade.getTradeNo())
.setBizTradeNo(Objects.nonNull(normalOrder) ? normalOrder.getBizOrderNo() : null)
.setBizTradeNo(trade.getBizOrderNo())
.setProduct(trade.getProduct())
.setChannel(trade.getChannel())
.setClosed(StrUtil.isBlank(errMsg))
.setCloseType(closeType.getCode())
.setErrorMsg(errMsg);
// 商户号显式赋值, 不依赖线程上下文自动填充(异步通知/定时任务等非HTTP场景上下文缺失会导致填充null)
record.setMchNo(trade.getMchNo());
payCloseRecordService.saveRecord(record);
}

View File

@@ -1,5 +1,20 @@
package cn.daxpay.open.payment.core.trade.service;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.core.strategy.PaymentStrategyFactory;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.record.entity.PaySyncRecord;
import cn.daxpay.open.payment.core.trade.record.service.PaySyncRecordService;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPaySyncParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
@@ -7,20 +22,6 @@ 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.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.core.strategy.PaymentStrategyFactory;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.record.entity.PaySyncRecord;
import cn.daxpay.open.payment.core.trade.record.service.PaySyncRecordService;
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPaySyncParam;
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult;
import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
@@ -36,9 +37,6 @@ import java.util.Objects;
import java.util.Optional;
/// # 支付同步服务
///
/// 通道策略层返回同步结果, 本服务负责本地资金凭证(PayTrade)与通道状态对齐,
/// 含: 状态比对、超时主动关网关、成功/失败/关闭调整, 以及同步流水记录持久化
@Slf4j
@Service
@RequiredArgsConstructor
@@ -65,7 +63,7 @@ public class PaySyncService {
NormalPayOrder normalOrder = payNormalOrderManager.findByBizOrderNo(
param.getBizOrderNo()).orElse(null);
if (Objects.nonNull(normalOrder)) {
trade = payTradeManager.findByContainerId(normalOrder.getId())
trade = payTradeManager.findByContainerId(normalOrder.getId(), PayTradeTypeEnum.NORMAL.getCode())
.orElse(null);
}
}
@@ -90,10 +88,7 @@ public class PaySyncService {
throw new RepetitiveOperationException();
}
try {
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId())
.orElse(null);
var context = new PayStrategyContext()
.setContainer(normalOrder)
.setTrade(trade);
var syncStrategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsSyncPayOrderStrategy.class);
@@ -105,13 +100,12 @@ public class PaySyncService {
boolean statusSync = this.checkAndAdjust(syncResult, trade);
if (!statusSync) {
try {
this.adjustHandler(syncResult, trade, normalOrder);
this.adjustHandler(syncResult, trade);
} catch (PayFailureException e) {
syncResult.setSyncSuccess(false).setSyncErrorMsg(e.getMessage());
}
}
// 保存同步流水记录(成功/失败均记录)
this.saveRecord(trade, normalOrder, syncResult, !statusSync);
this.saveRecord(trade, syncResult, !statusSync);
return new NormalPaySyncResult()
.setOrderStatus(trade.getStatus())
.setAdjust(statusSync);
@@ -120,20 +114,16 @@ public class PaySyncService {
}
}
/// 判断本地与通道状态是否一致, 并在本地支付中且超时时标记需要远程关网关
private boolean checkAndAdjust(PaySyncResultBo syncResult, PayTrade trade) {
var payStatus = Optional.ofNullable(syncResult.getPayStatus())
.orElse(PayFundStatusEnum.PROCESSING);
String orderStatus = trade.getStatus();
// 本地已失败, 直接返回需要调整
if (orderStatus.equals(PayFundStatusEnum.FAIL.getCode())) {
return false;
}
if (orderStatus.equals(PayFundStatusEnum.PROCESSING.getCode())) {
// 通道也是处理中, 进一步判断是否超时
if (Objects.equals(PayFundStatusEnum.PROCESSING, payStatus)) {
if (DateTimeUtil.le(trade.getExpiredTime(), OffsetDateTime.now(ZoneOffset.UTC))) {
// 本地超时且通道订单仍存活, 标记需要主动关闭网关交易
syncResult.setPayStatus(PayFundStatusEnum.CLOSE);
syncResult.setRemoteClose(true);
return false;
@@ -141,36 +131,32 @@ public class PaySyncService {
return true;
}
} else {
// 本地非处理中(已成功/已关闭/已撤销), 视为一致
return true;
}
return false;
}
/// 根据同步结果调整资金凭证状态
private void adjustHandler(PaySyncResultBo syncResult, PayTrade trade, NormalPayOrder normalOrder) {
private void adjustHandler(PaySyncResultBo syncResult, PayTrade trade) {
var payStatus = syncResult.getPayStatus();
if (Objects.isNull(payStatus)) {
return;
}
switch (payStatus) {
case PROCESSING -> {}
case SUCCESS -> this.success(trade, normalOrder, syncResult);
case SUCCESS -> this.success(trade, syncResult);
case CLOSE -> {
// 通道已关闭 → 本地关闭; 超时(remoteClose) → 主动调用通道关单后本地关闭
if (syncResult.isRemoteClose()) {
this.closeRemote(trade, normalOrder);
this.closeRemote(trade);
} else {
payUniHandleService.payClose(trade, normalOrder, false);
payUniHandleService.payClose(trade, false);
}
}
case FAIL -> payUniHandleService.payFail(trade, normalOrder, syncResult.getSyncErrorMsg());
case FAIL -> payUniHandleService.payFail(trade, syncResult.getSyncErrorMsg());
default -> throw new SystemUnknownErrorException();
}
}
/// 同步成功后更新资金凭证与容器
private void success(PayTrade trade, NormalPayOrder normalOrder, PaySyncResultBo syncResult) {
private void success(PayTrade trade, PaySyncResultBo syncResult) {
trade.setStatus(PayFundStatusEnum.SUCCESS.getCode());
trade.setPayTime(syncResult.getFinishTime());
trade.setCloseTime(null);
@@ -186,27 +172,21 @@ public class PaySyncService {
payUniHandleService.paySuccess(trade);
}
/// 主动关闭网关交易(超时场景), 调用通道关闭策略后置超时关闭态
/// 资金态置 CLOSE, 容器态置 EXPIRED(见 [PayUniHandleService#payTimeout]),
/// 与 MQ 延时关单 / 兜底定时任务 三条路径统一落点。
private void closeRemote(PayTrade trade, NormalPayOrder normalOrder) {
private void closeRemote(PayTrade trade) {
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
var context = new PayStrategyContext()
.setContainer(normalOrder)
.setTrade(trade);
strategy.doBeforeClose(context);
strategy.doClose(context, false);
// 超时关闭: 资金态置 CLOSE, 容器态置 EXPIRED(区分业务语义)
payUniHandleService.payTimeout(trade, normalOrder);
payUniHandleService.payTimeout(trade);
}
/// 保存同步流水记录
private void saveRecord(PayTrade trade, NormalPayOrder normalOrder, PaySyncResultBo syncResult, boolean adjust) {
private void saveRecord(PayTrade trade, PaySyncResultBo syncResult, boolean adjust) {
PaySyncRecord record = new PaySyncRecord()
.setAppId(trade.getAppId())
.setTradeNo(trade.getTradeNo())
.setBizTradeNo(Objects.nonNull(normalOrder) ? normalOrder.getBizOrderNo() : null)
.setBizTradeNo(trade.getBizOrderNo())
.setOutTradeNo(trade.getOutOrderNo())
.setOutTradeStatus(Objects.nonNull(syncResult.getPayStatus())
? syncResult.getPayStatus().getCode() : null)
@@ -217,7 +197,6 @@ public class PaySyncService {
.setAdjust(adjust)
.setErrorCode(syncResult.getSyncErrorCode())
.setErrorMsg(syncResult.getSyncErrorMsg());
// 商户号显式赋值, 不依赖线程上下文自动填充(异步通知/定时任务等非HTTP场景上下文缺失会导致填充null)
record.setMchNo(trade.getMchNo());
paySyncRecordService.saveRecord(record);
}

View File

@@ -11,27 +11,26 @@ import org.mapstruct.factory.Mappers;
///
/// 将 core 双表(NormalPayOrder 业务容器 + PayTrade 资金凭证)映射为对外契约 NormalPayOrderResult
/// 资金态字段(status / outOrderNo / refundableBalance 等)取自 PayTrade, 业务字段取自 NormalPayOrder
///
/// 仅声明:改名、多源同名消歧、以及目标独有字段的 ignore由服务层后填或暂不输出
@Mapper
public interface UnipayNormalPayOrderConvert {
UnipayNormalPayOrderConvert CONVERT = Mappers.getMapper(UnipayNormalPayOrderConvert.class);
/// NormalPayOrder + PayTrade → 对外 NormalPayOrderResult
// 改名
@Mapping(target = "orderNo", source = "trade.tradeNo")
@Mapping(target = "outOrderNo", source = "trade.outOrderNo")
@Mapping(target = "otherMethod", source = "trade.otherMethod")
@Mapping(target = "limitPay", source = "trade.limitPay")
@Mapping(target = "refundableBalance", source = "trade.refundableBalance")
// 多源同名消歧:资金态取 trade业务容器取 order
@Mapping(target = "status", source = "trade.status")
@Mapping(target = "provider", source = "trade.provider")
@Mapping(target = "buyerId", source = "trade.buyerId")
@Mapping(target = "errorMsg", source = "trade.errorMsg")
@Mapping(target = "bizOrderNo", source = "order.bizOrderNo")
@Mapping(target = "channel", source = "order.channel")
@Mapping(target = "method", source = "order.method")
@Mapping(target = "amount", source = "order.amount")
@Mapping(target = "payTime", source = "order.payTime")
@Mapping(target = "closeTime", source = "order.closeTime")
@Mapping(target = "expiredTime", source = "order.expiredTime")
// 目标独有字段:源无对应属性,显式 ignore 标明不在此转换
@Mapping(target = "realAmount", ignore = true)
@Mapping(target = "refundStatus", ignore = true)
NormalPayOrderResult toResult(NormalPayOrder order, PayTrade trade);