mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 23:45:39 +08:00
refactor(route): 清理通道路由死代码与冗余字段, 移除重付兼容分支
- RouteHit 精简为 (product, channelMchNo, capability), 删除 channel/method 等死字段 - PayRouteProductResolver 删除 resolve/resolveAndFill 等零调用死方法, 仅保留 productOfChannelMchNo - 移除 inferSceneCapability 全链路(Controller/Facade/Service/Support) - PayRouteStrategy 移除 provider 字段(改由支付方式解析) - PayRouteSceneConfig 移除 provider/channel 冗余字段(由 method 派生), saveSceneBatch 改为从 method 派生 provider - 移除 PayRouteService.resolve 兼容分支与 NormalPayService 重付复用分支 - update-tables.sql 同步清理 provider/channel 列
This commit is contained in:
@@ -1,259 +0,0 @@
|
||||
-- ===================================
|
||||
-- 云音箱设备域: 厂商配置表 + 设备台账表
|
||||
-- ===================================
|
||||
|
||||
-- 设备厂商配置
|
||||
CREATE TABLE IF NOT EXISTS device_vendor_config (
|
||||
id bigint NOT NULL,
|
||||
device_type varchar(32) NOT NULL,
|
||||
vendor_code varchar(64) NOT NULL,
|
||||
config_name varchar(128) NOT NULL,
|
||||
app_id varchar(128),
|
||||
app_secret varchar(512),
|
||||
enable boolean NOT NULL DEFAULT true,
|
||||
ext_param jsonb,
|
||||
remark varchar(512),
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT device_vendor_config_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE device_vendor_config IS '设备厂商配置';
|
||||
COMMENT ON COLUMN device_vendor_config.id IS '主键';
|
||||
COMMENT ON COLUMN device_vendor_config.device_type IS '设备类型';
|
||||
COMMENT ON COLUMN device_vendor_config.vendor_code IS '厂商代码';
|
||||
COMMENT ON COLUMN device_vendor_config.config_name IS '配置名称';
|
||||
COMMENT ON COLUMN device_vendor_config.app_id IS '应用ID';
|
||||
COMMENT ON COLUMN device_vendor_config.app_secret IS '应用密钥';
|
||||
COMMENT ON COLUMN device_vendor_config.enable IS '是否启用';
|
||||
COMMENT ON COLUMN device_vendor_config.ext_param IS '扩展参数';
|
||||
COMMENT ON COLUMN device_vendor_config.remark IS '备注';
|
||||
COMMENT ON COLUMN device_vendor_config.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN device_vendor_config.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN device_vendor_config.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN device_vendor_config.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN device_vendor_config.version IS '版本号';
|
||||
COMMENT ON COLUMN device_vendor_config.deleted IS '逻辑删除';
|
||||
|
||||
-- 设备类型+厂商+配置名称唯一索引(未删除范围内)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_device_vendor_config_name
|
||||
ON device_vendor_config (device_type, vendor_code, config_name) WHERE deleted = false;
|
||||
-- 厂商查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_device_vendor_config_vendor
|
||||
ON device_vendor_config (device_type, vendor_code, deleted);
|
||||
|
||||
-- 云音箱设备
|
||||
CREATE TABLE IF NOT EXISTS device_speaker (
|
||||
id bigint NOT NULL,
|
||||
mch_no varchar(32),
|
||||
vendor_code varchar(64),
|
||||
vendor_config_id bigint,
|
||||
device_sn varchar(64) NOT NULL,
|
||||
imei varchar(32),
|
||||
shop_id varchar(64),
|
||||
device_name varchar(128),
|
||||
status varchar(16) NOT NULL DEFAULT 'unbound',
|
||||
bind_time timestamptz(6),
|
||||
last_online_time timestamptz(6),
|
||||
remark varchar(512),
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT device_speaker_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE device_speaker IS '云音箱设备';
|
||||
COMMENT ON COLUMN device_speaker.id IS '主键';
|
||||
COMMENT ON COLUMN device_speaker.mch_no IS '商户号';
|
||||
COMMENT ON COLUMN device_speaker.device_sn IS '设备序列号';
|
||||
COMMENT ON COLUMN device_speaker.imei IS 'IMEI';
|
||||
COMMENT ON COLUMN device_speaker.shop_id IS '厂商门店ID';
|
||||
COMMENT ON COLUMN device_speaker.device_name IS '设备名称';
|
||||
COMMENT ON COLUMN device_speaker.status IS '设备状态';
|
||||
COMMENT ON COLUMN device_speaker.bind_time IS '绑定时间';
|
||||
COMMENT ON COLUMN device_speaker.last_online_time IS '最后在线时间';
|
||||
COMMENT ON COLUMN device_speaker.remark IS '备注';
|
||||
COMMENT ON COLUMN device_speaker.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN device_speaker.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN device_speaker.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN device_speaker.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN device_speaker.version IS '版本号';
|
||||
COMMENT ON COLUMN device_speaker.deleted IS '逻辑删除';
|
||||
COMMENT ON COLUMN device_speaker.vendor_code IS '厂商代码';
|
||||
COMMENT ON COLUMN device_speaker.vendor_config_id IS '厂商配置ID';
|
||||
|
||||
-- 设备序列号唯一索引(未删除范围内)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_device_speaker_sn ON device_speaker (device_sn) WHERE deleted = false;
|
||||
-- 商户号查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_device_speaker_mch_no ON device_speaker (mch_no, deleted);
|
||||
|
||||
-- 云打印设备
|
||||
CREATE TABLE IF NOT EXISTS device_printer (
|
||||
id bigint NOT NULL,
|
||||
mch_no varchar(32),
|
||||
vendor_code varchar(64),
|
||||
vendor_config_id bigint,
|
||||
device_sn varchar(64) NOT NULL,
|
||||
imei varchar(32),
|
||||
shop_id varchar(64),
|
||||
device_name varchar(128),
|
||||
status varchar(16) NOT NULL DEFAULT 'unbound',
|
||||
bind_time timestamptz(6),
|
||||
last_online_time timestamptz(6),
|
||||
remark varchar(512),
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT device_printer_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE device_printer IS '云打印设备';
|
||||
COMMENT ON COLUMN device_printer.id IS '主键';
|
||||
COMMENT ON COLUMN device_printer.mch_no IS '商户号';
|
||||
COMMENT ON COLUMN device_printer.device_sn IS '设备序列号';
|
||||
COMMENT ON COLUMN device_printer.imei IS 'IMEI';
|
||||
COMMENT ON COLUMN device_printer.shop_id IS '厂商门店ID';
|
||||
COMMENT ON COLUMN device_printer.device_name IS '设备名称';
|
||||
COMMENT ON COLUMN device_printer.status IS '设备状态';
|
||||
COMMENT ON COLUMN device_printer.bind_time IS '绑定时间';
|
||||
COMMENT ON COLUMN device_printer.last_online_time IS '最后在线时间';
|
||||
COMMENT ON COLUMN device_printer.remark IS '备注';
|
||||
COMMENT ON COLUMN device_printer.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN device_printer.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN device_printer.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN device_printer.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN device_printer.version IS '版本号';
|
||||
COMMENT ON COLUMN device_printer.deleted IS '逻辑删除';
|
||||
COMMENT ON COLUMN device_printer.vendor_code IS '厂商代码';
|
||||
COMMENT ON COLUMN device_printer.vendor_config_id IS '厂商配置ID';
|
||||
|
||||
-- 设备序列号唯一索引(未删除范围内)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_device_printer_sn ON device_printer (device_sn) WHERE deleted = false;
|
||||
-- 商户号查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_device_printer_mch_no ON device_printer (mch_no, deleted);
|
||||
|
||||
-- ===================================
|
||||
-- 双因素认证: 用户 TOTP 绑定表
|
||||
-- ===================================
|
||||
|
||||
-- 用户双因素认证绑定记录(一对一, 记录存在即代表该用户已启用 TOTP 双因素认证)
|
||||
-- 移除最后验证时间字段(仅展示用途, 已废弃)
|
||||
ALTER TABLE iam_user_two_factor DROP COLUMN IF EXISTS last_verify_time;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS iam_user_two_factor (
|
||||
id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
secret varchar(512) NOT NULL,
|
||||
backup_codes jsonb,
|
||||
backup_codes_remaining int NOT NULL DEFAULT 0,
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT iam_user_two_factor_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE iam_user_two_factor IS '用户双因素认证绑定记录';
|
||||
COMMENT ON COLUMN iam_user_two_factor.id IS '主键';
|
||||
COMMENT ON COLUMN iam_user_two_factor.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN iam_user_two_factor.secret IS 'TOTP 密钥';
|
||||
COMMENT ON COLUMN iam_user_two_factor.backup_codes IS '备用验证码';
|
||||
COMMENT ON COLUMN iam_user_two_factor.backup_codes_remaining IS '剩余可用备用验证码数量';
|
||||
COMMENT ON COLUMN iam_user_two_factor.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN iam_user_two_factor.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN iam_user_two_factor.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN iam_user_two_factor.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN iam_user_two_factor.version IS '版本号(乐观锁)';
|
||||
COMMENT ON COLUMN iam_user_two_factor.deleted IS '逻辑删除标志';
|
||||
|
||||
-- 用户ID唯一索引(未删除范围内, 一个用户最多一条绑定记录)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_iam_user_two_factor_user_id
|
||||
ON iam_user_two_factor (user_id) WHERE deleted = false;
|
||||
|
||||
-- ===================================
|
||||
-- 支付通道路由: 场景模式配置表 + 基础模式配置表
|
||||
-- 重构: 以通道商户号(channel_mch_no)替代支付产品(product)定位,场景模式新增支付能力(capability)
|
||||
-- ===================================
|
||||
|
||||
DROP TABLE IF EXISTS pay_route_scene_config;
|
||||
DROP TABLE IF EXISTS pay_route_basic_config;
|
||||
|
||||
-- 支付通道路由场景模式配置(每支付方式一条: 通道商户 + 支付能力)
|
||||
CREATE TABLE IF NOT EXISTS pay_route_scene_config (
|
||||
id bigint NOT NULL,
|
||||
strategy_id bigint NOT NULL,
|
||||
provider varchar(32),
|
||||
channel varchar(32),
|
||||
method varchar(32) NOT NULL,
|
||||
channel_mch_no varchar(32) NOT NULL,
|
||||
capability varchar(32) NOT NULL,
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT pay_route_scene_config_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE pay_route_scene_config IS '支付通道路由场景模式配置';
|
||||
COMMENT ON COLUMN pay_route_scene_config.id IS '主键';
|
||||
COMMENT ON COLUMN pay_route_scene_config.strategy_id IS '路由策略ID';
|
||||
COMMENT ON COLUMN pay_route_scene_config.provider IS '支付渠道(派生自支付方式)';
|
||||
COMMENT ON COLUMN pay_route_scene_config.channel IS '通道编码(派生自通道商户绑定的产品)';
|
||||
COMMENT ON COLUMN pay_route_scene_config.method IS '支付方式编码';
|
||||
COMMENT ON COLUMN pay_route_scene_config.channel_mch_no IS '通道商户号(唯一绑定支付产品)';
|
||||
COMMENT ON COLUMN pay_route_scene_config.capability IS '支付能力编码';
|
||||
COMMENT ON COLUMN pay_route_scene_config.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN pay_route_scene_config.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN pay_route_scene_config.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN pay_route_scene_config.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN pay_route_scene_config.version IS '版本号';
|
||||
COMMENT ON COLUMN pay_route_scene_config.deleted IS '逻辑删除';
|
||||
|
||||
-- 策略 + 支付方式 唯一索引(未删除范围内)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_route_scene_config_method
|
||||
ON pay_route_scene_config (strategy_id, method) WHERE deleted = false;
|
||||
|
||||
-- 支付通道路由基础模式配置(每支付渠道一条: 通道商户)
|
||||
CREATE TABLE IF NOT EXISTS pay_route_basic_config (
|
||||
id bigint NOT NULL,
|
||||
strategy_id bigint NOT NULL,
|
||||
provider varchar(32) NOT NULL,
|
||||
channel_mch_no varchar(32) NOT NULL,
|
||||
creator bigint,
|
||||
create_time timestamptz(6),
|
||||
last_modifier bigint,
|
||||
last_modified_time timestamptz(6),
|
||||
version int NOT NULL DEFAULT 0,
|
||||
deleted boolean NOT NULL DEFAULT false,
|
||||
CONSTRAINT pay_route_basic_config_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE pay_route_basic_config IS '支付通道路由基础模式配置';
|
||||
COMMENT ON COLUMN pay_route_basic_config.id IS '主键';
|
||||
COMMENT ON COLUMN pay_route_basic_config.strategy_id IS '路由策略ID';
|
||||
COMMENT ON COLUMN pay_route_basic_config.provider IS '支付渠道';
|
||||
COMMENT ON COLUMN pay_route_basic_config.channel_mch_no IS '通道商户号(唯一绑定支付产品)';
|
||||
COMMENT ON COLUMN pay_route_basic_config.creator IS '创建人ID';
|
||||
COMMENT ON COLUMN pay_route_basic_config.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN pay_route_basic_config.last_modifier IS '最后修改人ID';
|
||||
COMMENT ON COLUMN pay_route_basic_config.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN pay_route_basic_config.version IS '版本号';
|
||||
COMMENT ON COLUMN pay_route_basic_config.deleted IS '逻辑删除';
|
||||
|
||||
-- 策略 + 支付渠道 唯一索引(未删除范围内)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_route_basic_config_provider
|
||||
ON pay_route_basic_config (strategy_id, provider) WHERE deleted = false;
|
||||
|
||||
@@ -114,17 +114,6 @@ public class PayRouteAdminController {
|
||||
return Res.ok(configService.listSceneCapabilityCandidatesForMethod(appId, provider, method, channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "通道路由查看", nameEn = "Pay Route View")
|
||||
@Operation(summary = "推断目录项与通道商户下唯一支付能力(仅供回显)")
|
||||
@GetMapping("/scene-config/infer-capability")
|
||||
public Result<String> inferSceneCapability(
|
||||
@NotBlank(message = "{validation.field.appId.notBlank}") String appId,
|
||||
@NotBlank(message = "{validation.field.provider.notBlank}") String provider,
|
||||
@NotBlank(message = "{validation.field.method.notBlank}") String method,
|
||||
@NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(configService.inferSceneCapability(appId, provider, method, channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "通道路由查看", nameEn = "Pay Route View")
|
||||
@Operation(summary = "查询基础模式配置列表")
|
||||
@GetMapping("/basic-config/list-by-app-id")
|
||||
|
||||
@@ -56,12 +56,7 @@ public class NormalPayService {
|
||||
public PayResult payHandle(PayParam payParam, PayTrade trade) {
|
||||
// 解析应用号:空则取商户默认应用
|
||||
payAssistService.resolveApp(payParam);
|
||||
// 重付场景:已有订单且 param 未传产品时,复用首次产品,不重复路由
|
||||
if (Objects.nonNull(trade) && StrUtil.isBlank(payParam.getProduct())
|
||||
&& StrUtil.isNotBlank(trade.getProduct())) {
|
||||
payParam.setProduct(trade.getProduct());
|
||||
}
|
||||
// 路由解析:已指定 product 则跳过,否则按 appId+method 策略匹配
|
||||
// 路由解析:直定模式(已传 channelMchNo)直接解析,否则按 appId+method 策略匹配
|
||||
payRouteFacade.resolve(payParam);
|
||||
var payStrategy = PaymentStrategyFactory.createByProduct(payParam.getProduct(), AbsPayStrategy.class);
|
||||
payStrategy.setPayParam(payParam);
|
||||
|
||||
@@ -8,7 +8,7 @@ import cn.daxpay.open.payment.unipay.param.trade.pay.PayParam;
|
||||
/// 实现在 merchant 模块(PayRouteService),按应用路由策略匹配并回填产品。
|
||||
public interface PayRouteFacade {
|
||||
|
||||
/// 解析支付路由:已指定 product 则跳过,否则按策略模式匹配并回填 product。
|
||||
/// 解析支付路由:直定模式(已传 channelMchNo)直接解析,否则按策略模式匹配并回填。
|
||||
/// 调用方需保证 appId 已解析完毕。
|
||||
void resolve(PayParam payParam);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import cn.daxpay.open.payment.merchant.convert.route.scene.PayRouteSceneConfigCo
|
||||
import cn.daxpay.open.payment.merchant.result.route.scene.PayRouteSceneConfigResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -21,13 +20,6 @@ public class PayRouteSceneConfig extends MpBaseEntity implements ToResult<PayRou
|
||||
/// 路由策略ID
|
||||
private Long strategyId;
|
||||
|
||||
/// 支付渠道, 空表示通用方式(qrcode/barcode 等)
|
||||
/// @see PayProviderEnum
|
||||
private String provider;
|
||||
|
||||
/// 通道编码
|
||||
private String channel;
|
||||
|
||||
/// 支付方式编码
|
||||
private String method;
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ public class PayRouteStrategy extends MchBaseEntity implements ToResult<PayRoute
|
||||
/// @see PayRouteModeEnum
|
||||
private String mode;
|
||||
|
||||
/// 支付渠道(微信/支付宝/银联)
|
||||
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum
|
||||
private String provider;
|
||||
|
||||
/// 是否启用
|
||||
private boolean enable;
|
||||
|
||||
|
||||
@@ -10,18 +10,12 @@ import lombok.experimental.Accessors;
|
||||
@Schema(title = "场景模式配置项")
|
||||
public class PayRouteSceneConfigItem {
|
||||
|
||||
@Schema(description = "支付渠道")
|
||||
private String provider;
|
||||
|
||||
@Schema(description = "通道商户号(场景模式定位通道商户,由其推导支付产品)")
|
||||
private String channelMchNo;
|
||||
|
||||
@Schema(description = "支付能力")
|
||||
private String capability;
|
||||
|
||||
@Schema(description = "支付通道")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "支付方式")
|
||||
private String method;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@ public class PayRouteStrategyParam {
|
||||
@Schema(description = "路由模式:basic/scene")
|
||||
private String mode;
|
||||
|
||||
@Schema(description = "支付渠道")
|
||||
private String provider;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enable;
|
||||
|
||||
|
||||
@@ -16,12 +16,6 @@ public class PayRouteSceneConfigResult extends BaseResult {
|
||||
@Schema(description = "路由策略ID")
|
||||
private String strategyId;
|
||||
|
||||
@Schema(description = "支付渠道")
|
||||
private String provider;
|
||||
|
||||
@Schema(description = "通道编码")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "支付方式编码")
|
||||
private String method;
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ public class PayRouteStrategyResult extends BaseResult {
|
||||
@Schema(description = "路由模式")
|
||||
private String mode;
|
||||
|
||||
@Schema(description = "支付渠道")
|
||||
private String provider;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private boolean enable;
|
||||
|
||||
|
||||
@@ -60,10 +60,9 @@ public class PayRouteBasicMatcher {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.basicProductNotAvailable", PayRouteI18nHelper.provider(provider.getCode()));
|
||||
}
|
||||
String channel = productResolver.channelOfProduct(product);
|
||||
// 基础模式不存能力,由 (产品, 支付方式) 派生
|
||||
String capability = resolveCapability(product, methodEnum);
|
||||
return new RouteHit(channel, methodEnum.getCode(), product, channelMchNo, capability, null, null);
|
||||
return new RouteHit(product, channelMchNo, capability);
|
||||
}
|
||||
|
||||
/// 从基础配置中取指定支付渠道已绑定的通道商户号
|
||||
|
||||
@@ -98,11 +98,6 @@ public class PayRouteConfigService {
|
||||
return sceneConfigService.listSceneCapabilityCandidatesForMethod(appId, provider, method, channelMchNo);
|
||||
}
|
||||
|
||||
/// 推断目录项与通道商户下唯一支付能力(仅供回显)
|
||||
public String inferSceneCapability(String appId, String provider, String method, String channelMchNo) {
|
||||
return sceneConfigService.inferSceneCapability(appId, provider, method, channelMchNo);
|
||||
}
|
||||
|
||||
/// 查询基础模式各支付渠道对应产品
|
||||
public List<PayRouteBasicConfigResult> listBasicByAppId(String appId) {
|
||||
return basicConfigService.listBasicByAppId(appId);
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
package cn.daxpay.open.payment.merchant.service.route.model;
|
||||
|
||||
import cn.daxpay.open.payment.merchant.entity.route.scene.PayRouteSceneConfig;
|
||||
|
||||
/// # 通道路由命中结果
|
||||
///
|
||||
/// 匹配成功后得到的通道、方式、产品、通道商户及能力,供填充 PayParam。
|
||||
/// 匹配成功后得到的支付产品、通道商户及能力,供填充 PayParam。
|
||||
///
|
||||
/// @param channel 支付通道编码
|
||||
/// @param method 支付方式编码
|
||||
/// @param product 支付产品编码(可为空,由运行时解析)
|
||||
/// @param product 支付产品编码(由通道商户号派生)
|
||||
/// @param channelMchNo 通道商户号(场景/基础模式命中的核心定位字段)
|
||||
/// @param capability 支付能力编码(场景模式命中的能力声明;基础模式可为空)
|
||||
/// @param hitRuleId 精细模式命中的规则 ID(预留字段,当前实现恒为 null)
|
||||
/// @param hitConfigId 场景模式命中的场景配置 ID,基础模式为 null
|
||||
public record RouteHit(String channel, String method, String product, String channelMchNo,
|
||||
String capability, Long hitRuleId, Long hitConfigId) {
|
||||
/// @param capability 支付能力编码(场景模式命中的能力声明;基础模式可为空)
|
||||
public record RouteHit(String product, String channelMchNo, String capability) {
|
||||
|
||||
/// 由场景模式配置构造命中结果
|
||||
public static RouteHit fromScene(PayRouteSceneConfig config) {
|
||||
return new RouteHit(config.getChannel(), config.getMethod(), null,
|
||||
config.getChannelMchNo(), config.getCapability(), null, config.getId());
|
||||
/// 由场景模式配置构造命中结果(product 留空,由运行时按通道商户号派生)
|
||||
public static RouteHit fromScene(String channelMchNo, String capability) {
|
||||
return new RouteHit(null, channelMchNo, capability);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,19 @@ package cn.daxpay.open.payment.merchant.service.route.runtime;
|
||||
|
||||
import cn.daxpay.open.payment.channel.dao.mch.ChannelMerchantManager;
|
||||
import cn.daxpay.open.payment.channel.entity.mch.ChannelMerchant;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteStrategyCapabilitySupport;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 通道路由产品解析器
|
||||
///
|
||||
/// 通道商户号(channelMchNo)与支付产品一一绑定,是路由重构后的唯一定位锚点;
|
||||
/// 通道(channel)/支付方式(method)→产品的旧解析逻辑暂时保留以兼容基础模式回退场景。
|
||||
/// 通道商户号(channelMchNo)与支付产品一一绑定,是路由的唯一定位锚点。
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PayRouteProductResolver {
|
||||
|
||||
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
|
||||
private final ChannelMerchantManager channelMerchantManager;
|
||||
|
||||
/// 根据通道商户号解析支付产品编码(channelMchNo 唯一绑定 product)
|
||||
@@ -38,41 +30,4 @@ public class PayRouteProductResolver {
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.channelMchNotExist", channelMchNo));
|
||||
}
|
||||
|
||||
/// 根据通道商户号解析所属通道编码
|
||||
public String channelOfChannelMchNo(String channelMchNo) {
|
||||
return channelOfProduct(productOfChannelMchNo(channelMchNo));
|
||||
}
|
||||
|
||||
/// 根据通道及支付方式解析产品编码
|
||||
public String resolve(String mchNo, String channel, String method) {
|
||||
return Arrays.stream(ProductEnum.values())
|
||||
.filter(pe -> Objects.equals(pe.getChannel(), channel))
|
||||
.map(ProductEnum::getCode)
|
||||
.filter(product -> payRouteStrategyCapabilitySupport.routeProductSupportsMethod(
|
||||
product, PayMethodEnum.findByCode(method)))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.productNotResolved", channel, method));
|
||||
}
|
||||
|
||||
/// 填充并校验产品字段
|
||||
public String resolveAndFill(String mchNo, String channel, String method, String product) {
|
||||
String resolved = resolve(mchNo, channel, method);
|
||||
if (StrUtil.isNotBlank(product) && !Objects.equals(product, resolved)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.productMismatch", product, resolved);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/// 根据产品编码填充通道
|
||||
public String channelOfProduct(String product) {
|
||||
ProductEnum productEnum = ProductEnum.findByCode(product);
|
||||
if (productEnum == null) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.productInvalid", product);
|
||||
}
|
||||
return productEnum.getChannel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,9 @@ import java.util.Objects;
|
||||
/// # 支付通道路由服务
|
||||
///
|
||||
/// 实现 PayRouteFacade,供 NormalPayService 在支付流程中调用。
|
||||
/// 三种解析路径:
|
||||
/// 1. 直定模式:已传 channelMchNo,跳过路由,直接由 channelMchNo 推导产品并校验/派生能力;
|
||||
/// 2. 兼容跳过:未传 channelMchNo 但已指定 product(重付复用),直接返回;
|
||||
/// 3. 路由模式:按 appId 加载策略,经基础/场景模式匹配后回填 product/channelMchNo/capability。
|
||||
/// 两种解析路径:
|
||||
/// 1. 直定模式:已传 channelMchNo,跳过路由,由 channelMchNo 推导产品;capability 必填,method 未传时由能力反推。
|
||||
/// 2. 路由模式:按 appId 加载策略,经基础/场景模式匹配后回填 product/channelMchNo/capability。
|
||||
/// 调用方需保证 appId 已解析完毕。
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -47,7 +46,7 @@ public class PayRouteService implements PayRouteFacade {
|
||||
private final PayRouteBasicMatcher basicMatcher;
|
||||
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
|
||||
|
||||
/// 实付路由解析:直定模式优先,其次兼容已指定 product,最后按策略模式匹配
|
||||
/// 实付路由解析:直定模式优先,否则按策略模式匹配
|
||||
@Override
|
||||
public void resolve(PayParam payParam) {
|
||||
// 直定模式:指定通道商户号,跳过路由
|
||||
@@ -55,10 +54,6 @@ public class PayRouteService implements PayRouteFacade {
|
||||
resolveDirect(payParam);
|
||||
return;
|
||||
}
|
||||
// 兼容:已指定 product(重付复用),跳过路由
|
||||
if (StrUtil.isNotBlank(payParam.getProduct())) {
|
||||
return;
|
||||
}
|
||||
String appId = payParam.getAppId();
|
||||
var bundle = loadBundle(appId);
|
||||
if (bundle == null || bundle.getStrategy() == null) {
|
||||
|
||||
@@ -11,12 +11,13 @@ import cn.daxpay.open.payment.merchant.param.route.scene.PayRouteSceneCapability
|
||||
import cn.daxpay.open.payment.merchant.param.route.scene.PayRouteSceneConfigBatchParam;
|
||||
import cn.daxpay.open.payment.merchant.param.route.scene.PayRouteSceneConfigItem;
|
||||
import cn.daxpay.open.payment.merchant.result.route.scene.PayRouteSceneConfigResult;
|
||||
import cn.daxpay.open.payment.merchant.service.route.runtime.PayRouteProductResolver;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteConfigProviders;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteI18nHelper;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteStrategyCapabilitySupport;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.service.PayProviderMethodService;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.core.rest.dto.LabelValue;
|
||||
@@ -41,7 +42,6 @@ public class PayRouteSceneConfigService {
|
||||
|
||||
private final PayRouteStrategyManager strategyManager;
|
||||
private final PayRouteSceneConfigManager sceneConfigManager;
|
||||
private final PayRouteProductResolver productResolver;
|
||||
private final MchAppInfoManager mchAppInfoManager;
|
||||
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
|
||||
private final PayRouteMethodValidator payRouteMethodValidator;
|
||||
@@ -66,13 +66,19 @@ public class PayRouteSceneConfigService {
|
||||
sceneConfigManager.deleteByStrategyId(strategy.getId());
|
||||
List<PayRouteSceneConfig> configs = new ArrayList<>();
|
||||
for (PayRouteSceneConfigItem item : param.getItems()) {
|
||||
if (StrUtil.isBlank(item.getMethod()) || !PayRouteConfigProviders.contains(item.getProvider())) {
|
||||
if (StrUtil.isBlank(item.getMethod())) {
|
||||
continue;
|
||||
}
|
||||
// 支付渠道由支付方式派生(OTHER 等无渠道归属或非白名单则跳过)
|
||||
PayProviderEnum provider = PayMethodEnum.findByCode(item.getMethod()).getProvider();
|
||||
if (provider == null || !PayRouteConfigProviders.contains(provider.getCode())) {
|
||||
continue;
|
||||
}
|
||||
String providerCode = provider.getCode();
|
||||
if (isSceneRowEmpty(item)) {
|
||||
continue;
|
||||
}
|
||||
payRouteMethodValidator.validateSceneConfigItem(item.getProvider(), item.getMethod());
|
||||
payRouteMethodValidator.validateSceneConfigItem(providerCode, item.getMethod());
|
||||
// 校验通道商户属本商户且启用
|
||||
ChannelMerchant mch = channelMerchantManager
|
||||
.findByMchNoAndChannelMchNo(mchNo, item.getChannelMchNo())
|
||||
@@ -86,20 +92,16 @@ public class PayRouteSceneConfigService {
|
||||
String product = mch.getProduct();
|
||||
// 校验产品支持该(provider, method)
|
||||
if (!payRouteStrategyCapabilitySupport.routeProductSupportsMethod(
|
||||
product, item.getProvider(), item.getMethod())) {
|
||||
product, providerCode, item.getMethod())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.route.error.sceneMethodProductMismatch",
|
||||
PayRouteI18nHelper.payMethod(item.getMethod()), PayRouteI18nHelper.product(product));
|
||||
}
|
||||
// 校验支付能力在候选集合内
|
||||
payRouteStrategyCapabilitySupport.validateSceneCapability(
|
||||
item.getProvider(), item.getMethod(), item.getChannelMchNo(), item.getCapability());
|
||||
// 通道编码由产品派生
|
||||
String channel = productResolver.channelOfProduct(product);
|
||||
providerCode, item.getMethod(), item.getChannelMchNo(), item.getCapability());
|
||||
PayRouteSceneConfig config = new PayRouteSceneConfig();
|
||||
config.setStrategyId(strategy.getId());
|
||||
config.setProvider(item.getProvider());
|
||||
config.setChannel(channel);
|
||||
config.setMethod(item.getMethod());
|
||||
config.setChannelMchNo(item.getChannelMchNo());
|
||||
config.setCapability(item.getCapability());
|
||||
@@ -140,14 +142,6 @@ public class PayRouteSceneConfigService {
|
||||
return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidates(provider, method, channelMchNo);
|
||||
}
|
||||
|
||||
/// 回显推断支付能力(候选唯一时返回编码)
|
||||
public String inferSceneCapability(String appId, String provider, String method, String channelMchNo) {
|
||||
if (StrUtil.isBlank(channelMchNo)) {
|
||||
return null;
|
||||
}
|
||||
return payRouteStrategyCapabilitySupport.inferSceneCapability(provider, method, channelMchNo);
|
||||
}
|
||||
|
||||
/// 校验批量保存项:支付方式全局唯一
|
||||
private void validateSceneConfigUnique(List<PayRouteSceneConfigItem> items) {
|
||||
Set<String> methodKeys = new HashSet<>();
|
||||
|
||||
@@ -40,6 +40,6 @@ public class PayRouteSceneMatcher {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.route.error.duplicateSceneMethod",
|
||||
method);
|
||||
}
|
||||
return RouteHit.fromScene(candidates.getFirst());
|
||||
return RouteHit.fromScene(candidates.getFirst().getChannelMchNo(), candidates.getFirst().getCapability());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,15 +126,6 @@ public class PayRouteStrategyCapabilitySupport {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 候选唯一时返回能力编码(仅供回显)
|
||||
public String inferSceneCapability(String provider, String method, String channelMchNo) {
|
||||
List<LabelValue> candidates = listSceneCapabilityCandidates(provider, method, channelMchNo);
|
||||
if (candidates.size() == 1) {
|
||||
return candidates.getFirst().getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 传值模式: 商户全部启用通道商户候选(不按支付方式过滤)
|
||||
public List<LabelValue> listDirectChannelMchCandidates(String mchNo) {
|
||||
List<ChannelMerchant> mchants = channelMerchantManager.findAllByMchNo(mchNo);
|
||||
|
||||
Reference in New Issue
Block a user