diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index 73ea1306b..e69de29bb 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -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; diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/route/PayRouteAdminController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/route/PayRouteAdminController.java index 60c441238..7ed85bc36 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/route/PayRouteAdminController.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/route/PayRouteAdminController.java @@ -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 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") diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/NormalPayService.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/NormalPayService.java index 516bd7de4..f207fff9b 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/NormalPayService.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/NormalPayService.java @@ -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); diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/route/PayRouteFacade.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/route/PayRouteFacade.java index 72f080115..6d19c4ab3 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/route/PayRouteFacade.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/pay/service/route/PayRouteFacade.java @@ -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); } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/scene/PayRouteSceneConfig.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/scene/PayRouteSceneConfig.java index 45eb5f55a..bdd3ee857 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/scene/PayRouteSceneConfig.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/entity/route/scene/PayRouteSceneConfig.java @@ -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 listBasicByAppId(String appId) { return basicConfigService.listBasicByAppId(appId); diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/model/RouteHit.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/model/RouteHit.java index a3dc59a4e..efbf1e33e 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/model/RouteHit.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/model/RouteHit.java @@ -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); } } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteProductResolver.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteProductResolver.java index 865ce2cc5..f47654f35 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteProductResolver.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteProductResolver.java @@ -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(); - } } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteService.java index 318320026..c4906666c 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteService.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/runtime/PayRouteService.java @@ -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) { diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java index db1af99f5..296e82074 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneConfigService.java @@ -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 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 items) { Set methodKeys = new HashSet<>(); diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneMatcher.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneMatcher.java index 25e29292c..b7593b8bd 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneMatcher.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/scene/PayRouteSceneMatcher.java @@ -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()); } } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java index 2c134f54b..d1b7d38f3 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/route/support/PayRouteStrategyCapabilitySupport.java @@ -126,15 +126,6 @@ public class PayRouteStrategyCapabilitySupport { .toList(); } - /// 候选唯一时返回能力编码(仅供回显) - public String inferSceneCapability(String provider, String method, String channelMchNo) { - List candidates = listSceneCapabilityCandidates(provider, method, channelMchNo); - if (candidates.size() == 1) { - return candidates.getFirst().getValue(); - } - return null; - } - /// 传值模式: 商户全部启用通道商户候选(不按支付方式过滤) public List listDirectChannelMchCandidates(String mchNo) { List mchants = channelMerchantManager.findAllByMchNo(mchNo);