mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-10 06:46:04 +08:00
refactor(payment): 通道应用类型解析改为多兼容类型遍历
- 微信/抖音/支付宝应用类型映射由单值改为有序兼容列表, 多个同类型应用时跳过并尝试下一个兼容类型 - 抖音 H5 验签 web_app 报错参数改用本地化名称(网站应用), 不再显示裸编码 - 补齐 wx_app_type 东盟四语(id-ID/vi-VN/th-TH/ms-MY) - 抖音直连应用体系(douyin_direct_app*)下线, 迁移至 dy_mch_app/dy_channel_app_capability 统一模式 - 删除已废弃的抖音应用管理菜单, 清理通道子应用配置注释
This commit is contained in:
@@ -142,7 +142,6 @@ INSERT INTO "public"."iam_perm_menu" VALUES (308, 3, 'system:notify', 'admin', '
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (40502, 40508, 'payment:isv', 'admin', 'AlipayIsvAppManage', 'menu.payment.config.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/app/AlipayIsvAppManage', '/payment/config/product/app-manage', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-07-11 03:37:15.43231+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040106, 4040131, 'channel:merchant', 'admin', 'ChannelMerchant', 'menu.payment.merchant.channelMerchant', NULL, 't', 'f', '/payment/global/channel-merchant/ChannelMerchantList', '/payment/global/channel-merchant', NULL, 6, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-03 16:00:00+00', '2026-07-22 13:50:46.61698+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (30403, 301, 'system:protocol', 'admin', 'UserProtocol', 'menu.system.basic.protocol', 'lucide:file-text', 'f', 'f', '/system/basic/protocol/UserProtocolList', '/system/basic/protocol', NULL, 2, 'f', 't', 'f', 1, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-24 10:32:13.183371+00', '2026-07-16 08:43:35.038937+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040115, 4040132, 'channel:app', 'admin', 'DouyinMchAppManage', 'menu.payment.merchant.channelMerchant.douyinApp', NULL, 't', 'f', '/payment/channel/douyin/manage/app/DouyinMchAppManage', '/payment/global/channel-merchant/douyin-app-manage', NULL, 13, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-15 16:00:00+00', '2026-07-22 13:50:46.642335+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (802, 8, 'develop:sign', 'admin', 'DevelopSign', 'menu.develop.sign', 'lucide:pen-tool', 'f', 'f', '/payment/develop/sign/DevelopSign', '/develop/sign', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-23 16:00:00+00', '2026-06-23 16:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (310, 308, 'system:notify:wechat-config', 'admin', 'WechatNotify', 'menu.system.notify.wechatConfig', 'lucide:message-circle', 'f', 'f', '/system/notify/wechat/index', '/system/notify/wechat', NULL, 30, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-09 00:00:00+00', '2026-07-09 00:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040121, 4040130, 'merchant:gateway-aggregate', 'admin', 'AggregateScanConfig', 'menu.payment.merchant.aggregateScan', NULL, 't', 'f', '/payment/merchant/aggregate/AggregateScanConfig', '/payment/merchant/aggregate', NULL, 3, 'f', 't', 'f', 1, 1, 1, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-11 03:00:13.374496+00', '2026-07-13 10:13:20.81126+00');
|
||||
@@ -217,7 +216,6 @@ INSERT INTO "public"."iam_perm_menu" VALUES (91409, 91403, 'merchant:easypay', '
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91501, 91300, 'merchant:credential', 'merchant', 'MerchantCredentialConfig', 'menu.payment.merchant.credential', 'lucide:key', 'f', 'f', '/payment/merchant/credential/MerchantCredentialConfig', '/mch/credential', NULL, 5, 'f', 'f', 'f', 0, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-24 13:08:44.950986+00', '2026-07-24 13:08:44.950986+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91305, 91300, 'merchant:wx-verify', 'merchant', 'MchWxDomainVerifyList', 'menu.payment.merchant.wxVerify', 'lucide:shield-check', 'f', 'f', '/payment/merchant/wx-verify/MchWxDomainVerifyList', '/mch/wx-verify', NULL, 6, 'f', 'f', 'f', 0, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-24 14:17:59.251093+00', '2026-07-24 14:17:59.251093+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91421, 91402, 'channel:app', 'merchant', 'AlipayMchAppManage', 'menu.payment.merchant.channelMerchant.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/mch/app/AlipayMchAppManage', '/mch/channel-merchant/alipay-app-manage', NULL, 4, 'f', 'f', 'f', 0, 1, 1, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 01:53:14.928869+00', '2026-07-25 01:53:14.928869+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91422, 91402, 'channel:app', 'merchant', 'DouyinMchAppManage', 'menu.payment.merchant.channelMerchant.douyinApp', NULL, 't', 'f', '/payment/channel/douyin/manage/app/DouyinMchAppManage', '/mch/channel-merchant/douyin-app-manage', NULL, 5, 'f', 'f', 'f', 0, 1, 1, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 03:24:01.70476+00', '2026-07-25 03:24:01.70476+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91601, 91600, 'develop:trade', 'merchant', 'DevelopTrade', 'menu.develop.trade', 'lucide:credit-card', 'f', 'f', '/payment/develop/trade/DevelopTrade', '/develop/trade', NULL, 1, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91602, 91600, 'develop:gateway', 'merchant', 'DevelopGateway', 'menu.develop.gateway', 'lucide:globe', 'f', 'f', '/payment/develop/gateway/DevelopGateway', '/develop/gateway', NULL, 2, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (91603, 91600, 'develop:sign', 'merchant', 'DevelopSign', 'menu.develop.sign', 'lucide:pen-tool', 'f', 'f', '/payment/develop/sign/DevelopSign', '/develop/sign', NULL, 3, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
|
||||
|
||||
@@ -2605,69 +2605,7 @@ COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."version" IS '乐观
|
||||
COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."deleted" IS '逻辑删除标志';
|
||||
COMMENT ON TABLE "public"."douyin_direct_channel_merchant" IS '抖音直连通道商户绑定';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for douyin_direct_app_auth_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app_auth_config";
|
||||
CREATE TABLE "public"."douyin_direct_app_auth_config" (
|
||||
"id" int8 NOT NULL,
|
||||
"mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"channel_mch_no" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"douyin_direct_app_id" int8,
|
||||
"creator" int8,
|
||||
"create_time" timestamptz(6),
|
||||
"last_modifier" int8,
|
||||
"last_modified_time" timestamptz(6),
|
||||
"version" int4 NOT NULL DEFAULT 0,
|
||||
"deleted" bool NOT NULL DEFAULT false,
|
||||
"app_secret" varchar(512) COLLATE "pg_catalog"."default"
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."id" IS '主键';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."mch_no" IS '商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."channel_mch_no" IS '通道商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."douyin_direct_app_id" IS '关联抖音直连应用 ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."creator" IS '创建人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."last_modifier" IS '最后修改人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."last_modified_time" IS '最后修改时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."version" IS '乐观锁版本号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."deleted" IS '逻辑删除标志';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."app_secret" IS '应用密钥(加密存储)';
|
||||
COMMENT ON TABLE "public"."douyin_direct_app_auth_config" IS '抖音直连商户应用授权认证配置';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for douyin_direct_app
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app";
|
||||
CREATE TABLE "public"."douyin_direct_app" (
|
||||
"id" int8 NOT NULL,
|
||||
"mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"channel_mch_no" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"app_name" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"douyin_app_id" varchar(64) COLLATE "pg_catalog"."default",
|
||||
"creator" int8,
|
||||
"create_time" timestamptz(6),
|
||||
"last_modifier" int8,
|
||||
"last_modified_time" timestamptz(6),
|
||||
"version" int4 NOT NULL DEFAULT 0,
|
||||
"deleted" bool NOT NULL DEFAULT false,
|
||||
"app_type" varchar(32) COLLATE "pg_catalog"."default"
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."id" IS '主键';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."mch_no" IS '商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."channel_mch_no" IS '通道商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."app_name" IS '应用名称';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."douyin_app_id" IS '抖音应用AppId(APPID)';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."creator" IS '创建人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."last_modifier" IS '最后修改人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."last_modified_time" IS '最后修改时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."version" IS '乐观锁版本号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."deleted" IS '逻辑删除标志';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app"."app_type" IS '应用类型(mini_program小程序/mobile_app移动应用/web_app网站应用)';
|
||||
COMMENT ON TABLE "public"."douyin_direct_app" IS '抖音直连商户应用';
|
||||
-- (douyin_direct_app / douyin_direct_app_auth_config 表已废弃, 改用 payment-core 跨通道通用应用模式, 详见 update-tables.sql)
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for device_qr_code
|
||||
@@ -3856,36 +3794,7 @@ COMMENT ON COLUMN "public"."hkrt_isv_channel_merchant"."create_time" IS '创建
|
||||
COMMENT ON COLUMN "public"."hkrt_isv_channel_merchant"."sandbox" IS '是否沙箱环境商户';
|
||||
COMMENT ON TABLE "public"."hkrt_isv_channel_merchant" IS '海科融通通道商户绑定';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for douyin_direct_app_capability
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app_capability";
|
||||
CREATE TABLE "public"."douyin_direct_app_capability" (
|
||||
"id" int8 NOT NULL DEFAULT nextval('douyin_direct_app_capability_id_seq'::regclass),
|
||||
"mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"channel_mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"capability" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"douyin_direct_app_id" int8 NOT NULL,
|
||||
"creator" int8,
|
||||
"create_time" timestamptz(6) DEFAULT now(),
|
||||
"last_modifier" int8,
|
||||
"last_modified_time" timestamptz(6),
|
||||
"version" int4 NOT NULL DEFAULT 0,
|
||||
"deleted" bool NOT NULL DEFAULT false
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."id" IS '主键';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."mch_no" IS '商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."channel_mch_no" IS '通道商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."capability" IS '支付能力编码(如 DOUYIN_QR/DOUYIN_JSAPI 等)';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."douyin_direct_app_id" IS '关联抖音直连应用 ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."creator" IS '创建人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."last_modifier" IS '最后修改人ID';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."last_modified_time" IS '最后修改时间';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."version" IS '乐观锁版本号';
|
||||
COMMENT ON COLUMN "public"."douyin_direct_app_capability"."deleted" IS '逻辑删除标志';
|
||||
COMMENT ON TABLE "public"."douyin_direct_app_capability" IS '抖音直连商户应用支付能力关联';
|
||||
-- (douyin_direct_app_capability 表已废弃, 改用 payment-core 跨通道通用应用模式, 详见 update-tables.sql)
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for base_street
|
||||
@@ -5014,25 +4923,6 @@ COMMENT ON INDEX "public"."uk_douyin_direct_mch_dyid" IS '同一商户下抖音
|
||||
-- ----------------------------
|
||||
ALTER TABLE "public"."douyin_direct_channel_merchant" ADD CONSTRAINT "pk_douyin_direct_channel_merchant" PRIMARY KEY ("id");
|
||||
|
||||
-- ----------------------------
|
||||
-- Primary Key structure for table douyin_direct_app_auth_config
|
||||
-- ----------------------------
|
||||
ALTER TABLE "public"."douyin_direct_app_auth_config" ADD CONSTRAINT "pk_douyin_direct_app_auth_config" PRIMARY KEY ("id");
|
||||
|
||||
-- ----------------------------
|
||||
-- Indexes structure for table douyin_direct_app
|
||||
-- ----------------------------
|
||||
CREATE UNIQUE INDEX "uk_douyin_direct_app_appid" ON "public"."douyin_direct_app" USING btree (
|
||||
"channel_mch_no" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST,
|
||||
"douyin_app_id" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
|
||||
) WHERE deleted = false;
|
||||
COMMENT ON INDEX "public"."uk_douyin_direct_app_appid" IS '同一通道商户下抖音应用 ID 唯一';
|
||||
|
||||
-- ----------------------------
|
||||
-- Primary Key structure for table douyin_direct_app
|
||||
-- ----------------------------
|
||||
ALTER TABLE "public"."douyin_direct_app" ADD CONSTRAINT "pk_douyin_direct_app" PRIMARY KEY ("id");
|
||||
|
||||
-- ----------------------------
|
||||
-- Indexes structure for table device_qr_code
|
||||
-- ----------------------------
|
||||
@@ -5528,25 +5418,6 @@ CREATE UNIQUE INDEX "uk_hkrt_isv_key_prod_sandbox" ON "public"."hkrt_isv_key_con
|
||||
) WHERE deleted = false;
|
||||
COMMENT ON INDEX "public"."uk_hkrt_isv_key_prod_sandbox" IS '同一产品同一环境密钥唯一';
|
||||
|
||||
-- ----------------------------
|
||||
-- Indexes structure for table douyin_direct_app_capability
|
||||
-- ----------------------------
|
||||
CREATE UNIQUE INDEX "uk_douyin_direct_cap_cmchno_cap" ON "public"."douyin_direct_app_capability" USING btree (
|
||||
"channel_mch_no" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST,
|
||||
"capability" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
|
||||
) WHERE deleted = false;
|
||||
COMMENT ON INDEX "public"."uk_douyin_direct_cap_cmchno_cap" IS '同一通道商户下支付能力唯一';
|
||||
|
||||
-- ----------------------------
|
||||
-- Uniques structure for table douyin_direct_app_capability
|
||||
-- ----------------------------
|
||||
ALTER TABLE "public"."douyin_direct_app_capability" ADD CONSTRAINT "uk_douyin_direct_app_capability" UNIQUE ("channel_mch_no", "capability");
|
||||
|
||||
-- ----------------------------
|
||||
-- Primary Key structure for table douyin_direct_app_capability
|
||||
-- ----------------------------
|
||||
ALTER TABLE "public"."douyin_direct_app_capability" ADD CONSTRAINT "douyin_direct_app_capability_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- ----------------------------
|
||||
-- Indexes structure for table base_street
|
||||
-- ----------------------------
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
-- 微信通道旧应用体系下线: 删除已废弃的直连/服务商应用管理菜单
|
||||
-- 4040113=WechatMchAppManage(直连商户应用)、4040119=WechatIsvMchAppManage(ISV子商户应用)、40503=WechatIsvAppManage(ISV服务商应用)
|
||||
-- 入口已由 WxAppHub(id=40106, menu.payment.wx.app)统一接管
|
||||
|
||||
DELETE FROM "public"."iam_perm_menu" WHERE "id" IN (4040113, 4040119, 40503);
|
||||
|
||||
-- 「移动端应用管理」菜单归属迁移: 系统管理>系统配置 → 支付管理(顶级)
|
||||
-- 40504=MobileAppConfig(移动端应用管理)、40505=MobileAppDetail(移动端应用详情)
|
||||
-- 原因: 该配置的 cashier(收银台小程序)凭证被支付核心 CashierAuthService 直接依赖, 属支付链路前置配置;
|
||||
-- 表 pay_platform_mobile_app 前缀亦属支付域。pid 由 304(系统配置) 改为 4(支付管理)。
|
||||
-- 注: 405(支付配置 catalog) 在运营端已软删(deleted=t), 故直接挂顶级 4 下, 与"支付产品配置(40105)"平级。
|
||||
|
||||
-- 40504 主菜单: pid 304→4, menu_code/i18n_key/component/path 支付域化, sort_no 调整到支付产品配置之后
|
||||
UPDATE "public"."iam_perm_menu" SET
|
||||
"pid" = 4,
|
||||
"menu_code" = 'payment:config:mobile-app',
|
||||
"i18n_key" = 'menu.payment.config.mobileApp',
|
||||
"component" = '/payment/config/mobile-app/MobileAppConfig',
|
||||
"path" = '/payment/config/mobile-app',
|
||||
"sort_no" = 2.7
|
||||
WHERE "id" = 40504;
|
||||
|
||||
-- 40505 详情子页: menu_code/i18n_key/component/path 支付域化 (pid 仍挂 40504)
|
||||
UPDATE "public"."iam_perm_menu" SET
|
||||
"menu_code" = 'payment:config:mobile-app-detail',
|
||||
"i18n_key" = 'menu.payment.config.mobileAppDetail',
|
||||
"component" = '/payment/config/mobile-app/detail/MobileAppDetail',
|
||||
"path" = '/payment/config/mobile-app/detail/:appType'
|
||||
WHERE "id" = 40505;
|
||||
|
||||
@@ -14,3 +14,10 @@ DROP TABLE IF EXISTS "public"."wechat_isv_mch_app_capability";
|
||||
-- 微信服务商通道商户: 移除冗余的认证应用类型字段
|
||||
-- 实际 sp/sub 路由由通道能力绑定(WxChannelAppCapability)决定, 该字段不被任何业务逻辑消费
|
||||
ALTER TABLE "public"."wechat_isv_channel_merchant" DROP COLUMN IF EXISTS "auth_app_type";
|
||||
|
||||
-- 抖音通道旧直连应用体系下线: 删除已废弃的直连应用/能力/授权配置表
|
||||
-- 抖音改用 payment-core 跨通道通用应用模式(对齐微信), 应用身份迁移至 dy_mch_app / dy_channel_app_capability 统一体系
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app_auth_config";
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app_capability";
|
||||
DROP TABLE IF EXISTS "public"."douyin_direct_app";
|
||||
DROP SEQUENCE IF EXISTS "public"."douyin_direct_app_capability_id_seq";
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.daxpay.open.channel.alipay.code;
|
||||
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/// # 支付宝直连应用类型与支付能力映射
|
||||
@@ -26,33 +27,39 @@ public final class AlipayDirectAppTypeCode {
|
||||
/// 应用类型: 网站应用
|
||||
public static final String WEB_APP = "web_app";
|
||||
|
||||
/// 支付能力 → 应用类型的默认推导映射
|
||||
/// 支付能力 → 兼容应用类型集合(有序, 首个为兜底推导优先级)
|
||||
///
|
||||
/// - JSAPI 支付(含小程序) → 小程序应用(官方 JSAPI_PAY 主场景)
|
||||
/// - JSAPI → 小程序(场景强绑 buyer_id)
|
||||
/// - APP支付 → 移动应用
|
||||
/// - 其余支付宝能力(付款码/扫码/H5/PC) → 网站应用
|
||||
private static final Map<String, String> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.ALIPAY_JSAPI.getCode(), MINI_PROGRAM,
|
||||
PayCapabilityEnum.ALIPAY_APP.getCode(), MOBILE_APP,
|
||||
PayCapabilityEnum.ALIPAY_BARCODE.getCode(), WEB_APP,
|
||||
PayCapabilityEnum.ALIPAY_QR.getCode(), WEB_APP,
|
||||
PayCapabilityEnum.ALIPAY_H5.getCode(), WEB_APP,
|
||||
PayCapabilityEnum.ALIPAY_PC.getCode(), WEB_APP);
|
||||
/// - 付款码/扫码/H5/PC → 网站应用优先, 兼容小程序/移动应用(不依赖 buyer_id)
|
||||
private static final Map<String, List<String>> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.ALIPAY_JSAPI.getCode(), List.of(MINI_PROGRAM),
|
||||
PayCapabilityEnum.ALIPAY_APP.getCode(), List.of(MOBILE_APP),
|
||||
PayCapabilityEnum.ALIPAY_BARCODE.getCode(), List.of(WEB_APP, MINI_PROGRAM, MOBILE_APP),
|
||||
PayCapabilityEnum.ALIPAY_QR.getCode(), List.of(WEB_APP, MINI_PROGRAM, MOBILE_APP),
|
||||
PayCapabilityEnum.ALIPAY_H5.getCode(), List.of(WEB_APP, MINI_PROGRAM, MOBILE_APP),
|
||||
PayCapabilityEnum.ALIPAY_PC.getCode(), List.of(WEB_APP, MINI_PROGRAM, MOBILE_APP));
|
||||
|
||||
/// 根据支付能力编码推导默认应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
/// 根据支付能力推导全部兼容应用类型(有序, 首个为兜底优先级);未知能力返回空列表
|
||||
public static List<String> resolveCompatibleAppTypes(String capabilityCode) {
|
||||
if (capabilityCode == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
return CAPABILITY_APP_TYPE_MAP.get(capabilityCode);
|
||||
return CAPABILITY_APP_TYPE_MAP.getOrDefault(capabilityCode, List.of());
|
||||
}
|
||||
|
||||
/// 根据支付能力编码推导默认(优先级最高)应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
List<String> types = resolveCompatibleAppTypes(capabilityCode);
|
||||
return types.isEmpty() ? null : types.getFirst();
|
||||
}
|
||||
|
||||
/// 校验应用类型与支付能力是否兼容;未知能力不做强制约束
|
||||
public static boolean isCompatible(String appType, String capability) {
|
||||
String expected = resolveAppType(capability);
|
||||
if (expected == null) {
|
||||
List<String> types = resolveCompatibleAppTypes(capability);
|
||||
if (types.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return expected.equals(appType);
|
||||
return types.contains(appType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +112,10 @@ public class AlipayDirectAppCapabilityService {
|
||||
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导(须已装载 mchNo,租户内)
|
||||
///
|
||||
/// appType 推导要求该通道商户下该类型应用唯一命中:
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个应用:抛 appNotUnique,要求显式配置能力绑
|
||||
/// - 该类型无应用:返回 empty,由调用方走最终报错
|
||||
/// appType 推导:遍历兼容类型(按优先级), 该通道商户下首个唯一命中即返回:
|
||||
/// - 某类型恰好 1 个:返回该应用
|
||||
/// - 某类型多个/无:跳过, 继续下一个兼容类型
|
||||
/// - 全部兼容类型均未唯一命中:返回 empty, 由调用方走最终报错
|
||||
///
|
||||
/// 认证无上下文请用 [#resolveAppNotTenant]。
|
||||
public Optional<AlipayDirectApp> resolveApp(String channelMchNo, String capability) {
|
||||
@@ -127,15 +127,9 @@ public class AlipayDirectAppCapabilityService {
|
||||
if (rel.isPresent()) {
|
||||
return alipayDirectAppManager.findById(rel.get().getAlipayDirectAppId());
|
||||
}
|
||||
// 2. appType 推导:要求该通道商户下该类型应用唯一命中,>1 拒绝猜测
|
||||
String appType = AlipayDirectAppTypeCode.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
// 2. appType 推导:遍历兼容类型(按优先级), 该通道商户下首个唯一命中即返回; 多个则跳过
|
||||
for (String appType : AlipayDirectAppTypeCode.resolveCompatibleAppTypes(capability)) {
|
||||
List<AlipayDirectApp> apps = alipayDirectAppManager.listByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return Optional.of(apps.getFirst());
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 支付宝直连商户应用管理
|
||||
///
|
||||
@@ -62,14 +64,23 @@ public class AlipayDirectAppService {
|
||||
}
|
||||
|
||||
/// 更新支付宝直连商户应用
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(AlipayDirectAppParam param) {
|
||||
var entity = alipayDirectAppManager.findById(param.getId())
|
||||
// 支付宝: 直连商户应用不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.mchAppNotFound"));
|
||||
this.assertScopeMatch(entity, param.getMchNo(), param.getChannelMchNo());
|
||||
this.assertAliAppIdUnique(entity.getMchNo(), entity.getChannelMchNo(), param.getAliAppId(), param.getId());
|
||||
// 记录原始应用类型, 用于判断是否需要联动清理支付能力绑定
|
||||
String oldAppType = entity.getAppType();
|
||||
AlipayDirectAppConvert.CONVERT.copy(param, entity);
|
||||
alipayDirectAppManager.updateById(entity);
|
||||
// 应用类型变更: 旧 appType 下的能力绑定对新类型不再兼容, 清理后由用户重新配置
|
||||
if (!Objects.equals(oldAppType, param.getAppType())) {
|
||||
alipayDirectAppCapabilityService.deleteByAlipayDirectAppId(entity.getId());
|
||||
log.warn("支付宝直连商户应用[{}] appType 从[{}]变更为[{}], 已清理能力绑定",
|
||||
entity.getId(), oldAppType, param.getAppType());
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除应用(级联删除密钥配置、授权认证配置与能力关联)
|
||||
|
||||
@@ -26,12 +26,15 @@ public final class WechatAppTypeCode {
|
||||
/// 应用类型: 移动应用
|
||||
public static final String MOBILE_APP = "mobile_app";
|
||||
|
||||
/// 支付能力 → 应用类型的默认推导映射
|
||||
/// 支付能力 → 默认应用类型映射
|
||||
///
|
||||
/// 注意: 本类已废弃(运行时无调用), 完整多值兼容规则(扫码/付款码/H5 的兼容类型)统一以
|
||||
/// payment-core 的 [cn.daxpay.open.payment.wx.enums.WxAppTypeEnum#resolveCompatibleAppTypes] 为准。
|
||||
///
|
||||
/// - JSAPI支付 → 公众号
|
||||
/// - 小程序支付 → 小程序
|
||||
/// - APP支付 → 移动应用
|
||||
/// - 扫码/H5/付款码 → 公众号(微信扫码/H5/付款码通常使用公众号应用)
|
||||
/// - 扫码/H5/付款码 → 公众号(默认, 实际兼容类型见 WxAppTypeEnum)
|
||||
private static final Map<String, String> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.WECHAT_JSAPI.getCode(), OFFICIAL_ACCOUNT,
|
||||
PayCapabilityEnum.WECHAT_QR.getCode(), OFFICIAL_ACCOUNT,
|
||||
|
||||
@@ -5,7 +5,6 @@ import cn.daxpay.open.payment.douyin.convert.merchant.DyMchAppConvert;
|
||||
import cn.daxpay.open.payment.douyin.result.merchant.DyMchAppResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -27,7 +26,6 @@ public class DyMchApp extends MchBaseEntity implements ToResult<DyMchAppResult>
|
||||
|
||||
/// 应用类型
|
||||
/// @see cn.daxpay.open.payment.douyin.enums.DyAppTypeEnum
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String appType;
|
||||
|
||||
/// 抖音应用AppId
|
||||
|
||||
@@ -5,7 +5,6 @@ import cn.daxpay.open.payment.douyin.result.platform.DyPlatformAppResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -27,7 +26,6 @@ public class DyPlatformApp extends MpBaseEntity implements ToResult<DyPlatformAp
|
||||
|
||||
/// 应用类型
|
||||
/// @see cn.daxpay.open.payment.douyin.enums.DyAppTypeEnum
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String appType;
|
||||
|
||||
/// 抖音应用AppId
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -26,16 +27,16 @@ public enum DyAppTypeEnum implements I18nSupport {
|
||||
|
||||
private final String code;
|
||||
|
||||
/// 支付能力 → 应用类型的默认推导映射
|
||||
/// 支付能力 → 兼容应用类型集合(有序, 首个为兜底推导优先级)
|
||||
///
|
||||
/// - JSAPI → 小程序
|
||||
/// - JSAPI → 小程序(场景强绑 openId)
|
||||
/// - APP支付 → 移动应用
|
||||
/// - 扫码/H5 → 网站应用
|
||||
private static final Map<String, String> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.DOUYIN_JSAPI.getCode(), MINI_PROGRAM.code,
|
||||
PayCapabilityEnum.DOUYIN_APP.getCode(), MOBILE_APP.code,
|
||||
PayCapabilityEnum.DOUYIN_QR.getCode(), WEB_APP.code,
|
||||
PayCapabilityEnum.DOUYIN_H5.getCode(), WEB_APP.code);
|
||||
/// - 扫码/H5 → 网站应用优先, 兼容小程序/移动应用(不依赖 openId)
|
||||
private static final Map<String, List<String>> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.DOUYIN_JSAPI.getCode(), List.of(MINI_PROGRAM.code),
|
||||
PayCapabilityEnum.DOUYIN_APP.getCode(), List.of(MOBILE_APP.code),
|
||||
PayCapabilityEnum.DOUYIN_QR.getCode(), List.of(WEB_APP.code, MINI_PROGRAM.code, MOBILE_APP.code),
|
||||
PayCapabilityEnum.DOUYIN_H5.getCode(), List.of(WEB_APP.code, MINI_PROGRAM.code, MOBILE_APP.code));
|
||||
|
||||
/// 翻译 key 前缀
|
||||
@Override
|
||||
@@ -54,20 +55,26 @@ public enum DyAppTypeEnum implements I18nSupport {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/// 根据支付能力编码推导默认应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
/// 根据支付能力推导全部兼容应用类型(有序, 首个为兜底优先级);未知能力返回空列表
|
||||
public static List<String> resolveCompatibleAppTypes(String capabilityCode) {
|
||||
if (capabilityCode == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
return CAPABILITY_APP_TYPE_MAP.get(capabilityCode);
|
||||
return CAPABILITY_APP_TYPE_MAP.getOrDefault(capabilityCode, List.of());
|
||||
}
|
||||
|
||||
/// 根据支付能力编码推导默认(优先级最高)应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
List<String> types = resolveCompatibleAppTypes(capabilityCode);
|
||||
return types.isEmpty() ? null : types.getFirst();
|
||||
}
|
||||
|
||||
/// 校验应用类型与支付能力是否兼容;未知能力不做强制约束
|
||||
public static boolean isCompatible(String appType, String capability) {
|
||||
String expected = resolveAppType(capability);
|
||||
if (expected == null) {
|
||||
List<String> types = resolveCompatibleAppTypes(capability);
|
||||
if (types.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return expected.equals(appType);
|
||||
return types.contains(appType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import cn.daxpay.open.payment.douyin.enums.DyAppTypeEnum;
|
||||
import cn.daxpay.open.payment.douyin.facade.DouyinAppFacade;
|
||||
import cn.daxpay.open.payment.douyin.facade.DyAppView;
|
||||
import cn.daxpay.open.payment.douyin.facade.DyIsvAppPair;
|
||||
import cn.daxpay.open.platform.common.i18n.util.I18nUtil;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
@@ -148,24 +149,18 @@ public class DyAppResolveService implements DouyinAppFacade {
|
||||
throw new DataNotExistException("error.payment.douyin.channelAppIdNotFound", channelAppId);
|
||||
}
|
||||
|
||||
/// appType 推导兜底:要求该类型平台应用唯一命中
|
||||
/// appType 推导兜底:按兼容类型优先级遍历, 首个唯一命中的平台应用返回
|
||||
///
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个平台应用:抛 notUnique,要求显式配置通道能力绑
|
||||
/// - 该类型无平台应用:返回 null,由调用方走最终报错
|
||||
/// - 某类型恰好 1 个:返回该应用
|
||||
/// - 某类型多个/无:跳过, 继续下一个兼容类型
|
||||
/// - 全部兼容类型均未唯一命中:返回 null, 由调用方走最终报错
|
||||
private DyAppView resolvePlatformFallback(String capability) {
|
||||
if (StrUtil.isBlank(capability)) {
|
||||
return null;
|
||||
}
|
||||
// appType 推导:要求该类型平台应用唯一命中,>1 拒绝猜测
|
||||
String appType = DyAppTypeEnum.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
// 遍历兼容 appType(按优先级), 首个唯一命中即返回; 某类型存在多个则跳过, 避免猜测
|
||||
for (String appType : DyAppTypeEnum.resolveCompatibleAppTypes(capability)) {
|
||||
List<DyPlatformApp> apps = dyPlatformAppManager.listByAppType(appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型平台应用,请显式配置通道能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.payment.douyin.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return this.toPlatformView(apps.getFirst());
|
||||
}
|
||||
@@ -203,17 +198,19 @@ public class DyAppResolveService implements DouyinAppFacade {
|
||||
}
|
||||
// 3. 平台档 web_app 唯一命中
|
||||
List<DyPlatformApp> webApps = dyPlatformAppManager.listByAppType(DyAppTypeEnum.WEB_APP.getCode());
|
||||
// 网站应用类型本地化名称
|
||||
String webAppTypeLabel = I18nUtil.getEnumName(DyAppTypeEnum.WEB_APP);
|
||||
if (webApps.size() > 1) {
|
||||
// 存在多个同类型平台应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.payment.douyin.appNotUnique", DyAppTypeEnum.WEB_APP.getCode());
|
||||
"error.payment.douyin.appNotUnique", webAppTypeLabel);
|
||||
}
|
||||
if (webApps.size() == 1) {
|
||||
return this.toPlatformView(webApps.getFirst());
|
||||
}
|
||||
// 抖音: 未配置 H5 验签所需的 web_app 类型应用
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.payment.douyin.appNotConfigured", DyAppTypeEnum.WEB_APP.getCode());
|
||||
"error.payment.douyin.appNotConfigured", webAppTypeLabel);
|
||||
}
|
||||
|
||||
/// 按真实 douyinAppId 解析:商户档优先, 平台档兜底
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 商户抖音应用管理
|
||||
///
|
||||
@@ -75,10 +76,18 @@ public class DyMchAppService {
|
||||
String mchNo = entity.getMchNo();
|
||||
this.assertDouyinAppIdUnique(mchNo, param.getDouyinAppId(), param.getId());
|
||||
this.validateAppType(param.getAppType());
|
||||
// 记录原始应用类型, 用于判断是否需要联动清理支付能力绑定
|
||||
String oldAppType = entity.getAppType();
|
||||
DyMchAppConvert.CONVERT.copy(param, entity);
|
||||
// 保持主表商户号不变
|
||||
entity.setMchNo(mchNo);
|
||||
dyMchAppManager.updateById(entity);
|
||||
// 应用类型变更: 旧 appType 下的能力绑定对新类型不再兼容, 清理后由用户重新配置
|
||||
if (!Objects.equals(oldAppType, param.getAppType())) {
|
||||
dyChannelAppCapabilityManager.deleteByScopeAndRefId(AppScopeEnum.MERCHANT.getCode(), entity.getId());
|
||||
log.warn("商户抖音应用[{}] appType 从[{}]变更为[{}], 已清理通道能力绑定",
|
||||
entity.getId(), oldAppType, param.getAppType());
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除应用(被通道能力绑定引用时拒删;级联删除授权认证配置)
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 平台抖音应用管理
|
||||
///
|
||||
@@ -70,8 +71,17 @@ public class DyPlatformAppService {
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.douyin.appNotFound"));
|
||||
this.assertDouyinAppIdUnique(param.getDouyinAppId(), param.getId());
|
||||
this.validateAppType(param.getAppType());
|
||||
// 记录原始应用类型, 用于判断是否需要联动清理支付能力绑定
|
||||
String oldAppType = entity.getAppType();
|
||||
DyPlatformAppConvert.CONVERT.copy(param, entity);
|
||||
dyPlatformAppManager.updateById(entity);
|
||||
// 应用类型变更: 旧 appType 下的能力绑定对新类型不再兼容, 清理后由用户重新配置
|
||||
if (!Objects.equals(oldAppType, param.getAppType())) {
|
||||
dyPlatformAppCapabilityManager.deleteByDyPlatformAppId(entity.getId());
|
||||
dyChannelAppCapabilityManager.deleteByScopeAndRefId(AppScopeEnum.PLATFORM.getCode(), entity.getId());
|
||||
log.warn("平台抖音应用[{}] appType 从[{}]变更为[{}], 已清理平台默认能力绑定与通道能力绑定",
|
||||
entity.getId(), oldAppType, param.getAppType());
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除应用(被能力绑定引用时拒删;级联删除授权认证配置)
|
||||
|
||||
@@ -5,7 +5,6 @@ import cn.daxpay.open.payment.wx.convert.merchant.WxMchAppConvert;
|
||||
import cn.daxpay.open.payment.wx.result.merchant.WxMchAppResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -27,7 +26,6 @@ public class WxMchApp extends MchBaseEntity implements ToResult<WxMchAppResult>
|
||||
|
||||
/// 应用类型
|
||||
/// @see cn.daxpay.open.payment.wx.enums.WxAppTypeEnum
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String appType;
|
||||
|
||||
/// 微信应用AppId
|
||||
|
||||
@@ -5,7 +5,6 @@ import cn.daxpay.open.payment.wx.result.platform.WxPlatformAppResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -27,7 +26,6 @@ public class WxPlatformApp extends MpBaseEntity implements ToResult<WxPlatformAp
|
||||
|
||||
/// 应用类型
|
||||
/// @see cn.daxpay.open.payment.wx.enums.WxAppTypeEnum
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String appType;
|
||||
|
||||
/// 微信应用AppId
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -26,19 +27,21 @@ public enum WxAppTypeEnum implements I18nSupport {
|
||||
|
||||
private final String code;
|
||||
|
||||
/// 支付能力 → 应用类型的默认推导映射
|
||||
/// 支付能力 → 兼容应用类型集合(有序, 首个为兜底推导优先级)
|
||||
///
|
||||
/// - JSAPI/扫码/H5/付款码/收银台 → 公众号
|
||||
/// - JSAPI/收银台 → 公众号(场景强绑 openId)
|
||||
/// - 扫码/付款码 → 公众号优先, 兼容小程序/移动应用(不依赖 openId)
|
||||
/// - H5 → 公众号优先, 兼容移动应用(微信 H5 不支持小程序 appid)
|
||||
/// - 小程序支付 → 小程序
|
||||
/// - APP支付 → 移动应用
|
||||
private static final Map<String, String> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.WECHAT_JSAPI.getCode(), OFFICIAL_ACCOUNT.code,
|
||||
PayCapabilityEnum.WECHAT_QR.getCode(), OFFICIAL_ACCOUNT.code,
|
||||
PayCapabilityEnum.WECHAT_H5.getCode(), OFFICIAL_ACCOUNT.code,
|
||||
PayCapabilityEnum.WECHAT_BARCODE.getCode(), OFFICIAL_ACCOUNT.code,
|
||||
PayCapabilityEnum.WECHAT_CASHIER.getCode(), OFFICIAL_ACCOUNT.code,
|
||||
PayCapabilityEnum.WECHAT_MINI.getCode(), MINI_PROGRAM.code,
|
||||
PayCapabilityEnum.WECHAT_APP.getCode(), MOBILE_APP.code);
|
||||
private static final Map<String, List<String>> CAPABILITY_APP_TYPE_MAP = Map.of(
|
||||
PayCapabilityEnum.WECHAT_JSAPI.getCode(), List.of(OFFICIAL_ACCOUNT.code),
|
||||
PayCapabilityEnum.WECHAT_QR.getCode(), List.of(OFFICIAL_ACCOUNT.code, MINI_PROGRAM.code, MOBILE_APP.code),
|
||||
PayCapabilityEnum.WECHAT_H5.getCode(), List.of(OFFICIAL_ACCOUNT.code, MOBILE_APP.code),
|
||||
PayCapabilityEnum.WECHAT_BARCODE.getCode(), List.of(OFFICIAL_ACCOUNT.code, MINI_PROGRAM.code, MOBILE_APP.code),
|
||||
PayCapabilityEnum.WECHAT_CASHIER.getCode(), List.of(OFFICIAL_ACCOUNT.code),
|
||||
PayCapabilityEnum.WECHAT_MINI.getCode(), List.of(MINI_PROGRAM.code),
|
||||
PayCapabilityEnum.WECHAT_APP.getCode(), List.of(MOBILE_APP.code));
|
||||
|
||||
/// 翻译 key 前缀
|
||||
@Override
|
||||
@@ -57,20 +60,26 @@ public enum WxAppTypeEnum implements I18nSupport {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/// 根据支付能力编码推导默认应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
/// 根据支付能力推导全部兼容应用类型(有序, 首个为兜底优先级);未知能力返回空列表
|
||||
public static List<String> resolveCompatibleAppTypes(String capabilityCode) {
|
||||
if (capabilityCode == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
return CAPABILITY_APP_TYPE_MAP.get(capabilityCode);
|
||||
return CAPABILITY_APP_TYPE_MAP.getOrDefault(capabilityCode, List.of());
|
||||
}
|
||||
|
||||
/// 根据支付能力编码推导默认(优先级最高)应用类型;未知能力返回 null
|
||||
public static String resolveAppType(String capabilityCode) {
|
||||
List<String> types = resolveCompatibleAppTypes(capabilityCode);
|
||||
return types.isEmpty() ? null : types.getFirst();
|
||||
}
|
||||
|
||||
/// 校验应用类型与支付能力是否兼容;未知能力不做强制约束
|
||||
public static boolean isCompatible(String appType, String capability) {
|
||||
String expected = resolveAppType(capability);
|
||||
if (expected == null) {
|
||||
List<String> types = resolveCompatibleAppTypes(capability);
|
||||
if (types.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return expected.equals(appType);
|
||||
return types.contains(appType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,24 +176,18 @@ public class WxAppResolveService implements WxAppFacade {
|
||||
throw new DataNotExistException("error.payment.wx.channelAppIdNotFound", channelAppId);
|
||||
}
|
||||
|
||||
/// appType 推导兜底:要求该类型平台应用唯一命中
|
||||
/// appType 推导兜底:按兼容类型优先级遍历, 首个唯一命中的平台应用返回
|
||||
///
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个平台应用:抛 notUnique,要求显式配置通道能力绑
|
||||
/// - 该类型无平台应用:返回 null,由调用方走最终报错
|
||||
/// - 某类型恰好 1 个:返回该应用
|
||||
/// - 某类型多个/无:跳过, 继续下一个兼容类型
|
||||
/// - 全部兼容类型均未唯一命中:返回 null, 由调用方走最终报错
|
||||
private WxAppView resolvePlatformFallback(String capability) {
|
||||
if (StrUtil.isBlank(capability)) {
|
||||
return null;
|
||||
}
|
||||
// appType 推导:要求该类型平台应用唯一命中,>1 拒绝猜测
|
||||
String appType = WxAppTypeEnum.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
// 遍历兼容 appType(按优先级), 首个唯一命中即返回; 某类型存在多个则跳过, 避免猜测
|
||||
for (String appType : WxAppTypeEnum.resolveCompatibleAppTypes(capability)) {
|
||||
List<WxPlatformApp> apps = wxPlatformAppManager.listByAppType(appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型平台应用,请显式配置通道能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.payment.wx.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return this.toPlatformView(apps.getFirst());
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 商户微信应用管理
|
||||
///
|
||||
@@ -75,10 +76,18 @@ public class WxMchAppService {
|
||||
String mchNo = entity.getMchNo();
|
||||
this.assertWxAppIdUnique(mchNo, param.getWxAppId(), param.getId());
|
||||
this.validateAppType(param.getAppType());
|
||||
// 记录原始应用类型, 用于判断是否需要联动清理支付能力绑定
|
||||
String oldAppType = entity.getAppType();
|
||||
WxMchAppConvert.CONVERT.copy(param, entity);
|
||||
// 保持主表商户号不变
|
||||
entity.setMchNo(mchNo);
|
||||
wxMchAppManager.updateById(entity);
|
||||
// 应用类型变更: 旧 appType 下的能力绑定对新类型不再兼容, 清理后由用户重新配置
|
||||
if (!Objects.equals(oldAppType, param.getAppType())) {
|
||||
wxChannelAppCapabilityManager.deleteByScopeAndRefId(AppScopeEnum.MERCHANT.getCode(), entity.getId());
|
||||
log.warn("商户微信应用[{}] appType 从[{}]变更为[{}], 已清理通道能力绑定",
|
||||
entity.getId(), oldAppType, param.getAppType());
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除应用(被通道能力绑定引用时拒删;级联删除授权认证配置)
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 平台微信应用管理
|
||||
///
|
||||
@@ -70,8 +71,17 @@ public class WxPlatformAppService {
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.wx.appNotFound"));
|
||||
this.assertWxAppIdUnique(param.getWxAppId(), param.getId());
|
||||
this.validateAppType(param.getAppType());
|
||||
// 记录原始应用类型, 用于判断是否需要联动清理支付能力绑定
|
||||
String oldAppType = entity.getAppType();
|
||||
WxPlatformAppConvert.CONVERT.copy(param, entity);
|
||||
wxPlatformAppManager.updateById(entity);
|
||||
// 应用类型变更: 旧 appType 下的能力绑定对新类型不再兼容, 清理后由用户重新配置
|
||||
if (!Objects.equals(oldAppType, param.getAppType())) {
|
||||
wxPlatformAppCapabilityManager.deleteByWxPlatformAppId(entity.getId());
|
||||
wxChannelAppCapabilityManager.deleteByScopeAndRefId(AppScopeEnum.PLATFORM.getCode(), entity.getId());
|
||||
log.warn("平台微信应用[{}] appType 从[{}]变更为[{}], 已清理平台默认能力绑定与通道能力绑定",
|
||||
entity.getId(), oldAppType, param.getAppType());
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除应用(被能力绑定引用时拒删;级联删除授权认证配置)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"official_account": "Akun Resmi",
|
||||
"mini_program": "Program Kecil",
|
||||
"mobile_app": "Aplikasi Seluler"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"official_account": "Akaun Rasmi",
|
||||
"mini_program": "Program Mini",
|
||||
"mobile_app": "Apl Mudah Alih"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"official_account": "บัญชีอย่างเป็นทางการ",
|
||||
"mini_program": "มินิโปรแกรม",
|
||||
"mobile_app": "แอพมือถือ"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"official_account": "Tài khoản chính thức",
|
||||
"mini_program": "Chương trình nhỏ",
|
||||
"mobile_app": "Ứng dụng di động"
|
||||
}
|
||||
@@ -167,21 +167,17 @@ daxpay:
|
||||
# 关闭后不可切换到沙箱; 若启动时已有产品处于沙箱, 将自动重置为生产环境
|
||||
sandbox-enabled: true
|
||||
# 通道适配子应用配置
|
||||
# 按支付通道拆分的独立部署子服务, 主服务通过 HTTP 调用对应子服务完成通道对接。
|
||||
# 拆分目的: 通道 SDK 依赖隔离(避免冲突) + 独立升级 + 弹性伸缩
|
||||
# 子应用编号(one/two/three)固定且不可改名, 与路由策略中的通道分配一一对应。
|
||||
channel:
|
||||
# 子应用1: 支付宝/微信支付/银联商务/(已启用, 本机启动的 dax-pay-channel-one 服务)
|
||||
one:
|
||||
base-url: http://127.0.0.1:20100
|
||||
# 通道传输 AES 密钥(强制常开,恰好 32 字符;仅本地占位,勿用于生产)
|
||||
# 须与子应用一致: dax-pay-channel-one application.yml / dax-pay-channel-one-go config.yaml 的 channel.transport-encrypt.key
|
||||
# 通道传输 AES 密钥(强制常开)
|
||||
transport-encrypt:
|
||||
key: 0123456789abcdef0123456789abcdef
|
||||
# 子应用2: 其他支付通道
|
||||
two:
|
||||
base-url: http://127.0.0.1:20200
|
||||
# 须与子应用一致: dax-pay-channel-two application.yml 的 channel.transport-encrypt.key
|
||||
# 须与子应用一致
|
||||
transport-encrypt:
|
||||
key: fedcba9876543210fedcba9876543210
|
||||
# 子应用3: 其他通道(未来扩展, 暂未启用)
|
||||
|
||||
Reference in New Issue
Block a user