ref 支付交易相关内容调整

This commit is contained in:
DaxPay Dev
2026-07-12 15:48:51 +08:00
parent a53dae3d76
commit f021a5d002
59 changed files with 657 additions and 262 deletions

View File

@@ -464,4 +464,94 @@ COMMENT ON COLUMN pay_gateway_cashier_item.deleted IS '逻辑删除标记';
CREATE INDEX IF NOT EXISTS idx_pay_gateway_cashier_item_list
ON pay_gateway_cashier_item (app_id, cashier_type, scene)
WHERE deleted = false;
WHERE deleted = false;
-- ============================================================
-- PayTrade 瘦身: 业务/路由/回执字段归容器, trade 仅留资金动作固有属性
-- ============================================================
-- pay_normal_order 补充 14 字段(请求参数 + 回执 + 通道关联 + 错误)
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS other_method varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS limit_pay varchar(128);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS openid varchar(128);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS bar_code varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS error_msg varchar(500);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS buyer_id varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS buyer_logon_id varchar(128);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS trade_product varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS trade_way varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS bank_type varchar(32);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS promotion_type varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS provider varchar(32);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS trans_order_no varchar(64);
ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS relation_order_no varchar(64);
COMMENT ON COLUMN pay_normal_order.other_method IS '其他支付方式, method=other 时生效';
COMMENT ON COLUMN pay_normal_order.limit_pay IS '限制支付类型(如限制信用卡)';
COMMENT ON COLUMN pay_normal_order.openid IS '微信 openid(jsapi/app/miniapp)';
COMMENT ON COLUMN pay_normal_order.bar_code IS '付款码(被扫支付)';
COMMENT ON COLUMN pay_normal_order.error_msg IS '错误信息';
COMMENT ON COLUMN pay_normal_order.buyer_id IS '付款用户 ID(支付宝 buyer_user_id 等)';
COMMENT ON COLUMN pay_normal_order.buyer_logon_id IS '买家登录账号(支付宝手机号/邮箱)';
COMMENT ON COLUMN pay_normal_order.trade_product IS '通道方记录的支付产品';
COMMENT ON COLUMN pay_normal_order.trade_way IS '通道方记录的交易方式';
COMMENT ON COLUMN pay_normal_order.bank_type IS '银行卡类型(借记卡/贷记卡)';
COMMENT ON COLUMN pay_normal_order.promotion_type IS '活动类型';
COMMENT ON COLUMN pay_normal_order.provider IS '支付渠道(微信/支付宝/银联等)';
COMMENT ON COLUMN pay_normal_order.trans_order_no IS '透传订单号(三方通道产生的透传订单号)';
COMMENT ON COLUMN pay_normal_order.relation_order_no IS '特殊通道关联订单号(部分通道订单号有前缀/长度限制时使用)';
-- pay_gateway_order 补充 14 字段
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS other_method varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS limit_pay varchar(128);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS openid varchar(128);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS bar_code varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS error_msg varchar(500);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS buyer_id varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS buyer_logon_id varchar(128);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS trade_product varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS trade_way varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS bank_type varchar(32);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS promotion_type varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS provider varchar(32);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS trans_order_no varchar(64);
ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS relation_order_no varchar(64);
COMMENT ON COLUMN pay_gateway_order.other_method IS '其他支付方式, method=other 时生效';
COMMENT ON COLUMN pay_gateway_order.limit_pay IS '限制支付类型(如限制信用卡)';
COMMENT ON COLUMN pay_gateway_order.openid IS '微信 openid(jsapi/app/miniapp)';
COMMENT ON COLUMN pay_gateway_order.bar_code IS '付款码(被扫支付)';
COMMENT ON COLUMN pay_gateway_order.error_msg IS '错误信息';
COMMENT ON COLUMN pay_gateway_order.buyer_id IS '付款用户 ID(支付宝 buyer_user_id 等)';
COMMENT ON COLUMN pay_gateway_order.buyer_logon_id IS '买家登录账号(支付宝手机号/邮箱)';
COMMENT ON COLUMN pay_gateway_order.trade_product IS '通道方记录的支付产品';
COMMENT ON COLUMN pay_gateway_order.trade_way IS '通道方记录的交易方式';
COMMENT ON COLUMN pay_gateway_order.bank_type IS '银行卡类型(借记卡/贷记卡)';
COMMENT ON COLUMN pay_gateway_order.promotion_type IS '活动类型';
COMMENT ON COLUMN pay_gateway_order.provider IS '支付渠道(微信/支付宝/银联等)';
COMMENT ON COLUMN pay_gateway_order.trans_order_no IS '透传订单号(三方通道产生的透传订单号)';
COMMENT ON COLUMN pay_gateway_order.relation_order_no IS '特殊通道关联订单号(部分通道订单号有前缀/长度限制时使用)';
-- pay_trade 删除 22 字段(已归容器)
ALTER TABLE pay_trade DROP COLUMN IF EXISTS biz_order_no;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS product;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS channel;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS method;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS other_method;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS limit_pay;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS provider;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS expired_time;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS trans_order_no;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS relation_order_no;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS buyer_id;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS buyer_logon_id;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS openid;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS trade_product;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS trade_way;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS bank_type;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS bar_code;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS promotion_type;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS channel_mch_no;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS capability;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS client_ip;
ALTER TABLE pay_trade DROP COLUMN IF EXISTS error_msg;

View File

@@ -31,7 +31,7 @@ public class AdapayDirectCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
AdapaySdkCredential credential = adapayDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return adapayCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -29,7 +29,7 @@ public class AdapayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
AdapaySdkCredential credential = adapayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return adapaySyncService.sync(context.getTrade(), credential);
}
}

View File

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

View File

@@ -55,7 +55,7 @@ public class AlipayPayService {
// 通道通知地址: 始终使用平台生成的回调地址(支付宝→平台), 不使用 payParam.notifyUrl(语义为平台→商户)
req.setNotifyUrl(this.buildNotifyUrl(order));
// 关单时间取自订单(createOrder 已对 null 兜底默认30分钟), 子应用据此向支付宝设置 time_expire
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
req.setCredential(credential);
// 调用子应用

View File

@@ -37,7 +37,7 @@ public class AlipayDirectCloseStrategy extends AbsPayCloseStrategy {
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return alipayCloseService.close(trade, credential, useCancel);
}

View File

@@ -34,7 +34,7 @@ public class AlipayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return alipaySyncService.sync(context.getTrade(), credential);
}

View File

@@ -52,7 +52,7 @@ public class DougongPayService {
req.setOpenId(payParam.getOpenId());
req.setClientIp(payParam.getClientIp());
req.setNotifyUrl(this.buildNotifyUrl(order));
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
req.setCredential(credential);
// 调用子应用

View File

@@ -34,8 +34,8 @@ public class DougongCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
DougongSdkCredential credential = dougongIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return dougongCloseService.close(trade, credential, useCancel, trade.getClientIp());
return dougongCloseService.close(trade, credential, useCancel, context.getClientIp());
}
}

View File

@@ -29,7 +29,7 @@ public class DougongSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DougongSdkCredential credential = dougongIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return dougongSyncService.sync(context.getTrade(), credential);
}

View File

@@ -35,7 +35,7 @@ public class DouyinDirectCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DouyinSdkCredential credential = douyinDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return douyinCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -33,7 +33,7 @@ public class DouyinDirectSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
DouyinSdkCredential credential = douyinDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return douyinSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -7,7 +7,9 @@ import cn.daxpay.open.channel.fuyou.client.resp.FuyouCallbackParseResp;
import cn.daxpay.open.channel.fuyou.dao.isv.FuyouIsvKeyConfigManager;
import cn.daxpay.open.channel.fuyou.entity.isv.FuyouIsvKeyConfig;
import cn.daxpay.open.payment.common.callback.CallbackData;
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.service.PayCallbackService;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
@@ -40,6 +42,7 @@ public class FuyouPayCallbackService {
private final FuyouChannelClient fuyouChannelClient;
private final FuyouIsvKeyConfigManager fuyouIsvKeyConfigManager;
private final PayTradeManager payTradeManager;
private final NormalPayOrderManager normalPayOrderManager;
private final PayCallbackService payCallbackService;
/// 支付回调处理
@@ -64,12 +67,16 @@ public class FuyouPayCallbackService {
return RESP_FAIL;
}
// 3. 凭关联订单号(mchnt_order_no)反查平台交易
// 3. 凭关联订单号(mchnt_order_no)反查容器, 再查关联交易
String relationOrderNo = resp.getOutTradeNo();
PayTrade trade = payTradeManager.lambdaQuery()
.eq(PayTrade::getRelationOrderNo, relationOrderNo)
NormalPayOrder normalOrder = normalPayOrderManager.lambdaQuery()
.eq(NormalPayOrder::getRelationOrderNo, relationOrderNo)
.oneOpt()
.orElse(null);
PayTrade trade = null;
if (normalOrder != null) {
trade = payTradeManager.findByContainerId(normalOrder.getId()).orElse(null);
}
if (trade == null) {
log.error("富友支付回调: 未找到关联订单 relationOrderNo={}", relationOrderNo);
return RESP_FAIL;

View File

@@ -5,6 +5,7 @@ import cn.daxpay.open.channel.fuyou.client.credential.FuyouSdkCredential;
import cn.daxpay.open.channel.fuyou.client.req.FuyouCloseReq;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.service.ContainerFieldResolver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -19,13 +20,14 @@ import org.springframework.stereotype.Service;
public class FuyouCloseService {
private final FuyouChannelClient fuyouChannelClient;
private final ContainerFieldResolver containerFieldResolver;
/// 关闭订单(富友无撤销, 统一关单)
public CloseTypeEnum close(PayTrade order, FuyouSdkCredential credential, boolean useCancel) {
FuyouCloseReq req = new FuyouCloseReq();
req.setCredential(credential);
req.setRelationOrderNo(order.getRelationOrderNo());
req.setTradeProduct(order.getTradeProduct());
req.setRelationOrderNo(containerFieldResolver.getRelationOrderNo(order));
req.setTradeProduct(containerFieldResolver.getTradeProduct(order));
fuyouChannelClient.close(req);
return CloseTypeEnum.CLOSE;
}

View File

@@ -10,6 +10,7 @@ import cn.daxpay.open.payment.core.trade.bo.RefundResultBo;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
import cn.daxpay.open.payment.core.trade.entity.PayRefundOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.service.ContainerFieldResolver;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.DataNotExistException;
@@ -31,6 +32,7 @@ public class FuyouRefundService {
private final FuyouChannelClient fuyouChannelClient;
private final PayTradeManager payTradeManager;
private final ContainerFieldResolver containerFieldResolver;
/// 执行退款
public RefundResultBo refund(PayRefundOrder refundOrder, FuyouSdkCredential credential) {
@@ -41,9 +43,9 @@ public class FuyouRefundService {
FuyouRefundReq req = new FuyouRefundReq();
req.setCredential(credential);
// 原支付订单的关联订单号(富友 mchnt_order_no)
req.setRelationOrderNo(originOrder.getRelationOrderNo());
req.setRelationOrderNo(containerFieldResolver.getRelationOrderNo(originOrder));
// 原支付厂商(富友 order_type)
req.setTradeProduct(originOrder.getTradeProduct());
req.setTradeProduct(containerFieldResolver.getTradeProduct(originOrder));
// 退款单号(平台 refundNo, 作为富友 refund_order_no)
req.setOutRefundNo(refundOrder.getRefundNo());
// 原订单总金额

View File

@@ -8,6 +8,7 @@ import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.service.ContainerFieldResolver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -29,14 +30,15 @@ public class FuyouSyncService {
private static final String STATUS_CLOSED = "CLOSED";
private final FuyouChannelClient fuyouChannelClient;
private final ContainerFieldResolver containerFieldResolver;
/// 同步订单状态
public PaySyncResultBo sync(PayTrade order, FuyouSdkCredential credential) {
FuyouSyncReq req = new FuyouSyncReq();
req.setCredential(credential);
// 富友凭关联订单号(mchnt_order_no) + order_type 查询
req.setRelationOrderNo(order.getRelationOrderNo());
req.setTradeProduct(order.getTradeProduct());
req.setRelationOrderNo(containerFieldResolver.getRelationOrderNo(order));
req.setTradeProduct(containerFieldResolver.getTradeProduct(order));
DaxResult<FuyouSyncResp> result = fuyouChannelClient.sync(req);
PaySyncResultBo bo = new PaySyncResultBo();

View File

@@ -34,7 +34,7 @@ public class FuyouCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
FuyouSdkCredential credential = fuyouIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return fuyouCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -29,7 +29,7 @@ public class FuyouSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
FuyouSdkCredential credential = fuyouIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return fuyouSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -60,7 +60,7 @@ public class HkrtPayService {
req.setAuthCode(payParam.getAuthCode());
req.setClientIp(payParam.getClientIp());
req.setNotifyUrl(this.buildNotifyUrl(order));
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
req.setCredential(credential);
// 调用子应用

View File

@@ -38,8 +38,8 @@ public class HkrtCloseStrategy extends AbsPayCloseStrategy {
// 组装通道调用凭证
HkrtSdkCredential credential = hkrtIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return hkrtCloseService.close(trade, credential, useCancel, trade.getClientIp());
return hkrtCloseService.close(trade, credential, useCancel, context.getClientIp());
}
}

View File

@@ -34,7 +34,7 @@ public class HkrtSyncStrategy extends AbsSyncPayOrderStrategy {
// 组装通道调用凭证
HkrtSdkCredential credential = hkrtIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return hkrtSyncService.sync(context.getTrade(), credential);
}

View File

@@ -33,7 +33,7 @@ public class HmpayCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
HmpaySdkCredential credential = hmpayIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return hmpayCloseService.close(trade, credential);
}

View File

@@ -29,7 +29,7 @@ public class HmpaySyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
HmpaySdkCredential credential = hmpayIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return hmpaySyncService.sync(context.getTrade(), credential);
}

View File

@@ -61,7 +61,7 @@ public class LakalaPayService {
req.setAuthCode(payParam.getAuthCode());
req.setClientIp(payParam.getClientIp());
req.setNotifyUrl(this.buildNotifyUrl(order));
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
req.setCredential(credential);
// 调用子应用

View File

@@ -38,8 +38,8 @@ public class LakalaCloseStrategy extends AbsPayCloseStrategy {
// 组装通道调用凭证
LakalaSdkCredential credential = lakalaIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return lakalaCloseService.close(trade, credential, useCancel, trade.getClientIp());
return lakalaCloseService.close(trade, credential, useCancel, context.getClientIp());
}
}

View File

@@ -33,7 +33,7 @@ public class LakalaSyncStrategy extends AbsSyncPayOrderStrategy {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
LakalaSdkCredential credential = lakalaIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return lakalaSyncService.sync(context.getTrade(), credential);
}

View File

@@ -33,7 +33,7 @@ public class LeshuaCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
// 直接从 trade 读取路由参数, 不再需要 container 中间层
LeshuaSdkCredential credential = leshuaIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
return leshuaCloseService.close(trade, credential, useCancel, trade.getClientIp());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return leshuaCloseService.close(trade, credential, useCancel, context.getClientIp());
}
}

View File

@@ -31,7 +31,7 @@ public class LeshuaSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
LeshuaSdkCredential credential = leshuaIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return leshuaSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -32,7 +32,7 @@ public class UmsDirectCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
UmsSdkCredential credential = umsDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return umsCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -29,7 +29,7 @@ public class UmsDirectSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
UmsSdkCredential credential = umsDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return umsSyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -62,7 +62,7 @@ public class VbillPayService {
req.setAuthCode(payParam.getAuthCode());
req.setClientIp(payParam.getClientIp());
req.setNotifyUrl(this.buildNotifyUrl(order));
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
req.setCredential(credential);
// 调用子应用

View File

@@ -38,7 +38,7 @@ public class VbillCloseStrategy extends AbsPayCloseStrategy {
// 组装通道调用凭证
VbillSdkCredential credential = vbillIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return vbillCloseService.close(trade, credential, useCancel);
}

View File

@@ -34,7 +34,7 @@ public class VbillSyncStrategy extends AbsSyncPayOrderStrategy {
// 组装通道调用凭证
VbillSdkCredential credential = vbillIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return vbillSyncService.sync(context.getTrade(), credential);
}

View File

@@ -56,7 +56,7 @@ public class WechatIsvPayService {
req.setAttach(payParam.getAttach());
req.setNotifyUrl(this.buildNotifyUrl(order));
// 关单时间取自订单(createOrder 已对 null 兜底默认30分钟), 不用 payParam 原始入参
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
if (req.getMethod() == WechatPayMethod.H5) {
req.setWapUrl(payParam.getReturnUrl());
req.setWapName(payParam.getTitle());

View File

@@ -56,7 +56,7 @@ public class WechatPayService {
// 通道通知地址: 始终使用平台生成的回调地址(微信→平台), 不使用 payParam.notifyUrl(语义为平台→商户)
req.setNotifyUrl(this.buildNotifyUrl(order));
// 关单时间取自订单(createOrder 已对 null 兜底默认30分钟), 不用 payParam 原始入参
req.setExpireTime(order.getExpiredTime());
req.setExpireTime(payParam.getExpiredTime());
// H5 场景信息(场景参数)
if (req.getMethod() == WechatPayMethod.H5) {
req.setWapUrl(payParam.getReturnUrl());

View File

@@ -39,7 +39,7 @@ public class WechatDirectCloseStrategy extends AbsPayCloseStrategy {
// 组装通道调用凭证
WechatSdkCredential credential = wechatDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return wechatCloseService.close(trade, credential, useCancel);
}

View File

@@ -34,7 +34,7 @@ public class WechatDirectSyncStrategy extends AbsSyncPayOrderStrategy {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装通道调用凭证
WechatSdkCredential credential = wechatDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return wechatSyncService.sync(context.getTrade(), credential);
}

View File

@@ -39,7 +39,7 @@ public class WechatIsvCloseStrategy extends AbsPayCloseStrategy {
// 组装服务商通道调用凭证
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return wechatIsvCloseService.close(trade, credential, useCancel);
}

View File

@@ -34,7 +34,7 @@ public class WechatIsvSyncStrategy extends AbsSyncPayOrderStrategy {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
// 组装服务商通道调用凭证
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return wechatIsvSyncService.sync(context.getTrade(), credential);
}

View File

@@ -32,7 +32,7 @@ public class YeepayDirectCloseStrategy extends AbsPayCloseStrategy {
PayTrade trade = context.getTrade();
YeepaySdkCredential credential = yeepayDirectConfigAssembler.buildConfig(
trade.getMchNo(), trade.getChannelMchNo(), trade.getCapability());
trade.getMchNo(), context.getChannelMchNo(), context.getCapability());
return yeepayCloseService.close(trade, credential, useCancel);
}
}

View File

@@ -29,7 +29,7 @@ public class YeepayDirectSyncStrategy extends AbsSyncPayOrderStrategy {
public PaySyncResultBo doSync(PayStrategyContext context) {
// 直接从 trade 读取路由参数, 不再需要 container 中间层
YeepaySdkCredential credential = yeepayDirectConfigAssembler.buildConfig(
context.getTrade().getMchNo(), context.getTrade().getChannelMchNo(), context.getTrade().getCapability());
context.getTrade().getMchNo(), context.getChannelMchNo(), context.getCapability());
return yeepaySyncService.sync(context.getTrade(), credential);
}
}

View File

@@ -8,9 +8,11 @@ import lombok.experimental.Accessors;
/// # 支付策略执行上下文(请求级,显式传参)
///
/// 持有单笔支付策略管线的"资金凭证 + 请求参数 + 通道配置"三要素,
/// 持有单笔支付策略管线的"资金凭证 + 通道配置 + 路由参数"三要素,
/// 每次支付/同步/关闭创建新实例,显式传递,不进 ThreadLocal。
/// 路由参数(channelMchNo / capability)已冗余到 [PayTrade],策略直接从 trade 读取。
///
/// close/sync 策略直接从本类读取路由参数(channelMchNo / capability / clientIp),
/// 这些字段由 service 层从容器提取后填入。
///
/// 与线程级身份上下文严格区分:
/// - 本类 = 请求级数据载体(函数传参)
@@ -20,7 +22,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class PayStrategyContext {
/// 资金交易凭证(记录本笔资金动作的状态与通道回执, 含路由参数)
/// 资金交易凭证(记录本笔资金动作的状态与通道回执)
private PayTrade trade;
/// 普通支付请求参数(pay 流程必填;sync/close 流程可为空)
@@ -29,6 +31,15 @@ public class PayStrategyContext {
/// 通道配置(doBeforePay 写入,doPay 读取;类型各通道不同)
private Object channelConfig;
/// 通道商户号(service 层从容器提取填入,供 close/sync 策略组装凭证)
private String channelMchNo;
/// 支付能力编码(service 层从容器提取填入,供 close/sync 策略组装凭证)
private String capability;
/// 客户端 IP(service 层从容器提取填入,供 close/sync 策略使用)
private String clientIp;
@SuppressWarnings("unchecked")
public <T> T getChannelConfig(Class<T> clazz) {

View File

@@ -4,12 +4,13 @@ import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.core.trade.result.PayTradeResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.mapstruct.ReportingPolicy;
/// # 资金交易凭证转换器(管理)
///
/// 仅做凭证(PayTrade)到列表 Result 的映射;
/// 详情场景的容器(NormalPayOrder)字段由 Service 层手动补充, 避免多参数源同名字段歧义
@Mapper
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PayTradeAdminConvert {
PayTradeAdminConvert CONVERT = Mappers.getMapper(PayTradeAdminConvert.class);

View File

@@ -58,13 +58,14 @@ public class PayTradeManager extends BaseManager<PayTradeMapper, PayTrade> {
}
/// 查询网关支付已超时但仍处理中的资金交易(兜底)
/// expiredTime 在容器(pay_gateway_order)上, 用子查询关联
@IgnoreTenant
public List<PayTrade> findGatewayTimeoutTrades(OffsetDateTime now) {
return lambdaQuery()
.eq(PayTrade::getTradeType, PayTradeTypeEnum.GATEWAY.getCode())
.eq(PayTrade::getStatus, PayFundStatusEnum.PROCESSING.getCode())
.lt(PayTrade::getExpiredTime, now)
.orderByAsc(PayTrade::getExpiredTime)
.apply("container_id IN (SELECT id FROM pay_gateway_order WHERE expired_time < {0})", now)
.orderByAsc(PayTrade::getCreateTime)
.last("limit 500")
.list();
}
@@ -80,15 +81,16 @@ public class PayTradeManager extends BaseManager<PayTradeMapper, PayTrade> {
/// 查询普通支付已超时但仍处理中的资金交易(兜底定时任务用)
///
/// 条件: tradeType=NORMAL 且 status=PROCESSING 且 expiredTime < now
/// 条件: tradeType=NORMAL 且 status=PROCESSING 且容器 expiredTime < now
/// expiredTime 在容器(pay_normal_order)上, 用子查询关联。
/// 跨租户扫描(定时任务无 HTTP 上下文), 单次上限 500 防积压爆量。
@IgnoreTenant
public List<PayTrade> findNormalTimeoutTrades(OffsetDateTime now) {
return lambdaQuery()
.eq(PayTrade::getTradeType, PayTradeTypeEnum.NORMAL.getCode())
.eq(PayTrade::getStatus, PayFundStatusEnum.PROCESSING.getCode())
.lt(PayTrade::getExpiredTime, now)
.orderByAsc(PayTrade::getExpiredTime)
.apply("container_id IN (SELECT id FROM pay_normal_order WHERE expired_time < {0})", now)
.orderByAsc(PayTrade::getCreateTime)
.last("limit 500")
.list();
}

View File

@@ -78,6 +78,13 @@ public class GatewayPayOrder extends MchBaseEntity {
/// @see PayMethodEnum
private String method;
/// 其他支付方式method=other 时生效
private String otherMethod;
/// 限制支付类型(如限制信用卡)
/// @see cn.daxpay.open.platform.core.enums.unipay.PayLimitPayEnum
private String limitPay;
/// 支付产品
/// @see ProductEnum
private String product;
@@ -88,6 +95,14 @@ public class GatewayPayOrder extends MchBaseEntity {
/// 通道商户号(路由回填)
private String channelMchNo;
// ===== 支付请求参数(支付时写入)=====
/// 微信 openidjsapi/app/miniapp
private String openid;
/// 付款码(被扫支付)
private String barCode;
/// 收银场景 wechat_pay/alipay/union_pay
private String scene;
@@ -106,7 +121,43 @@ public class GatewayPayOrder extends MchBaseEntity {
/// 终端设备编码
private String terminalNo;
// ===== 通道回执(支付成功/同步后写入)=====
/// 支付渠道(微信/支付宝/银联等,三方通道透传时填)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum
private String provider;
/// 付款用户 ID支付宝 buyer_user_id 等)
private String buyerId;
/// 买家登录账号(支付宝手机号/邮箱)
private String buyerLogonId;
/// 通道方记录的支付产品
private String tradeProduct;
/// 通道方记录的交易方式
private String tradeWay;
/// 银行卡类型(借记卡/贷记卡)
private String bankType;
/// 活动类型
private String promotionType;
// ===== 通道关联订单号(部分通道专用)=====
/// 透传订单号(三方通道产生的透传订单号)
private String transOrderNo;
/// 特殊通道关联订单号(部分通道订单号有前缀/长度限制时使用)
private String relationOrderNo;
/// 商品明细
@TableField(typeHandler = JacksonTypeHandler.class)
private List<GoodsDetail> goodsDetail;
/// 错误信息
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorMsg;
}

View File

@@ -76,10 +76,25 @@ public class NormalPayOrder extends MchBaseEntity {
/// @see PayMethodEnum
private String method;
/// 其他支付方式method=other 时生效
private String otherMethod;
/// 限制支付类型(如限制信用卡)
/// @see cn.daxpay.open.platform.core.enums.unipay.PayLimitPayEnum
private String limitPay;
/// 支付产品编码
/// @see ProductEnum
private String product;
// ===== 支付请求参数(下单时写入)=====
/// 微信 openidjsapi/app/miniapp
private String openid;
/// 付款码(被扫支付)
private String barCode;
// ===== 时间线(冗余,查询展示用)=====
/// 支付成功时间
@@ -97,6 +112,38 @@ public class NormalPayOrder extends MchBaseEntity {
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum
private String capability;
// ===== 通道回执(支付成功/同步后写入)=====
/// 支付渠道(微信/支付宝/银联等,三方通道透传时填)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum
private String provider;
/// 付款用户 ID支付宝 buyer_user_id 等)
private String buyerId;
/// 买家登录账号(支付宝手机号/邮箱)
private String buyerLogonId;
/// 通道方记录的支付产品
private String tradeProduct;
/// 通道方记录的交易方式
private String tradeWay;
/// 银行卡类型(借记卡/贷记卡)
private String bankType;
/// 活动类型
private String promotionType;
// ===== 通道关联订单号(部分通道专用)=====
/// 透传订单号(三方通道产生的透传订单号)
private String transOrderNo;
/// 特殊通道关联订单号(部分通道订单号有前缀/长度限制时使用)
private String relationOrderNo;
// ===== 请求信息(低频,审计排查用)=====
/// 通道附加参数
@@ -111,4 +158,8 @@ public class NormalPayOrder extends MchBaseEntity {
/// 终端设备编码
private String terminalNo;
/// 错误信息
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String errorMsg;
}

View File

@@ -1,7 +1,6 @@
package cn.daxpay.open.payment.core.trade.entity;
import cn.daxpay.open.payment.common.entity.merchant.MchBaseEntity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -16,17 +15,14 @@ import java.time.OffsetDateTime;
/// # 资金交易凭证
///
/// 统一资金交易表,记录每一笔资金动作(普通支付/预授权冻结/预授权捕获/周期代扣/合单子单)
/// 与容器层(业务单/协议)分离,通过 trade_type + 容器关联字段建立联系
/// 与容器层(业务单/协议)分离,通过 trade_type + 容器关联字段建立联系
/// 仅保留资金动作固有属性与通道调用命脉字段;业务上下文/路由参数/通道回执统一归容器。
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("pay_trade")
public class PayTrade extends MchBaseEntity {
/// 应用号
@TableField(updateStrategy = FieldStrategy.NEVER)
private String appId;
/// 支付交易号(统一雪花,无形态前缀)
private String tradeNo;
@@ -36,34 +32,9 @@ public class PayTrade extends MchBaseEntity {
/// 关联容器ID
/// 根据 tradeType 决定关联哪种容器表:
/// normal → pay_normal_order / authorize/capture → pay_auth / recurring → pay_recurring / combine_sub → pay_combine
/// normal → pay_normal_order / gateway → pay_gateway_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;
/// 支付通道
private String channel;
/// 支付方式
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum
private String method;
/// 其他支付方式method=other 时生效
private String otherMethod;
/// 限制支付类型(如限制信用卡)
/// @see cn.daxpay.open.platform.core.enums.unipay.PayLimitPayEnum
private String limitPay;
/// 支付渠道(微信/支付宝/银联等,三方通道透传时填)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum
private String provider;
/// 本次交易金额(最小货币单位)
private Long amount;
@@ -77,9 +48,6 @@ public class PayTrade extends MchBaseEntity {
/// @see PayFundStatusEnum
private String status;
/// 过期时间
private OffsetDateTime expiredTime;
/// 支付成功时间
private OffsetDateTime payTime;
@@ -89,56 +57,16 @@ public class PayTrade extends MchBaseEntity {
/// 订单来源
private String source;
/// 通道订单号(三方通道返回的订单号)
/// 通道订单号(三方通道返回的订单号,通道 close/sync 调用参数 + 回调反查索引
private String outOrderNo;
/// 透传订单号(三方通道产生的透传订单号
private String transOrderNo;
/// 特殊通道关联订单号(部分通道订单号有前缀/长度限制时使用)
private String relationOrderNo;
/// 付款用户 ID支付宝 buyer_user_id 等)
private String buyerId;
/// 买家登录账号(支付宝手机号/邮箱)
private String buyerLogonId;
/// 微信 openidjsapi/app/miniapp
private String openid;
/// 通道方记录的支付产品
private String tradeProduct;
/// 通道方记录的交易方式
private String tradeWay;
/// 银行卡类型(借记卡/贷记卡)
private String bankType;
/// 付款码(被扫支付)
private String barCode;
/// 活动类型
private String promotionType;
/// 支付参数体(如微信 prepay_id 组装串)
/// 支付参数体(如微信 prepay_id 组装串,非空表示已拉起支付,免重复请求通道
private String payBody;
/// 支付参数体类型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;
/// 应用号
@TableField(updateStrategy = FieldStrategy.NEVER)
private String appId;
}

View File

@@ -0,0 +1,85 @@
package cn.daxpay.open.payment.core.trade.service;
import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.core.trade.dao.GatewayPayOrderManager;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.entity.GatewayPayOrder;
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.Objects;
/// # 容器字段解析器
///
/// 通道层(close/sync/refund 服务)需要读取容器的 relationOrderNo / tradeProduct 等字段时,
/// 通过本类凭 trade.containerId + tradeType 定位容器, 屏蔽容器多态。
@Service
@RequiredArgsConstructor
public class ContainerFieldResolver {
private final NormalPayOrderManager payNormalOrderManager;
private final GatewayPayOrderManager gatewayPayOrderManager;
/// 获取通道关联订单号(富友等通道 close/sync/refund 需要)
public String getRelationOrderNo(PayTrade trade) {
var opt = resolveContainer(trade);
if (opt.isEmpty()) {
return null;
}
Object container = opt.get();
if (container instanceof NormalPayOrder n) {
return n.getRelationOrderNo();
}
if (container instanceof GatewayPayOrder g) {
return g.getRelationOrderNo();
}
return null;
}
/// 获取通道方记录的支付产品(富友等通道 close/sync/refund 需要)
public String getTradeProduct(PayTrade trade) {
var opt = resolveContainer(trade);
if (opt.isEmpty()) {
return null;
}
Object container = opt.get();
if (container instanceof NormalPayOrder n) {
return n.getTradeProduct();
}
if (container instanceof GatewayPayOrder g) {
return g.getTradeProduct();
}
return null;
}
/// 获取支付完成时间(退款需原支付日期)
public OffsetDateTime getPayTime(PayTrade trade) {
var opt = resolveContainer(trade);
if (opt.isEmpty()) {
return null;
}
Object container = opt.get();
if (container instanceof NormalPayOrder n) {
return n.getPayTime();
}
if (container instanceof GatewayPayOrder g) {
return g.getPayTime();
}
return null;
}
private java.util.Optional<Object> resolveContainer(PayTrade trade) {
if (trade.getContainerId() == null) {
return java.util.Optional.empty();
}
if (Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode())) {
return gatewayPayOrderManager.findById(trade.getContainerId())
.map(g -> (Object) g);
}
return payNormalOrderManager.findById(trade.getContainerId())
.map(n -> (Object) n);
}
}

View File

@@ -77,9 +77,9 @@ public class GatewayPayHandleService {
}
if (Objects.equals(existing.getStatus(), PayFundStatusEnum.PROCESSING.getCode())
|| Objects.equals(existing.getStatus(), PayFundStatusEnum.INIT.getCode())) {
// 通道/方式不一致则拒绝换端
if (!Objects.equals(existing.getProduct(), product)
|| !Objects.equals(existing.getMethod(), method)) {
// 通道/方式不一致则拒绝换端(product/method 在容器上)
if (!Objects.equals(order.getProduct(), product)
|| !Objects.equals(order.getMethod(), method)) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.error.gateway.channelLocked");
}
@@ -115,13 +115,9 @@ public class GatewayPayHandleService {
result = payStrategy.doPay(context);
} catch (Exception e) {
log.error("网关支付出现异常", e);
existing.setStatus(PayFundStatusEnum.FAIL.getCode());
if (e instanceof PayFailureException) {
existing.setErrorMsg(e.getMessage());
} else {
existing.setErrorMsg("支付出现异常: " + e.getMessage());
}
payTradeManager.updateById(existing);
String errMsg = (e instanceof PayFailureException)
? e.getMessage() : "支付出现异常: " + e.getMessage();
payUniHandleService.payFail(existing, errMsg);
throw e;
}
return SpringUtil.getBean(this.getClass()).paySuccess(order, existing, result);
@@ -141,26 +137,21 @@ public class GatewayPayHandleService {
trade.setTradeNo(TradeNoGenerateUtil.pay());
trade.setTradeType(PayTradeTypeEnum.GATEWAY.getCode());
trade.setContainerId(order.getId());
trade.setBizOrderNo(order.getBizOrderNo());
trade.setProduct(payParam.getProduct());
trade.setChannel(channel);
trade.setMethod(payParam.getMethod());
trade.setChannelMchNo(payParam.getChannelMchNo());
trade.setCapability(payParam.getCapability());
trade.setClientIp(order.getClientIp());
trade.setAmount(order.getAmount());
trade.setCurrency(CurrencyEnum.CNY.getCode());
trade.setRefundableBalance(order.getAmount());
trade.setStatus(PayFundStatusEnum.PROCESSING.getCode());
trade.setExpiredTime(order.getExpiredTime());
trade.setSource(TradeSourceEnum.AGGRESS_PAY.getCode());
trade.setOpenid(payParam.getOpenId());
payTradeManager.save(trade);
this.fillRouteOnOrder(order, payParam, scene, device);
order.setStatus(GatewayOrderStatusEnum.PAYING.getCode());
order.setChannel(channel);
order.setMethod(payParam.getMethod());
order.setLimitPay(payParam.getLimitPay() != null
? String.join(",", payParam.getLimitPay()) : null);
order.setOpenid(payParam.getOpenId());
order.setBarCode(payParam.getAuthCode());
order.setProduct(payParam.getProduct());
gatewayPayOrderManager.updateById(order);
return trade;
@@ -173,19 +164,12 @@ public class GatewayPayHandleService {
trade.setPayTime(result.getFinishTime());
}
trade.setOutOrderNo(result.getOutOrderNo());
trade.setTransOrderNo(result.getTransOrderNo());
trade.setRelationOrderNo(result.getRelationOrderNo());
trade.setBuyerId(result.getBuyerId());
trade.setBuyerLogonId(result.getBuyerLogonId());
trade.setTradeProduct(result.getTradeProduct());
trade.setTradeWay(result.getTradeWay());
trade.setBankType(result.getBankType());
trade.setPromotionType(result.getPromotionType());
// payBody/payBodyType 留 trade(已拉起缓存标记)
trade.setPayBody(result.getPayBody());
trade.setPayBodyType(Objects.nonNull(result.getPayBodyType())
? result.getPayBodyType().getCode() : null);
trade.setErrorMsg(null);
payUniHandleService.payAfterHandel(trade);
// 回执字段写容器, 由 payAfterHandel 统一处理
payUniHandleService.payAfterHandel(trade, result);
return this.buildResult(order, trade);
}

View File

@@ -89,12 +89,9 @@ public class NormalPayService {
} catch (Exception e) {
log.error("支付出现异常", e);
trade.setStatus(PayFundStatusEnum.FAIL.getCode());
if (e instanceof PayFailureException) {
trade.setErrorMsg(e.getMessage());
} else {
trade.setErrorMsg("支付出现异常: " + e.getMessage());
}
payTradeManager.updateById(trade);
String errMsg = (e instanceof PayFailureException)
? e.getMessage() : "支付出现异常: " + e.getMessage();
payUniHandleService.payFail(trade, errMsg);
throw e;
}
return SpringUtil.getBean(this.getClass()).paySuccess(trade, result);
@@ -109,20 +106,12 @@ public class NormalPayService {
}
// trade.status 在 complete=false 时保持 PROCESSING(createOrder 时已设)
trade.setOutOrderNo(result.getOutOrderNo());
trade.setTransOrderNo(result.getTransOrderNo());
trade.setRelationOrderNo(result.getRelationOrderNo());
trade.setBuyerId(result.getBuyerId());
trade.setBuyerLogonId(result.getBuyerLogonId());
trade.setTradeProduct(result.getTradeProduct());
trade.setTradeWay(result.getTradeWay());
trade.setBankType(result.getBankType());
trade.setPromotionType(result.getPromotionType());
// payBody/payBodyType 留 trade(已拉起缓存标记)
trade.setPayBody(result.getPayBody());
trade.setPayBodyType(Objects.nonNull(result.getPayBodyType())
? result.getPayBodyType().getCode() : null);
trade.setErrorMsg(null);
// 参考商业版: 不论是否完成都更新交易单, 仅 SUCCESS 时同步容器
payUniHandleService.payAfterHandel(trade);
// 回执字段写容器, 由 payAfterHandel 统一处理
payUniHandleService.payAfterHandel(trade, result);
return payAssistService.buildResult(trade);
}
}

View File

@@ -70,6 +70,10 @@ public class PayAssistService {
normalOrder.setCurrency(CurrencyEnum.CNY.getCode());
normalOrder.setChannel(channel);
normalOrder.setMethod(payParam.getMethod());
normalOrder.setLimitPay(payParam.getLimitPay() != null
? String.join(",", payParam.getLimitPay()) : null);
normalOrder.setOpenid(payParam.getOpenId());
normalOrder.setBarCode(payParam.getAuthCode());
normalOrder.setProduct(payParam.getProduct());
normalOrder.setExtraParam(payParam.getExtraParam());
normalOrder.setGoodsDetail(payParam.getGoodsDetail());
@@ -87,23 +91,11 @@ 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);
trade.setCurrency(CurrencyEnum.CNY.getCode());
trade.setRefundableBalance(amount);
trade.setStatus(PayFundStatusEnum.PROCESSING.getCode());
trade.setExpiredTime(expiredTime);
trade.setSource(cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum.MCH_API.getCode());
trade.setBarCode(payParam.getAuthCode());
trade.setOpenid(payParam.getOpenId());
payTradeManager.save(trade);
context.setTrade(trade);
// 注册超时关单延时消息(按订单过期时间定时投递)
@@ -163,8 +155,8 @@ public class PayAssistService {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.failedOrClosed");
}
// 超时检查
if (Objects.nonNull(trade.getExpiredTime())
&& DateTimeUtil.ge(OffsetDateTime.now(ZoneOffset.UTC), trade.getExpiredTime())) {
if (Objects.nonNull(normalOrder.getExpiredTime())
&& DateTimeUtil.ge(OffsetDateTime.now(ZoneOffset.UTC), normalOrder.getExpiredTime())) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.timeoutRetry");
}
}

View File

@@ -2,7 +2,12 @@ package cn.daxpay.open.payment.core.trade.service;
import cn.daxpay.open.payment.common.callback.CallbackData;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.core.trade.dao.GatewayPayOrderManager;
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.GatewayPayOrder;
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.notice.CallbackStatusEnum;
import com.baomidou.lock.LockInfo;
@@ -23,6 +28,8 @@ import java.util.Objects;
public class PayCallbackService {
private final PayTradeManager payTradeManager;
private final NormalPayOrderManager payNormalOrderManager;
private final GatewayPayOrderManager gatewayPayOrderManager;
private final PayUniHandleService payUniHandleService;
private final LockTemplate lockTemplate;
@@ -56,12 +63,22 @@ public class PayCallbackService {
} else {
this.fail(trade, callbackData);
}
return trade.getProduct();
return resolveProduct(trade);
} finally {
lockTemplate.releaseLock(lock);
}
}
/// 从容器获取 product(回调返回供通道策略识别来源)
private String resolveProduct(PayTrade trade) {
if (Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode())) {
return gatewayPayOrderManager.findById(trade.getContainerId())
.map(GatewayPayOrder::getProduct).orElse(null);
}
return payNormalOrderManager.findById(trade.getContainerId())
.map(NormalPayOrder::getProduct).orElse(null);
}
private void success(PayTrade trade, CallbackData callbackData) {
trade.setStatus(PayFundStatusEnum.SUCCESS.getCode());
trade.setPayTime(callbackData.getFinishTime());

View File

@@ -5,8 +5,10 @@ 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.GatewayPayOrderManager;
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.GatewayPayOrder;
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;
@@ -26,6 +28,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
@@ -37,6 +40,7 @@ public class PayCloseService {
private final PayTradeManager payTradeManager;
private final NormalPayOrderManager payNormalOrderManager;
private final GatewayPayOrderManager gatewayPayOrderManager;
private final PayUniHandleService payUniHandleService;
private final PayCloseRecordService payCloseRecordService;
private final LockTemplate lockTemplate;
@@ -46,7 +50,10 @@ public class PayCloseService {
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 = null;
if (StrUtil.isNotBlank(param.getOrderNo())) {
trade = payTradeManager.findByTradeNo(param.getOrderNo()).orElse(null);
}
if (Objects.isNull(trade) && Objects.nonNull(param.getBizOrderNo())) {
NormalPayOrder normalOrder = payNormalOrderManager.findByBizOrderNo(param.getBizOrderNo())
.orElse(null);
@@ -78,10 +85,14 @@ public class PayCloseService {
payUniHandleService.payClose(trade, false);
} else {
closeType = useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE;
ContainerInfo info = loadContainerInfo(trade);
var context = new PayStrategyContext()
.setTrade(trade);
.setTrade(trade)
.setChannelMchNo(info.channelMchNo())
.setCapability(info.capability())
.setClientIp(info.clientIp());
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
info.product(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(context);
strategy.doClose(context, useCancel);
payUniHandleService.payClose(trade, useCancel);
@@ -121,10 +132,14 @@ public class PayCloseService {
}
String errMsg = null;
try {
ContainerInfo info = loadContainerInfo(trade);
var context = new PayStrategyContext()
.setTrade(trade);
.setTrade(trade)
.setChannelMchNo(info.channelMchNo())
.setCapability(info.capability())
.setClientIp(info.clientIp());
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
info.product(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(context);
strategy.doClose(context, false);
} catch (Exception e) {
@@ -139,16 +154,41 @@ public class PayCloseService {
}
private void saveRecord(PayTrade trade, CloseTypeEnum closeType, String errMsg) {
ContainerInfo info = loadContainerInfo(trade);
PayCloseRecord record = new PayCloseRecord()
.setAppId(trade.getAppId())
.setTradeNo(trade.getTradeNo())
.setBizTradeNo(trade.getBizOrderNo())
.setProduct(trade.getProduct())
.setChannel(trade.getChannel())
.setBizTradeNo(info.bizOrderNo())
.setProduct(info.product())
.setChannel(info.channel())
.setClosed(StrUtil.isBlank(errMsg))
.setCloseType(closeType.getCode())
.setErrorMsg(errMsg);
record.setMchNo(trade.getMchNo());
payCloseRecordService.saveRecord(record);
}
/// 从容器(normal/gateway)提取关闭流程所需字段
private ContainerInfo loadContainerInfo(PayTrade trade) {
if (Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode())) {
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
return new ContainerInfo(
order.getProduct(), order.getChannel(), order.getBizOrderNo(),
order.getChannelMchNo(), order.getCapability(), order.getClientIp());
}
} else {
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
return new ContainerInfo(
order.getProduct(), order.getChannel(), order.getBizOrderNo(),
order.getChannelMchNo(), order.getCapability(), order.getClientIp());
}
}
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist");
}
private record ContainerInfo(
String product, String channel, String bizOrderNo,
String channelMchNo, String capability, String clientIp) {}
}

View File

@@ -71,9 +71,10 @@ public class PayRefundService {
refundOrder.setStatus(RefundOrderStatusEnum.PROGRESS.getCode());
payRefundOrderManager.save(refundOrder);
// 调用通道退款策略
// 调用通道退款策略(product 从容器获取)
String product = normalOrder != null ? normalOrder.getProduct() : null;
AbsRefundStrategy strategy = PaymentStrategyFactory.createByProduct(
lockedTrade.getProduct(), AbsRefundStrategy.class);
product, AbsRefundStrategy.class);
RefundResultBo result;
try {
strategy.doBeforeRefund(refundOrder);
@@ -138,12 +139,12 @@ public class PayRefundService {
.setAmount(param.getAmount())
.setOrderAmount(trade.getAmount())
.setCurrency(trade.getCurrency())
.setReason(param.getReason())
.setChannel(trade.getChannel())
.setProduct(trade.getProduct())
.setMethod(trade.getMethod());
.setReason(param.getReason());
if (normalOrder != null) {
refundOrder.setTitle(normalOrder.getTitle())
refundOrder.setChannel(normalOrder.getChannel())
.setProduct(normalOrder.getProduct())
.setMethod(normalOrder.getMethod())
.setTitle(normalOrder.getTitle())
.setBizOrderNo(normalOrder.getBizOrderNo())
.setChannelMchNo(normalOrder.getChannelMchNo())
.setCapability(normalOrder.getCapability())

View File

@@ -7,8 +7,10 @@ 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.GatewayPayOrderManager;
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.GatewayPayOrder;
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;
@@ -44,6 +46,7 @@ public class PaySyncService {
private final PayTradeManager payTradeManager;
private final NormalPayOrderManager payNormalOrderManager;
private final GatewayPayOrderManager gatewayPayOrderManager;
private final PayUniHandleService payUniHandleService;
private final PaySyncRecordService paySyncRecordService;
private final LockTemplate lockTemplate;
@@ -88,24 +91,28 @@ public class PaySyncService {
throw new RepetitiveOperationException();
}
try {
ContainerInfo info = loadContainerInfo(trade);
var context = new PayStrategyContext()
.setTrade(trade);
.setTrade(trade)
.setChannelMchNo(info.channelMchNo())
.setCapability(info.capability())
.setClientIp(info.clientIp());
var syncStrategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsSyncPayOrderStrategy.class);
info.product(), AbsSyncPayOrderStrategy.class);
PaySyncResultBo syncResult = syncStrategy.doSync(context);
if (!Objects.equals(syncResult.getOutOrderNo(), trade.getOutOrderNo())) {
trade.setOutOrderNo(syncResult.getOutOrderNo());
payTradeManager.updateById(trade);
}
boolean statusSync = this.checkAndAdjust(syncResult, trade);
boolean statusSync = this.checkAndAdjust(syncResult, trade, info);
if (!statusSync) {
try {
this.adjustHandler(syncResult, trade);
this.adjustHandler(syncResult, trade, info);
} catch (PayFailureException e) {
syncResult.setSyncSuccess(false).setSyncErrorMsg(e.getMessage());
}
}
this.saveRecord(trade, syncResult, !statusSync);
this.saveRecord(trade, syncResult, !statusSync, info);
return new NormalPaySyncResult()
.setOrderStatus(trade.getStatus())
.setAdjust(statusSync);
@@ -114,7 +121,7 @@ public class PaySyncService {
}
}
private boolean checkAndAdjust(PaySyncResultBo syncResult, PayTrade trade) {
private boolean checkAndAdjust(PaySyncResultBo syncResult, PayTrade trade, ContainerInfo info) {
var payStatus = Optional.ofNullable(syncResult.getPayStatus())
.orElse(PayFundStatusEnum.PROCESSING);
String orderStatus = trade.getStatus();
@@ -123,7 +130,8 @@ public class PaySyncService {
}
if (orderStatus.equals(PayFundStatusEnum.PROCESSING.getCode())) {
if (Objects.equals(PayFundStatusEnum.PROCESSING, payStatus)) {
if (DateTimeUtil.le(trade.getExpiredTime(), OffsetDateTime.now(ZoneOffset.UTC))) {
if (info.expiredTime() != null
&& DateTimeUtil.le(info.expiredTime(), OffsetDateTime.now(ZoneOffset.UTC))) {
syncResult.setPayStatus(PayFundStatusEnum.CLOSE);
syncResult.setRemoteClose(true);
return false;
@@ -136,7 +144,7 @@ public class PaySyncService {
return false;
}
private void adjustHandler(PaySyncResultBo syncResult, PayTrade trade) {
private void adjustHandler(PaySyncResultBo syncResult, PayTrade trade, ContainerInfo info) {
var payStatus = syncResult.getPayStatus();
if (Objects.isNull(payStatus)) {
return;
@@ -146,7 +154,7 @@ public class PaySyncService {
case SUCCESS -> this.success(trade, syncResult);
case CLOSE -> {
if (syncResult.isRemoteClose()) {
this.closeRemote(trade);
this.closeRemote(trade, info);
} else {
payUniHandleService.payClose(trade, false);
}
@@ -160,39 +168,34 @@ public class PaySyncService {
trade.setStatus(PayFundStatusEnum.SUCCESS.getCode());
trade.setPayTime(syncResult.getFinishTime());
trade.setCloseTime(null);
trade.setErrorMsg(null);
if (Objects.nonNull(syncResult.getProvider())) {
trade.setProvider(syncResult.getProvider().getCode());
}
trade.setBuyerId(syncResult.getBuyerId());
trade.setTradeProduct(syncResult.getTradeProduct());
trade.setTradeWay(syncResult.getTradeWay());
trade.setBankType(syncResult.getBankType());
trade.setPromotionType(syncResult.getPromotionType());
payUniHandleService.paySuccess(trade);
// 通道回执写容器, 由 payUniHandleService 统一处理
payUniHandleService.paySuccess(trade, syncResult);
}
private void closeRemote(PayTrade trade) {
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
trade.getProduct(), AbsPayCloseStrategy.class);
private void closeRemote(PayTrade trade, ContainerInfo info) {
var context = new PayStrategyContext()
.setTrade(trade);
.setTrade(trade)
.setChannelMchNo(info.channelMchNo())
.setCapability(info.capability())
.setClientIp(info.clientIp());
AbsPayCloseStrategy strategy = PaymentStrategyFactory.createByProduct(
info.product(), AbsPayCloseStrategy.class);
strategy.doBeforeClose(context);
strategy.doClose(context, false);
payUniHandleService.payTimeout(trade);
}
private void saveRecord(PayTrade trade, PaySyncResultBo syncResult, boolean adjust) {
private void saveRecord(PayTrade trade, PaySyncResultBo syncResult, boolean adjust, ContainerInfo info) {
PaySyncRecord record = new PaySyncRecord()
.setAppId(trade.getAppId())
.setTradeNo(trade.getTradeNo())
.setBizTradeNo(trade.getBizOrderNo())
.setBizTradeNo(info.bizOrderNo())
.setOutTradeNo(trade.getOutOrderNo())
.setOutTradeStatus(Objects.nonNull(syncResult.getPayStatus())
? syncResult.getPayStatus().getCode() : null)
.setTradeType(trade.getTradeType())
.setProduct(trade.getProduct())
.setChannel(trade.getChannel())
.setProduct(info.product())
.setChannel(info.channel())
.setSyncInfo(syncResult.getSyncData())
.setAdjust(adjust)
.setErrorCode(syncResult.getSyncErrorCode())
@@ -200,4 +203,32 @@ public class PaySyncService {
record.setMchNo(trade.getMchNo());
paySyncRecordService.saveRecord(record);
}
/// 从容器(normal/gateway)提取同步流程所需字段
private ContainerInfo loadContainerInfo(PayTrade trade) {
if (Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode())) {
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
return new ContainerInfo(
order.getProduct(), order.getChannel(), order.getBizOrderNo(),
order.getExpiredTime(), order.getChannelMchNo(),
order.getCapability(), order.getClientIp());
}
} else {
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
return new ContainerInfo(
order.getProduct(), order.getChannel(), order.getBizOrderNo(),
order.getExpiredTime(), order.getChannelMchNo(),
order.getCapability(), order.getClientIp());
}
}
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist");
}
/// 容器信息持有者(避免在各方法间传整个容器对象)
private record ContainerInfo(
String product, String channel, String bizOrderNo,
OffsetDateTime expiredTime, String channelMchNo,
String capability, String clientIp) {}
}

View File

@@ -4,6 +4,8 @@ import cn.daxpay.open.payment.common.enums.GatewayOrderStatusEnum;
import cn.daxpay.open.payment.common.enums.NormalPayOrderStatusEnum;
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
import cn.daxpay.open.payment.common.enums.PayTradeTypeEnum;
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
import cn.daxpay.open.payment.core.trade.dao.GatewayPayOrderManager;
import cn.daxpay.open.payment.core.trade.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.core.trade.dao.PayTradeManager;
@@ -21,6 +23,7 @@ import java.util.Objects;
/// # 交易统一处理服务
///
/// 支付成功/失败/关闭后的统一处理逻辑, 按 trade_type 回写对应业务容器。
/// 通道回执字段统一写容器, trade 仅保留资金态(payBody/outOrderNo 等)。
@Slf4j
@Service
@RequiredArgsConstructor
@@ -32,27 +35,67 @@ public class PayUniHandleService {
/// 支付发起后处理
/// 不论是否完成都更新交易单; 仅资金状态为 SUCCESS 时同步容器为 PAID。
public void payAfterHandel(PayTrade trade) {
/// 回执字段(transOrderNo/buyerId 等)写容器。
public void payAfterHandel(PayTrade trade, PayTradeResultBo result) {
payTradeManager.updateById(trade);
if (Objects.equals(trade.getStatus(), PayFundStatusEnum.SUCCESS.getCode())) {
this.markContainerPaid(trade);
if (isGateway(trade)) {
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
applyGatewayReceipts(order, result);
if (Objects.equals(trade.getStatus(), PayFundStatusEnum.SUCCESS.getCode())) {
order.setStatus(GatewayOrderStatusEnum.PAID.getCode());
order.setPayTime(trade.getPayTime());
}
gatewayPayOrderManager.updateById(order);
}
return;
}
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
applyNormalReceipts(order, result);
if (Objects.equals(trade.getStatus(), PayFundStatusEnum.SUCCESS.getCode())) {
order.setStatus(NormalPayOrderStatusEnum.PAID.getCode());
order.setPayTime(trade.getPayTime());
}
payNormalOrderManager.updateById(order);
}
}
/// 支付成功后续处理
public void paySuccess(PayTrade trade) {
this.markContainerPaid(trade);
/// 支付成功后续处理(同步/回调路径), 含通道回执写容器
public void paySuccess(PayTrade trade, PaySyncResultBo syncResult) {
payTradeManager.updateById(trade);
if (isGateway(trade)) {
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
order.setStatus(GatewayOrderStatusEnum.PAID.getCode());
order.setPayTime(trade.getPayTime());
applyGatewaySyncReceipts(order, syncResult);
gatewayPayOrderManager.updateById(order);
}
return;
}
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
order.setStatus(NormalPayOrderStatusEnum.PAID.getCode());
order.setPayTime(trade.getPayTime());
applyNormalSyncReceipts(order, syncResult);
payNormalOrderManager.updateById(order);
}
}
/// 支付成功后续处理(回调路径, 无回执详情)
public void paySuccess(PayTrade trade) {
payTradeManager.updateById(trade);
markContainerPaid(trade);
}
/// 支付失败处理
public void payFail(PayTrade trade, String errMsg) {
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
trade.setStatus(PayFundStatusEnum.FAIL.getCode());
trade.setErrorMsg(errMsg);
trade.setCloseTime(now);
payTradeManager.updateById(trade);
this.markContainerClosed(trade, now, false);
this.markContainerClosed(trade, now, false, errMsg);
}
/// 支付关闭处理
@@ -64,7 +107,7 @@ public class PayUniHandleService {
: PayFundStatusEnum.CLOSE.getCode());
trade.setCloseTime(now);
payTradeManager.updateById(trade);
this.markContainerClosed(trade, now, false);
this.markContainerClosed(trade, now, false, null);
}
/// 支付超时关闭: 资金态 CLOSE, 容器态 EXPIRED
@@ -73,7 +116,7 @@ public class PayUniHandleService {
trade.setStatus(PayFundStatusEnum.CLOSE.getCode());
trade.setCloseTime(now);
payTradeManager.updateById(trade);
this.markContainerClosed(trade, now, true);
this.markContainerClosed(trade, now, true, null);
}
/// 仅关闭网关容器(尚未创建 Trade 的预下单超时)
@@ -110,7 +153,7 @@ public class PayUniHandleService {
}
}
private void markContainerClosed(PayTrade trade, OffsetDateTime now, boolean expired) {
private void markContainerClosed(PayTrade trade, OffsetDateTime now, boolean expired, String errMsg) {
if (isGateway(trade)) {
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
if (order != null) {
@@ -118,6 +161,9 @@ public class PayUniHandleService {
? GatewayOrderStatusEnum.EXPIRED.getCode()
: GatewayOrderStatusEnum.CLOSED.getCode());
order.setCloseTime(now);
if (errMsg != null) {
order.setErrorMsg(errMsg);
}
gatewayPayOrderManager.updateById(order);
}
return;
@@ -128,10 +174,67 @@ public class PayUniHandleService {
? NormalPayOrderStatusEnum.EXPIRED.getCode()
: NormalPayOrderStatusEnum.CLOSED.getCode());
normalOrder.setCloseTime(now);
if (errMsg != null) {
normalOrder.setErrorMsg(errMsg);
}
payNormalOrderManager.updateById(normalOrder);
}
}
private void applyNormalReceipts(NormalPayOrder order, PayTradeResultBo result) {
order.setTransOrderNo(result.getTransOrderNo());
order.setRelationOrderNo(result.getRelationOrderNo());
order.setBuyerId(result.getBuyerId());
order.setBuyerLogonId(result.getBuyerLogonId());
order.setTradeProduct(result.getTradeProduct());
order.setTradeWay(result.getTradeWay());
order.setBankType(result.getBankType());
order.setPromotionType(result.getPromotionType());
order.setErrorMsg(null);
}
private void applyGatewayReceipts(GatewayPayOrder order, PayTradeResultBo result) {
order.setTransOrderNo(result.getTransOrderNo());
order.setRelationOrderNo(result.getRelationOrderNo());
order.setBuyerId(result.getBuyerId());
order.setBuyerLogonId(result.getBuyerLogonId());
order.setTradeProduct(result.getTradeProduct());
order.setTradeWay(result.getTradeWay());
order.setBankType(result.getBankType());
order.setPromotionType(result.getPromotionType());
order.setErrorMsg(null);
}
private void applyNormalSyncReceipts(NormalPayOrder order, PaySyncResultBo syncResult) {
if (syncResult == null) {
return;
}
if (Objects.nonNull(syncResult.getProvider())) {
order.setProvider(syncResult.getProvider().getCode());
}
order.setBuyerId(syncResult.getBuyerId());
order.setTradeProduct(syncResult.getTradeProduct());
order.setTradeWay(syncResult.getTradeWay());
order.setBankType(syncResult.getBankType());
order.setPromotionType(syncResult.getPromotionType());
order.setErrorMsg(null);
}
private void applyGatewaySyncReceipts(GatewayPayOrder order, PaySyncResultBo syncResult) {
if (syncResult == null) {
return;
}
if (Objects.nonNull(syncResult.getProvider())) {
order.setProvider(syncResult.getProvider().getCode());
}
order.setBuyerId(syncResult.getBuyerId());
order.setTradeProduct(syncResult.getTradeProduct());
order.setTradeWay(syncResult.getTradeWay());
order.setBankType(syncResult.getBankType());
order.setPromotionType(syncResult.getPromotionType());
order.setErrorMsg(null);
}
private boolean isGateway(PayTrade trade) {
return Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode());
}

View File

@@ -58,7 +58,6 @@ public class GatewayPayOrderAdminService {
result.setOutOrderNo(trade.getOutOrderNo());
result.setFundStatus(trade.getStatus());
result.setRefundableBalance(trade.getRefundableBalance());
result.setErrorMsg(trade.getErrorMsg());
}
return result;
}

View File

@@ -62,12 +62,6 @@ public class NormalPayOrderAdminService {
result.setRefundableBalance(trade.getRefundableBalance());
result.setPayBody(trade.getPayBody());
result.setPayBodyType(trade.getPayBodyType());
result.setBuyerId(trade.getBuyerId());
result.setOpenid(trade.getOpenid());
result.setTradeProduct(trade.getTradeProduct());
result.setTradeWay(trade.getTradeWay());
result.setBankType(trade.getBankType());
result.setErrorMsg(trade.getErrorMsg());
}
return result;
}

View File

@@ -52,12 +52,20 @@ public class PayTradeAdminService {
PayTrade entity = payTradeManager.findById(id)
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
PayTradeResult result = PayTradeAdminConvert.CONVERT.toResult(entity);
// 联表查询容器, 补充业务字段
// 联表查询容器, 补充业务字段与回执
NormalPayOrder normalOrder = normalPayOrderManager.findById(entity.getContainerId()).orElse(null);
if (Objects.nonNull(normalOrder)) {
result.setBizOrderNo(normalOrder.getBizOrderNo());
result.setTitle(normalOrder.getTitle());
result.setContainerStatus(normalOrder.getStatus());
result.setProduct(normalOrder.getProduct());
result.setChannel(normalOrder.getChannel());
result.setMethod(normalOrder.getMethod());
result.setBuyerId(normalOrder.getBuyerId());
result.setTradeProduct(normalOrder.getTradeProduct());
result.setTradeWay(normalOrder.getTradeWay());
result.setBankType(normalOrder.getBankType());
result.setErrorMsg(normalOrder.getErrorMsg());
}
return result;
}