diff --git a/_config/sql/douyin-tables.sql b/_config/sql/douyin-tables.sql new file mode 100644 index 000000000..acf0bd910 --- /dev/null +++ b/_config/sql/douyin-tables.sql @@ -0,0 +1,159 @@ +-- ===================================================================== +-- 抖音支付直连通道相关表 +-- 对应实体: +-- org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectChannelMerchant +-- org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectKeyConfig +-- org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectApp +-- org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectAppAuthConfig +-- 执行方式: 手动执行(业务表 DDL 不入库, 由开发者按需在数据库执行) +-- 注意: 加密字段(DataEncryptTypeHandler)在数据库层均为普通 text, 加解密在应用层完成 +-- ===================================================================== + +-- ========================= +-- 1. 抖音直连通道商户绑定 douyin_direct_channel_merchant +-- ========================= +DROP TABLE IF EXISTS "public"."douyin_direct_channel_merchant"; + +CREATE TABLE "public"."douyin_direct_channel_merchant" ( + "id" int8 NOT NULL, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "product" varchar(32) NOT NULL, + "dy_mch_id" varchar(64), + "creator" int8, + "create_time" timestamptz(6), + "last_modifier" int8, + "last_modified_time" timestamptz(6), + "version" int4 DEFAULT 0 NOT NULL, + "deleted" bool DEFAULT false NOT NULL +); + +COMMENT ON TABLE "public"."douyin_direct_channel_merchant" IS '抖音直连通道商户绑定'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."id" IS '主键'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."mch_no" IS '商户号'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."channel_mch_no" IS '通道商户号(系统生成雪花号)'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."product" IS '所属支付产品'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."dy_mch_id" IS '抖音商户号(MCHID)'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."creator" IS '创建人'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."create_time" IS '创建时间'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."last_modifier" IS '最后修改人'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."last_modified_time" IS '最后修改时间'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."version" IS '乐观锁版本号'; +COMMENT ON COLUMN "public"."douyin_direct_channel_merchant"."deleted" IS '逻辑删除标志'; + +ALTER TABLE "public"."douyin_direct_channel_merchant" + ADD CONSTRAINT "pk_douyin_direct_channel_merchant" PRIMARY KEY ("id"); + + +-- ========================= +-- 2. 抖音直连密钥配置 douyin_direct_key_config +-- 商户维度的密钥和证书配置, 按通道商户号关联, 敏感字段加密存储 +-- ========================= +DROP TABLE IF EXISTS "public"."douyin_direct_key_config"; + +CREATE TABLE "public"."douyin_direct_key_config" ( + "id" int8 NOT NULL, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "merchant_private_key" text, + "merchant_serial_number" varchar(64), + "encrypt_key" text, + "creator" int8, + "create_time" timestamptz(6), + "last_modifier" int8, + "last_modified_time" timestamptz(6), + "version" int4 DEFAULT 0 NOT NULL, + "deleted" bool DEFAULT false NOT NULL +); + +COMMENT ON TABLE "public"."douyin_direct_key_config" IS '抖音直连密钥配置'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."id" IS '主键'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."mch_no" IS '商户号'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."channel_mch_no" IS '通道商户号'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."merchant_private_key" IS '商户私钥(加密存储)'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."merchant_serial_number" IS '商家公钥证书序列号'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."encrypt_key" IS '接口加密密钥(加密存储)'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."creator" IS '创建人'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."create_time" IS '创建时间'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."last_modifier" IS '最后修改人'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."last_modified_time" IS '最后修改时间'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."version" IS '乐观锁版本号'; +COMMENT ON COLUMN "public"."douyin_direct_key_config"."deleted" IS '逻辑删除标志'; + +ALTER TABLE "public"."douyin_direct_key_config" + ADD CONSTRAINT "pk_douyin_direct_key_config" PRIMARY KEY ("id"); + + +-- ========================= +-- 3. 抖音直连商户应用 douyin_direct_app +-- 每个应用关联一个通道商户号, 拥有独立的抖音应用ID +-- ========================= +DROP TABLE IF EXISTS "public"."douyin_direct_app"; + +CREATE TABLE "public"."douyin_direct_app" ( + "id" int8 NOT NULL, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "app_name" varchar(64), + "douyin_app_id" varchar(64), + "app_type" varchar(32), + "creator" int8, + "create_time" timestamptz(6), + "last_modifier" int8, + "last_modified_time" timestamptz(6), + "version" int4 DEFAULT 0 NOT NULL, + "deleted" bool DEFAULT false NOT NULL +); + +COMMENT ON TABLE "public"."douyin_direct_app" IS '抖音直连商户应用'; +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"."app_type" IS '应用类型: mini_program-小程序, mobile_app-移动应用, web_app-网站应用'; +COMMENT ON COLUMN "public"."douyin_direct_app"."creator" IS '创建人'; +COMMENT ON COLUMN "public"."douyin_direct_app"."create_time" IS '创建时间'; +COMMENT ON COLUMN "public"."douyin_direct_app"."last_modifier" IS '最后修改人'; +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 '逻辑删除标志'; + +ALTER TABLE "public"."douyin_direct_app" + ADD CONSTRAINT "pk_douyin_direct_app" PRIMARY KEY ("id"); + + +-- ========================= +-- 4. 抖音直连商户应用授权认证配置 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) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "douyin_direct_app_id" int8, + "auth_callback_url" varchar(512), + "creator" int8, + "create_time" timestamptz(6), + "last_modifier" int8, + "last_modified_time" timestamptz(6), + "version" int4 DEFAULT 0 NOT NULL, + "deleted" bool DEFAULT false NOT NULL +); + +COMMENT ON TABLE "public"."douyin_direct_app_auth_config" IS '抖音直连商户应用授权认证配置'; +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(指向 douyin_direct_app.id)'; +COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."auth_callback_url" IS '授权回调地址'; +COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."creator" IS '创建人'; +COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."create_time" IS '创建时间'; +COMMENT ON COLUMN "public"."douyin_direct_app_auth_config"."last_modifier" IS '最后修改人'; +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 '逻辑删除标志'; + +ALTER TABLE "public"."douyin_direct_app_auth_config" + ADD CONSTRAINT "pk_douyin_direct_app_auth_config" PRIMARY KEY ("id"); diff --git a/_config/sql/iam_perm_menu.sql b/_config/sql/iam_perm_menu.sql index 29099c3c4..342357350 100644 --- a/_config/sql/iam_perm_menu.sql +++ b/_config/sql/iam_perm_menu.sql @@ -12,7 +12,7 @@ Target Server Version : 160009 (160009) File Encoding : 65001 - Date: 13/06/2026 17:02:31 + Date: 15/06/2026 22:06:18 */ @@ -139,92 +139,14 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040113, 40401, 'payment:merchant:c INSERT INTO "public"."iam_perm_menu" VALUES (4040114, 40401, 'payment:merchant:channelMerchant:alipayApp', 'admin', 'AlipayMchAppManage', '支付宝通道商户应用', 'Alipay Channel Merchant App', 'menu.payment.merchant.channelMerchant.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/mch/app/AlipayMchAppManage', '/payment/merchant/channel-merchant/alipay-app-manage', NULL, 12, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-11 22:28:11.274785+00', '2026-06-11 22:28:11.274785+00'); INSERT INTO "public"."iam_perm_menu" VALUES (404, NULL, 'payment:merchant', 'admin', 'PaymentMerchant', '商户管理', 'Merchant Management', 'menu.payment.merchant', 'lucide:store', 'f', 'f', NULL, '/payment/merchant', NULL, 4, 't', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-05 16:00:00+00', '2026-06-13 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (30403, 304, 'system:credential:config', 'admin', 'PlatformCredentialConfig', '对接配置', 'Credential Config', 'menu.system.config.credential', 'lucide:key-round', 'f', 'f', '/system/config/credential/PlatformCredentialConfig', '/system/config/credential', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00'); - --- 支付配置目录 -INSERT INTO "public"."iam_perm_menu" VALUES (405, 4, 'payment:config', 'admin', 'PaymentConfig', '支付配置', 'Payment Config', 'menu.payment.config', 'lucide:settings-2', 'f', 'f', NULL, '/payment/config', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00') -ON CONFLICT (id) DO UPDATE SET - menu_code = EXCLUDED.menu_code, - name = EXCLUDED.name, - title_cn = EXCLUDED.title_cn, - title_en = EXCLUDED.title_en, - i18n_key = EXCLUDED.i18n_key, - icon = EXCLUDED.icon, - path = EXCLUDED.path, - sort_no = EXCLUDED.sort_no, - keep_alive = EXCLUDED.keep_alive, - menu_type = EXCLUDED.menu_type, - last_modified_time = EXCLUDED.last_modified_time; - --- 支付配置:支付产品管理(卡片式) -INSERT INTO "public"."iam_perm_menu" VALUES (40105, 405, 'payment:config:productConfig', 'admin', 'ProductConfig', '支付产品管理', 'Payment Product Management', 'menu.payment.config.productConfig', 'lucide:layout-grid', 'f', 'f', '/payment/config/product/ProductConfig', '/payment/config/product', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00') -ON CONFLICT (id) DO UPDATE SET - pid = EXCLUDED.pid, - menu_code = EXCLUDED.menu_code, - name = EXCLUDED.name, - title_cn = EXCLUDED.title_cn, - title_en = EXCLUDED.title_en, - i18n_key = EXCLUDED.i18n_key, - icon = EXCLUDED.icon, - component = EXCLUDED.component, - path = EXCLUDED.path, - sort_no = EXCLUDED.sort_no, - keep_alive = EXCLUDED.keep_alive, - menu_type = EXCLUDED.menu_type, - last_modified_time = EXCLUDED.last_modified_time; +INSERT INTO "public"."iam_perm_menu" VALUES (405, 4, 'payment:config', 'admin', 'PaymentConfig', '支付配置', 'Payment Config', 'menu.payment.config', 'lucide:settings-2', 'f', 'f', NULL, '/payment/config', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40105, 405, 'payment:config:productConfig', 'admin', 'ProductConfig', '支付产品管理', 'Payment Product Management', 'menu.payment.config.productConfig', 'lucide:layout-grid', 'f', 'f', '/payment/config/product/ProductConfig', '/payment/config/product', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 09:14:10.179605+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40501, 405, 'payment:config:productDetail', 'admin', 'ProductDetailDispatch', '支付产品详情', 'Payment Product Detail', 'menu.payment.config.productDetail', NULL, 't', 'f', '/payment/config/product/detail/ProductDetailDispatch', '/payment/product-detail', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40502, 405, 'payment:alipay:isvApp', 'admin', 'AlipayIsvAppManage', '支付宝服务商应用', 'Alipay ISV Application', 'menu.payment.alipay.isvApp', 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-06-14 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40503, 405, 'payment:wechat:isvApp', 'admin', 'WechatIsvAppManage', '微信服务商应用', 'WeChat ISV Application', 'menu.payment.wechat.isvApp', NULL, 't', 'f', '/payment/channel/wechat/manage/app/WechatIsvAppManage', '/payment/config/product/wechat-app-manage', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (4040115, 40401, 'payment:merchant:channelMerchant:douyinApp', 'admin', 'DouyinMchAppManage', '抖音通道商户应用', 'Douyin Channel Merchant App', 'menu.payment.merchant.channelMerchant.douyinApp', NULL, 't', 'f', '/payment/channel/douyin/manage/app/DouyinMchAppManage', '/payment/merchant/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-06-15 16:00:00+00'); -- ---------------------------- -- Primary Key structure for table iam_perm_menu -- ---------------------------- ALTER TABLE "public"."iam_perm_menu" ADD CONSTRAINT "iam_perm_menu_pkey" PRIMARY KEY ("id"); - --- ---------------------------- --- 支付宝服务商相关菜单 --- ---------------------------- --- 支付产品详情分发页(隐藏,供支付产品管理卡片跳转进入对应通道总览页) -INSERT INTO "public"."iam_perm_menu" VALUES (40501, 405, 'payment:config:productDetail', 'admin', 'ProductDetailDispatch', '支付产品详情', 'Payment Product Detail', 'menu.payment.config.productDetail', NULL, 't', 'f', '/payment/config/product/detail/ProductDetailDispatch', '/payment/product-detail', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00') -ON CONFLICT (id) DO UPDATE SET - pid = EXCLUDED.pid, - menu_code = EXCLUDED.menu_code, - name = EXCLUDED.name, - title_cn = EXCLUDED.title_cn, - title_en = EXCLUDED.title_en, - i18n_key = EXCLUDED.i18n_key, - component = EXCLUDED.component, - path = EXCLUDED.path, - sort_no = EXCLUDED.sort_no, - menu_type = EXCLUDED.menu_type, - last_modified_time = EXCLUDED.last_modified_time; - --- 支付宝服务商应用管理(隐藏,从支付宝服务商总览页卡片进入) -INSERT INTO "public"."iam_perm_menu" VALUES (40502, 405, 'payment:alipay:isvApp', 'admin', 'AlipayIsvAppManage', '支付宝服务商应用', 'Alipay ISV Application', 'menu.payment.alipay.isvApp', 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-06-14 00:00:00+00') -ON CONFLICT (id) DO UPDATE SET - pid = EXCLUDED.pid, - menu_code = EXCLUDED.menu_code, - name = EXCLUDED.name, - title_cn = EXCLUDED.title_cn, - title_en = EXCLUDED.title_en, - i18n_key = EXCLUDED.i18n_key, - component = EXCLUDED.component, - path = EXCLUDED.path, - sort_no = EXCLUDED.sort_no, - menu_type = EXCLUDED.menu_type, - last_modified_time = EXCLUDED.last_modified_time; - --- ---------------------------- --- 微信服务商相关菜单 --- ---------------------------- --- 微信服务商应用管理(隐藏,从微信服务商总览页卡片进入) -INSERT INTO "public"."iam_perm_menu" VALUES (40503, 405, 'payment:wechat:isvApp', 'admin', 'WechatIsvAppManage', '微信服务商应用', 'WeChat ISV Application', 'menu.payment.wechat.isvApp', NULL, 't', 'f', '/payment/channel/wechat/manage/app/WechatIsvAppManage', '/payment/config/product/wechat-app-manage', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00') -ON CONFLICT (id) DO UPDATE SET - pid = EXCLUDED.pid, - menu_code = EXCLUDED.menu_code, - name = EXCLUDED.name, - title_cn = EXCLUDED.title_cn, - title_en = EXCLUDED.title_en, - i18n_key = EXCLUDED.i18n_key, - component = EXCLUDED.component, - path = EXCLUDED.path, - sort_no = EXCLUDED.sort_no, - menu_type = EXCLUDED.menu_type, - last_modified_time = EXCLUDED.last_modified_time; - diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index 949017b49..b77a3c86b 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -1,323 +1,3 @@ --- ===================================================================== --- 微信通道相关表 (DROP + CREATE 重建) --- 对应实体: --- org.dromara.daxpay.channel.wechat.entity.isv.WechatIsvApp --- org.dromara.daxpay.channel.wechat.entity.isv.WechatIsvAppAuthConfig --- org.dromara.daxpay.channel.wechat.entity.isv.WechatIsvKeyConfig --- org.dromara.daxpay.channel.wechat.entity.isv.WechatIsvChannelMerchant --- org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp --- org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectKeyConfig --- org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectAppAuthConfig --- org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectChannelMerchant --- 执行方式: 手动执行(业务表 DDL 不入库, 由开发者按需在数据库执行) --- 注意: 加密字段(DataEncryptTypeHandler)在数据库层均为普通 text, 加解密在应用层完成 --- ===================================================================== - --- ========================= --- 1. 微信服务商应用 wechat_isv_app --- ========================= -DROP TABLE IF EXISTS "public"."wechat_isv_app"; - -CREATE TABLE "public"."wechat_isv_app" ( - "id" int8 NOT NULL, - "app_name" varchar(64), - "app_type" varchar(32), - "wx_app_id" varchar(64), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_isv_app" IS '微信服务商应用'; -COMMENT ON COLUMN "public"."wechat_isv_app"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_isv_app"."app_name" IS '应用名称'; -COMMENT ON COLUMN "public"."wechat_isv_app"."app_type" IS '应用类型'; -COMMENT ON COLUMN "public"."wechat_isv_app"."wx_app_id" IS '微信应用AppId'; -COMMENT ON COLUMN "public"."wechat_isv_app"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_isv_app"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_isv_app"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_isv_app"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_isv_app"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_isv_app"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_isv_app" - ADD CONSTRAINT "pk_wechat_isv_app" PRIMARY KEY ("id"); - - --- ========================= --- 2. 微信服务商应用授权配置 wechat_isv_app_auth_config --- ========================= -DROP TABLE IF EXISTS "public"."wechat_isv_app_auth_config"; - -CREATE TABLE "public"."wechat_isv_app_auth_config" ( - "id" int8 NOT NULL, - "mch_no" varchar(32), - "channel_mch_no" varchar(64), - "wechat_isv_app_id" int8, - "app_secret" text, - "auth_callback_url" varchar(255), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_isv_app_auth_config" IS '微信服务商应用授权配置'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."wechat_isv_app_id" IS '关联服务商应用ID(指向 wechat_isv_app.id)'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."app_secret" IS '应用密钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."auth_callback_url" IS '授权回调地址(仅公众号)'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_isv_app_auth_config"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_isv_app_auth_config" - ADD CONSTRAINT "pk_wechat_isv_app_auth_config" PRIMARY KEY ("id"); - - --- ========================= --- 3. 微信服务商密钥配置 wechat_isv_key_config --- ========================= -DROP TABLE IF EXISTS "public"."wechat_isv_key_config"; - -CREATE TABLE "public"."wechat_isv_key_config" ( - "id" int8 NOT NULL, - "product" varchar(32), - "wx_mch_id" varchar(32), - "api_key_v3" text, - "public_key" text, - "public_key_id" varchar(128), - "private_key" text, - "private_cert" text, - "cert_serial_no" varchar(128), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_isv_key_config" IS '微信服务商密钥配置'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."product" IS '产品编码'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."wx_mch_id" IS '微信服务商商户号'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."api_key_v3" IS 'APIv3密钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."public_key" IS '支付公钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."public_key_id" IS '支付公钥ID'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."private_key" IS '商户私钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."private_cert" IS '商户证书(加密存储)'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."cert_serial_no" IS '证书序列号'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_isv_key_config"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_isv_key_config" - ADD CONSTRAINT "pk_wechat_isv_key_config" PRIMARY KEY ("id"); - - --- ========================= --- 4. 微信服务商通道商户绑定 mch_wechat_isv_channel_merchant --- 微信特约商户关联到服务商本身, 不挂靠具体服务商应用 --- ========================= -DROP TABLE IF EXISTS "public"."mch_wechat_isv_channel_merchant"; - -CREATE TABLE "public"."mch_wechat_isv_channel_merchant" ( - "id" int8 NOT NULL, - "mch_no" varchar(32) NOT NULL, - "channel_mch_no" varchar(64) NOT NULL, - "product" varchar(32) NOT NULL, - "sub_mch_id" varchar(32), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."mch_wechat_isv_channel_merchant" IS '微信服务商通道商户绑定'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."id" IS '主键'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."channel_mch_no" IS '通道商户号(WISV+雪花)'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."product" IS '所属支付产品'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."sub_mch_id" IS '微信特约商户号/二级商户号(V3服务商支付 sub_mchid)'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."mch_wechat_isv_channel_merchant"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."mch_wechat_isv_channel_merchant" - ADD CONSTRAINT "pk_mch_wechat_isv_channel_merchant" PRIMARY KEY ("id"); - - --- ========================= --- 5. 微信直连通道商户绑定 mch_wechat_direct_channel_merchant --- 一个微信商户号(wx_mch_id)对应一个channelMchNo, 商户的多个应用共享此绑定 --- ========================= -DROP TABLE IF EXISTS "public"."mch_wechat_direct_channel_merchant"; - -CREATE TABLE "public"."mch_wechat_direct_channel_merchant" ( - "id" int8 NOT NULL, - "mch_no" varchar(32) NOT NULL, - "channel_mch_no" varchar(64) NOT NULL, - "product" varchar(32) NOT NULL, - "wx_mch_id" varchar(32), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."mch_wechat_direct_channel_merchant" IS '微信直连通道商户绑定'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."id" IS '主键'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."channel_mch_no" IS '通道商户号(系统生成雪花号)'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."product" IS '所属支付产品'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."wx_mch_id" IS '微信直连商户号'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."mch_wechat_direct_channel_merchant"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."mch_wechat_direct_channel_merchant" - ADD CONSTRAINT "pk_mch_wechat_direct_channel_merchant" PRIMARY KEY ("id"); - - --- ========================= --- 6. 微信直连商户应用 wechat_direct_app --- 每个应用关联一个通道商户号, 拥有独立的微信应用ID --- ========================= -DROP TABLE IF EXISTS "public"."wechat_direct_app"; - -CREATE TABLE "public"."wechat_direct_app" ( - "id" int8 NOT NULL, - "mch_no" varchar(32) NOT NULL, - "channel_mch_no" varchar(64) NOT NULL, - "app_name" varchar(64), - "wx_app_id" varchar(64), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_direct_app" IS '微信直连商户应用'; -COMMENT ON COLUMN "public"."wechat_direct_app"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_direct_app"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."wechat_direct_app"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."wechat_direct_app"."app_name" IS '应用名称'; -COMMENT ON COLUMN "public"."wechat_direct_app"."wx_app_id" IS '微信应用AppId'; -COMMENT ON COLUMN "public"."wechat_direct_app"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_direct_app"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_direct_app"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_direct_app"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_direct_app"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_direct_app"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_direct_app" - ADD CONSTRAINT "pk_wechat_direct_app" PRIMARY KEY ("id"); - - --- ========================= --- 7. 微信直连密钥配置 wechat_direct_key_config --- 直连商户维度的APIv3密钥和证书, 按通道商户号关联, 与具体应用无关, 敏感字段加密存储 --- ========================= -DROP TABLE IF EXISTS "public"."wechat_direct_key_config"; - -CREATE TABLE "public"."wechat_direct_key_config" ( - "id" int8 NOT NULL, - "mch_no" varchar(32) NOT NULL, - "channel_mch_no" varchar(64) NOT NULL, - "api_key_v3" text, - "public_key" text, - "public_key_id" varchar(128), - "private_key" text, - "private_cert" text, - "cert_serial_no" varchar(128), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_direct_key_config" IS '微信直连密钥配置'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."api_key_v3" IS 'APIv3密钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."public_key" IS '支付公钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."public_key_id" IS '支付公钥ID'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."private_key" IS '商户私钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."private_cert" IS '商户证书(加密存储)'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."cert_serial_no" IS '证书序列号'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_direct_key_config"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_direct_key_config" - ADD CONSTRAINT "pk_wechat_direct_key_config" PRIMARY KEY ("id"); - - --- ========================= --- 8. 微信直连商户应用授权配置 wechat_direct_app_auth_config --- ========================= -DROP TABLE IF EXISTS "public"."wechat_direct_app_auth_config"; - -CREATE TABLE "public"."wechat_direct_app_auth_config" ( - "id" int8 NOT NULL, - "mch_no" varchar(32) NOT NULL, - "channel_mch_no" varchar(64) NOT NULL, - "wechat_direct_app_id" int8, - "app_secret" text, - "auth_callback_url" varchar(255), - "creator" int8, - "create_time" timestamptz(6), - "last_modifier" int8, - "last_modified_time" timestamptz(6), - "version" int4 DEFAULT 0 NOT NULL, - "deleted" bool DEFAULT false NOT NULL -); - -COMMENT ON TABLE "public"."wechat_direct_app_auth_config" IS '微信直连商户应用授权配置'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."wechat_direct_app_id" IS '关联应用ID(指向 wechat_direct_app.id)'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."app_secret" IS '应用密钥(加密存储)'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."auth_callback_url" IS '授权回调地址'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."wechat_direct_app_auth_config"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."wechat_direct_app_auth_config" - ADD CONSTRAINT "pk_wechat_direct_app_auth_config" PRIMARY KEY ("id"); +-- 支付宝直连商户应用增加应用类型字段 +ALTER TABLE "public"."alipay_direct_app" + ADD COLUMN "app_type" varchar(32); diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/org/dromara/daxpay/channel/alipay/entity/direct/AlipayDirectApp.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/org/dromara/daxpay/channel/alipay/entity/direct/AlipayDirectApp.java index 3aef7e1b1..1aeb01467 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/org/dromara/daxpay/channel/alipay/entity/direct/AlipayDirectApp.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/org/dromara/daxpay/channel/alipay/entity/direct/AlipayDirectApp.java @@ -31,6 +31,9 @@ public class AlipayDirectApp extends MchBaseEntity implements ToResult> listByChannelMchNo( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) { + return Res.ok(douyinDirectAppService.listByMchNoAndChannelMchNo(mchNo, channelMchNo)); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "查询应用详情") + @GetMapping("/find-by-id") + public Result findById( + @NotNull(message = "{validation.field.id.notNull}") Long id) { + return Res.ok(douyinDirectAppService.findById(id)); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "同一通道商户下抖音应用ID是否已存在") + @GetMapping("/exists-douyin-app-id-by-channel") + public Result existsDouyinAppIdByChannel( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo, + @NotBlank(message = "{validation.field.douyinAppId.notBlank}") String douyinAppId) { + return Res.ok(douyinDirectAppService.existsDouyinAppIdByChannel(mchNo, channelMchNo, douyinAppId, null)); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "同一通道商户下抖音应用ID是否已存在(排除自身)") + @GetMapping("/exists-douyin-app-id-by-channel-not-id") + public Result existsDouyinAppIdByChannelNotId( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo, + @NotBlank(message = "{validation.field.douyinAppId.notBlank}") String douyinAppId, + @NotNull(message = "{validation.field.id.notNull}") Long id) { + return Res.ok(douyinDirectAppService.existsDouyinAppIdByChannel(mchNo, channelMchNo, douyinAppId, id)); + } + + @PermCode(code = "add", nameCn = "通道商户新增", nameEn = "Channel Merchant Add") + @Operation(summary = "新增直连商户应用") + @PostMapping("/add") + public Result add(@RequestBody @Validated(ValidationGroup.add.class) DouyinDirectAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.add.class); + douyinDirectAppService.add(param); + return Res.ok(); + } + + @PermCode(code = "edit", nameCn = "通道商户编辑", nameEn = "Channel Merchant Edit") + @Operation(summary = "修改直连商户应用") + @PostMapping("/update") + public Result update(@RequestBody @Validated(ValidationGroup.edit.class) DouyinDirectAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.edit.class); + douyinDirectAppService.update(param); + return Res.ok(); + } + + @PermCode(code = "edit", nameCn = "通道商户编辑", nameEn = "Channel Merchant Edit") + @Operation(summary = "删除直连商户应用") + @PostMapping("/delete") + public Result delete(@NotNull(message = "{validation.field.id.notNull}") Long id) { + douyinDirectAppService.delete(id); + return Res.ok(); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "查询应用授权认证配置") + @GetMapping("/find-auth-config-by-app-id") + public Result findAuthConfigByAppId( + @NotNull(message = "{validation.field.douyinDirectAppId.notNull}") Long douyinDirectAppId) { + var config = douyinDirectAppAuthConfigService.findByDouyinDirectAppId(douyinDirectAppId); + return Res.ok(config.toResult()); + } + + @PermCode(code = "edit", nameCn = "通道商户编辑", nameEn = "Channel Merchant Edit") + @Operation(summary = "保存应用授权认证配置") + @PostMapping("/save-auth-config") + public Result saveAuthConfig(@RequestBody @Validated DouyinDirectAppAuthConfigParam param) { + douyinDirectAppAuthConfigService.save(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/controller/direct/DouyinDirectChannelMerchantController.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/controller/direct/DouyinDirectChannelMerchantController.java new file mode 100644 index 000000000..9eefabaa4 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/controller/direct/DouyinDirectChannelMerchantController.java @@ -0,0 +1,71 @@ +package org.dromara.daxpay.channel.douyin.controller.direct; + +import org.dromara.daxpay.platform.core.annotation.PermCode; +import org.dromara.daxpay.platform.core.rest.Res; +import org.dromara.daxpay.platform.core.rest.result.Result; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectChannelMerchantCreateParam; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectKeyConfigParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectChannelMerchantResult; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectKeyConfigResult; +import org.dromara.daxpay.channel.douyin.service.direct.DouyinDirectChannelMerchantService; +import org.dromara.daxpay.channel.douyin.service.direct.DouyinDirectKeyConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/// # 抖音直连通道商户管理 +/// +@PermCode(menuCode = "payment:merchant:channelMerchant") +@Validated +@Tag(name = "抖音直连通道商户管理") +@RestController +@RequestMapping("/admin/douyin/direct-channel-merchant") +@RequiredArgsConstructor +public class DouyinDirectChannelMerchantController { + + private final DouyinDirectChannelMerchantService douyinDirectChannelMerchantService; + private final DouyinDirectKeyConfigService douyinDirectKeyConfigService; + + @PermCode(code = "view", nameCn = "商户通道商户查看", nameEn = "Merchant Channel Merchant View") + @Operation(summary = "根据通道商户号查询抖音直连通道商户配置") + @GetMapping("/find-by-channel-mch-no") + public Result findByChannelMchNo( + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) { + return Res.ok(douyinDirectChannelMerchantService.findByChannelMchNo(channelMchNo)); + } + + @PermCode(code = "add", nameCn = "商户通道商户新增", nameEn = "Merchant Channel Merchant Add") + @Operation(summary = "创建抖音直连通道商户") + @PostMapping("/create") + public Result create(@RequestBody @Validated DouyinDirectChannelMerchantCreateParam param) { + douyinDirectChannelMerchantService.create(param); + return Res.ok(); + } + + @PermCode(code = "view", nameCn = "商户通道商户查看", nameEn = "Merchant Channel Merchant View") + @Operation(summary = "根据通道商户号查询密钥配置") + @GetMapping("/find-key-config") + public Result findKeyConfig( + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) { + var config = douyinDirectKeyConfigService.findByChannelMchNo(channelMchNo); + var result = config.toResult(); + result.setPrivateKeyConfigured(config.getMerchantPrivateKey() != null); + result.setEncryptKeyConfigured(config.getEncryptKey() != null); + return Res.ok(result); + } + + @PermCode(code = "edit", nameCn = "商户通道商户编辑", nameEn = "Merchant Channel Merchant Edit") + @Operation(summary = "保存密钥配置") + @PostMapping("/save-key-config") + public Result saveKeyConfig(@RequestBody @Validated DouyinDirectKeyConfigParam param) { + douyinDirectKeyConfigService.save(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppAuthConfigConvert.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppAuthConfigConvert.java new file mode 100644 index 000000000..33f1c4bc5 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppAuthConfigConvert.java @@ -0,0 +1,20 @@ +package org.dromara.daxpay.channel.douyin.convert.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectAppAuthConfig; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectAppAuthConfigParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectAppAuthConfigResult; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 抖音直连商户应用授权认证配置转换 +/// +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface DouyinDirectAppAuthConfigConvert { + DouyinDirectAppAuthConfigConvert CONVERT = Mappers.getMapper(DouyinDirectAppAuthConfigConvert.class); + + DouyinDirectAppAuthConfigResult toResult(DouyinDirectAppAuthConfig entity); + + void copy(DouyinDirectAppAuthConfigParam param, @MappingTarget DouyinDirectAppAuthConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppConvert.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppConvert.java new file mode 100644 index 000000000..9d8bdecbf --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectAppConvert.java @@ -0,0 +1,22 @@ +package org.dromara.daxpay.channel.douyin.convert.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectApp; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectAppParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectAppResult; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 抖音直连商户应用转换 +/// +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface DouyinDirectAppConvert { + DouyinDirectAppConvert CONVERT = Mappers.getMapper(DouyinDirectAppConvert.class); + + DouyinDirectAppResult toResult(DouyinDirectApp entity); + + DouyinDirectApp toEntity(DouyinDirectAppParam param); + + void copy(DouyinDirectAppParam param, @MappingTarget DouyinDirectApp entity); +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectChannelMerchantConvert.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectChannelMerchantConvert.java new file mode 100644 index 000000000..16428c164 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectChannelMerchantConvert.java @@ -0,0 +1,15 @@ +package org.dromara.daxpay.channel.douyin.convert.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectChannelMerchant; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectChannelMerchantResult; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/// # 抖音直连通道商户转换 +/// +@Mapper +public interface DouyinDirectChannelMerchantConvert { + DouyinDirectChannelMerchantConvert CONVERT = Mappers.getMapper(DouyinDirectChannelMerchantConvert.class); + + DouyinDirectChannelMerchantResult toResult(DouyinDirectChannelMerchant entity); +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectKeyConfigConvert.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectKeyConfigConvert.java new file mode 100644 index 000000000..5cdc94a5c --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/convert/direct/DouyinDirectKeyConfigConvert.java @@ -0,0 +1,20 @@ +package org.dromara.daxpay.channel.douyin.convert.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectKeyConfig; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectKeyConfigParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectKeyConfigResult; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 抖音直连密钥配置转换 +/// +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface DouyinDirectKeyConfigConvert { + DouyinDirectKeyConfigConvert CONVERT = Mappers.getMapper(DouyinDirectKeyConfigConvert.class); + + DouyinDirectKeyConfigResult toResult(DouyinDirectKeyConfig entity); + + void copy(DouyinDirectKeyConfigParam param, @MappingTarget DouyinDirectKeyConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigManager.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigManager.java new file mode 100644 index 000000000..23581dad8 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigManager.java @@ -0,0 +1,29 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectAppAuthConfig; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 抖音直连商户应用授权认证配置 +/// +/// 直连商户应用授权认证配置数据访问管理器,提供按应用ID查询和删除授权认证配置的方法。 +/// +@Repository +public class DouyinDirectAppAuthConfigManager extends BaseManager { + + /// 根据应用ID查询授权认证配置 + public Optional findByDouyinDirectAppId(Long douyinDirectAppId) { + return lambdaQuery() + .eq(DouyinDirectAppAuthConfig::getDouyinDirectAppId, douyinDirectAppId) + .oneOpt(); + } + + /// 根据应用ID删除授权认证配置 + public void deleteByDouyinDirectAppId(Long douyinDirectAppId) { + lambdaUpdate() + .eq(DouyinDirectAppAuthConfig::getDouyinDirectAppId, douyinDirectAppId) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigMapper.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigMapper.java new file mode 100644 index 000000000..d4358c4fd --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppAuthConfigMapper.java @@ -0,0 +1,11 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectAppAuthConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 抖音直连商户应用授权认证配置 +/// +@Mapper +public interface DouyinDirectAppAuthConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppManager.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppManager.java new file mode 100644 index 000000000..ecbd0f379 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppManager.java @@ -0,0 +1,35 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectApp; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/// # 抖音直连商户应用 +/// +/// 直连商户应用数据访问管理器,提供按商户号和通道商户号查询列表、同一通道下应用ID唯一性校验等方法。 +/// +@Repository +public class DouyinDirectAppManager extends BaseManager { + + /// 根据商户号和通道商户号查询应用列表(按创建时间升序) + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + return lambdaQuery() + .eq(DouyinDirectApp::getMchNo, mchNo) + .eq(DouyinDirectApp::getChannelMchNo, channelMchNo) + .orderByAsc(DouyinDirectApp::getCreateTime) + .orderByAsc(DouyinDirectApp::getId) + .list(); + } + + /// 校验同一通道商户下douyinAppId是否已存在(排除自身) + public boolean existsByChannelMchNoAndDouyinAppId(String mchNo, String channelMchNo, String douyinAppId, Long excludeId) { + return lambdaQuery() + .eq(DouyinDirectApp::getMchNo, mchNo) + .eq(DouyinDirectApp::getChannelMchNo, channelMchNo) + .eq(DouyinDirectApp::getDouyinAppId, douyinAppId) + .ne(excludeId != null, DouyinDirectApp::getId, excludeId) + .exists(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppMapper.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppMapper.java new file mode 100644 index 000000000..bef58d623 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectAppMapper.java @@ -0,0 +1,11 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectApp; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 抖音直连商户应用 +/// +@Mapper +public interface DouyinDirectAppMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantManager.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantManager.java new file mode 100644 index 000000000..8a3e94ef4 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantManager.java @@ -0,0 +1,28 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectChannelMerchant; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 抖音直连通道商户绑定 +/// +@Repository +public class DouyinDirectChannelMerchantManager extends BaseManager { + + /// 校验同一商户下抖音直连商户号不重复 + public boolean existsByMchNoAndDyMchId(String mchNo, String dyMchId) { + return lambdaQuery() + .eq(DouyinDirectChannelMerchant::getMchNo, mchNo) + .eq(DouyinDirectChannelMerchant::getDyMchId, dyMchId) + .exists(); + } + + /// 根据通道商户号查询 + public Optional findByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(DouyinDirectChannelMerchant::getChannelMchNo, channelMchNo) + .oneOpt(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantMapper.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantMapper.java new file mode 100644 index 000000000..18249dc28 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectChannelMerchantMapper.java @@ -0,0 +1,13 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectChannelMerchant; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 抖音直连通道商户绑定 +/// +/// 抖音直连通道商户 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface DouyinDirectChannelMerchantMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigManager.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigManager.java new file mode 100644 index 000000000..2e2a1561d --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigManager.java @@ -0,0 +1,29 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectKeyConfig; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 抖音直连密钥配置 +/// +/// 直连密钥配置数据访问管理器,提供按通道商户号查询和删除密钥配置的方法。 +/// +@Repository +public class DouyinDirectKeyConfigManager extends BaseManager { + + /// 根据通道商户号查询密钥配置 + public Optional findByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(DouyinDirectKeyConfig::getChannelMchNo, channelMchNo) + .oneOpt(); + } + + /// 根据通道商户号删除密钥配置 + public void deleteByChannelMchNo(String channelMchNo) { + lambdaUpdate() + .eq(DouyinDirectKeyConfig::getChannelMchNo, channelMchNo) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigMapper.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigMapper.java new file mode 100644 index 000000000..ce94ce73f --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/dao/direct/DouyinDirectKeyConfigMapper.java @@ -0,0 +1,11 @@ +package org.dromara.daxpay.channel.douyin.dao.direct; + +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectKeyConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 抖音直连密钥配置 +/// +@Mapper +public interface DouyinDirectKeyConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectApp.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectApp.java new file mode 100644 index 000000000..6be867871 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectApp.java @@ -0,0 +1,42 @@ +package org.dromara.daxpay.channel.douyin.entity.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectAppConvert; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectAppResult; +import org.dromara.daxpay.payment.common.entity.merchant.MchBaseEntity; +import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用 +/// +/// 表示抖音直连模式下商户注册的应用实体,每个应用关联一个通道商户号,拥有独立的抖音应用ID。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "douyin_direct_app", autoResultMap = true) +public class DouyinDirectApp extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 应用名称 + private String appName; + + /// 抖音应用AppId(APPID) + private String douyinAppId; + + /** 应用类型: mini_program-小程序 mobile_app-移动应用 web_app-网站应用 */ + private String appType; + + /// 转换 + @Override + public DouyinDirectAppResult toResult() { + return DouyinDirectAppConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectAppAuthConfig.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectAppAuthConfig.java new file mode 100644 index 000000000..44e9a8d7f --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectAppAuthConfig.java @@ -0,0 +1,40 @@ +package org.dromara.daxpay.channel.douyin.entity.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectAppAuthConfigConvert; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectAppAuthConfigResult; +import org.dromara.daxpay.payment.common.entity.merchant.MchBaseEntity; +import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用授权认证配置 +/// +/// 配置直连商户应用的授权回调地址,用于抖音用户授权流程中的回调跳转。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "douyin_direct_app_auth_config", autoResultMap = true) +public class DouyinDirectAppAuthConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 关联应用ID + @TableField(updateStrategy = FieldStrategy.NEVER) + private Long douyinDirectAppId; + + /// 授权回调地址 + private String authCallbackUrl; + + /// 转换 + @Override + public DouyinDirectAppAuthConfigResult toResult() { + return DouyinDirectAppAuthConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectChannelMerchant.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectChannelMerchant.java new file mode 100644 index 000000000..2136f5fb5 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectChannelMerchant.java @@ -0,0 +1,37 @@ +package org.dromara.daxpay.channel.douyin.entity.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectChannelMerchantConvert; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectChannelMerchantResult; +import org.dromara.daxpay.payment.common.entity.merchant.MchBaseEntity; +import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连通道商户绑定 +/// +/// 一个抖音商户号(dyMchId)对应一个 channelMchNo, 商户的多个应用共享此绑定。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "douyin_direct_channel_merchant", autoResultMap = true) +public class DouyinDirectChannelMerchant extends MchBaseEntity implements ToResult { + + /// 通道商户号(系统生成雪花号) + private String channelMchNo; + + /// 所属支付产品 + /// @see org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum + private String product; + + /// 抖音商户号(MCHID) + private String dyMchId; + + /// 转换 + @Override + public DouyinDirectChannelMerchantResult toResult() { + return DouyinDirectChannelMerchantConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectKeyConfig.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectKeyConfig.java new file mode 100644 index 000000000..678d766bd --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/entity/direct/DouyinDirectKeyConfig.java @@ -0,0 +1,45 @@ +package org.dromara.daxpay.channel.douyin.entity.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectKeyConfigConvert; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectKeyConfigResult; +import org.dromara.daxpay.payment.common.entity.merchant.MchBaseEntity; +import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult; +import org.dromara.daxpay.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; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连密钥配置 +/// +/// 直连商户维度的密钥配置,一个抖音商户号共享一套密钥,与具体应用无关,敏感字段加密存储。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "douyin_direct_key_config", autoResultMap = true) +public class DouyinDirectKeyConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 商户私钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String merchantPrivateKey; + + /// 商家公钥证书序列号 + private String merchantSerialNumber; + + /// 接口加密密钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String encryptKey; + + /// 转换 + @Override + public DouyinDirectKeyConfigResult toResult() { + return DouyinDirectKeyConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppAuthConfigParam.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppAuthConfigParam.java new file mode 100644 index 000000000..a0ddb530a --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppAuthConfigParam.java @@ -0,0 +1,34 @@ +package org.dromara.daxpay.channel.douyin.param.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用授权认证配置保存参数 +/// +/// 保存/更新抖音直连商户应用授权认证配置时接收的请求参数,含商户号、通道商户号和授权回调地址。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连商户应用授权认证配置保存参数") +public class DouyinDirectAppAuthConfigParam { + + @NotNull(message = "{validation.field.douyinDirectAppId.notNull}") + @Schema(description = "关联应用ID") + private Long douyinDirectAppId; + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @Size(max = 512, message = "{validation.field.authCallbackUrl.size}") + @Schema(description = "授权回调地址") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppParam.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppParam.java new file mode 100644 index 000000000..f433b90f9 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectAppParam.java @@ -0,0 +1,42 @@ +package org.dromara.daxpay.channel.douyin.param.direct; + +import org.dromara.daxpay.platform.core.validation.ValidationGroup; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用保存参数 +/// +/// 保存/更新抖音直连商户应用时接收的请求参数,含商户号、通道商户号、应用名称和抖音应用AppId。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连商户应用保存参数") +public class DouyinDirectAppParam { + + @NotNull(message = "{validation.field.id.notNull}", groups = ValidationGroup.edit.class) + @Schema(description = "主键,新增时不传") + private Long id; + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @NotBlank(message = "{validation.field.appName.notBlank}") + @Schema(description = "应用名称") + private String appName; + + @NotBlank(message = "{validation.field.douyinAppId.notBlank}") + @Schema(description = "抖音应用AppId(APPID)") + private String douyinAppId; + + @NotBlank(message = "{validation.field.appType.notBlank}", groups = ValidationGroup.add.class) + @Schema(description = "应用类型: mini_program-小程序 mobile_app-移动应用 web_app-网站应用") + private String appType; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectChannelMerchantCreateParam.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectChannelMerchantCreateParam.java new file mode 100644 index 000000000..c1ddf732e --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectChannelMerchantCreateParam.java @@ -0,0 +1,34 @@ +package org.dromara.daxpay.channel.douyin.param.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 抖音直连通道商户创建参数 +/// +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连通道商户创建参数") +public class DouyinDirectChannelMerchantCreateParam { + + /// 商户号 + @Schema(description = "商户号") + @NotBlank(message = "{validation.field.mchNo.notBlank}") + private String mchNo; + + /// 通道商户名称 + @Schema(description = "通道商户名称") + @NotBlank(message = "{validation.field.channelMerchantName.notBlank}") + private String channelMerchantName; + + /// 所属支付产品 + @Schema(description = "所属支付产品") + @NotBlank(message = "{validation.field.product.notBlank}") + private String product; + + /// 抖音商户号(MCHID) + @Schema(description = "抖音商户号(MCHID)") + @NotBlank(message = "{validation.field.dyMchId.notBlank}") + private String dyMchId; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectKeyConfigParam.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectKeyConfigParam.java new file mode 100644 index 000000000..5ecdcb6d6 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/param/direct/DouyinDirectKeyConfigParam.java @@ -0,0 +1,33 @@ +package org.dromara.daxpay.channel.douyin.param.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 抖音直连密钥配置保存参数 +/// +/// 保存/更新抖音直连密钥时接收的请求参数,含通道商户号和密钥信息。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连密钥配置保存参数") +public class DouyinDirectKeyConfigParam { + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "商户私钥") + private String merchantPrivateKey; + + @Schema(description = "商家公钥证书序列号") + private String merchantSerialNumber; + + @Schema(description = "接口加密密钥") + private String encryptKey; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppAuthConfigResult.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppAuthConfigResult.java new file mode 100644 index 000000000..b5ddb502c --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppAuthConfigResult.java @@ -0,0 +1,25 @@ +package org.dromara.daxpay.channel.douyin.result.direct; + +import org.dromara.daxpay.payment.common.result.MchBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用授权认证配置结果 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连商户应用授权认证配置结果") +public class DouyinDirectAppAuthConfigResult extends MchBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "关联应用ID") + private Long douyinDirectAppId; + + @Schema(description = "授权回调地址") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppResult.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppResult.java new file mode 100644 index 000000000..116a8b67f --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectAppResult.java @@ -0,0 +1,28 @@ +package org.dromara.daxpay.channel.douyin.result.direct; + +import org.dromara.daxpay.payment.common.result.MchBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连商户应用结果 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连商户应用结果") +public class DouyinDirectAppResult extends MchBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "抖音应用AppId(APPID)") + private String douyinAppId; + + @Schema(description = "应用类型") + private String appType; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectChannelMerchantResult.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectChannelMerchantResult.java new file mode 100644 index 000000000..9fdf72c35 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectChannelMerchantResult.java @@ -0,0 +1,25 @@ +package org.dromara.daxpay.channel.douyin.result.direct; + +import org.dromara.daxpay.payment.common.result.MchBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连通道商户结果 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连通道商户结果") +public class DouyinDirectChannelMerchantResult extends MchBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "所属支付产品") + private String product; + + @Schema(description = "抖音商户号(MCHID)") + private String dyMchId; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectKeyConfigResult.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectKeyConfigResult.java new file mode 100644 index 000000000..2ccbbfaa1 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/result/direct/DouyinDirectKeyConfigResult.java @@ -0,0 +1,34 @@ +package org.dromara.daxpay.channel.douyin.result.direct; + +import org.dromara.daxpay.payment.common.result.MchBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 抖音直连密钥配置结果 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "抖音直连密钥配置结果") +public class DouyinDirectKeyConfigResult extends MchBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "商户私钥(已脱敏)") + private String merchantPrivateKey; + + @Schema(description = "商家公钥证书序列号") + private String merchantSerialNumber; + + @Schema(description = "接口加密密钥(已脱敏)") + private String encryptKey; + + @Schema(description = "私钥是否已配置") + private boolean privateKeyConfigured; + + @Schema(description = "加密密钥是否已配置") + private boolean encryptKeyConfigured; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppAuthConfigService.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppAuthConfigService.java new file mode 100644 index 000000000..d5205ed68 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppAuthConfigService.java @@ -0,0 +1,66 @@ +package org.dromara.daxpay.channel.douyin.service.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectAppAuthConfigConvert; +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectAppManager; +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectAppAuthConfigManager; +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectAppAuthConfig; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectAppAuthConfigParam; +import org.dromara.daxpay.platform.core.code.CommonErrorCode; +import org.dromara.daxpay.platform.core.exception.BizInfoException; +import org.dromara.daxpay.platform.core.exception.DataNotExistException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/// # 抖音直连商户应用授权认证配置 +/// +/// 管理直连商户应用的授权认证配置,查询时不存在则创建默认记录,保存时校验应用归属关系。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinDirectAppAuthConfigService { + + private final DouyinDirectAppAuthConfigManager douyinDirectAppAuthConfigManager; + private final DouyinDirectAppManager douyinDirectAppManager; + + /// 根据应用ID查询授权认证配置, 不存在则创建默认记录 + @Transactional(rollbackFor = Exception.class) + public DouyinDirectAppAuthConfig findByDouyinDirectAppId(Long douyinDirectAppId) { + var existing = douyinDirectAppAuthConfigManager.findByDouyinDirectAppId(douyinDirectAppId); + if (existing.isPresent()) { + return existing.get(); + } + var app = douyinDirectAppManager.findById(douyinDirectAppId) + // 抖音: 直连商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound")); + var config = new DouyinDirectAppAuthConfig() + .setChannelMchNo(app.getChannelMchNo()) + .setDouyinDirectAppId(douyinDirectAppId); + config.setMchNo(app.getMchNo()); + douyinDirectAppAuthConfigManager.save(config); + return config; + } + + /// 保存应用授权认证配置(更新) + @Transactional(rollbackFor = Exception.class) + public void save(DouyinDirectAppAuthConfigParam param) { + var app = douyinDirectAppManager.findById(param.getDouyinDirectAppId()) + // 抖音: 直连商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound")); + if (!app.getMchNo().equals(param.getMchNo()) || !app.getChannelMchNo().equals(param.getChannelMchNo())) { + // 抖音: 直连商户应用不存在或商户号归属不匹配 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.douyin.mchAppNotFound"); + } + var config = this.findByDouyinDirectAppId(param.getDouyinDirectAppId()); + config.setAuthCallbackUrl(param.getAuthCallbackUrl()); + DouyinDirectAppAuthConfigConvert.CONVERT.copy(param, config); + douyinDirectAppAuthConfigManager.updateById(config); + } + + /// 删除应用授权认证配置 + public void deleteByDouyinDirectAppId(Long douyinDirectAppId) { + douyinDirectAppAuthConfigManager.deleteByDouyinDirectAppId(douyinDirectAppId); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppService.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppService.java new file mode 100644 index 000000000..9f3fe9b5f --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectAppService.java @@ -0,0 +1,96 @@ +package org.dromara.daxpay.channel.douyin.service.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectAppConvert; +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectAppManager; +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectApp; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectAppParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectAppResult; +import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil; +import org.dromara.daxpay.platform.core.code.CommonErrorCode; +import org.dromara.daxpay.platform.core.exception.BizInfoException; +import org.dromara.daxpay.platform.core.exception.DataNotExistException; +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; + +/// # 抖音直连商户应用管理 +/// +/// 提供直连商户应用的增删改查功能,包含同一通道商户下应用ID的唯一性校验和操作范围校验(防止跨商户操作)。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinDirectAppService { + + private final DouyinDirectAppManager douyinDirectAppManager; + private final DouyinDirectAppAuthConfigService douyinDirectAppAuthConfigService; + + /// 根据商户号和通道商户号查询应用列表 + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + var list = douyinDirectAppManager.listByMchNoAndChannelMchNo(mchNo, channelMchNo); + return MpUtil.toListResult(list); + } + + /// 根据ID查询应用详情 + public DouyinDirectAppResult findById(Long id) { + return douyinDirectAppManager.findById(id) + .map(DouyinDirectApp::toResult) + // 抖音: 直连商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound")); + } + + /// 判断同一通道商户下抖音应用ID是否已存在 + public boolean existsDouyinAppIdByChannel(String mchNo, String channelMchNo, String douyinAppId, Long excludeId) { + if (StrUtil.hasBlank(mchNo, channelMchNo, douyinAppId)) { + return false; + } + return douyinDirectAppManager.existsByChannelMchNoAndDouyinAppId(mchNo, channelMchNo, douyinAppId, excludeId); + } + + /// 新增抖音直连商户应用 + public void add(DouyinDirectAppParam param) { + this.assertDouyinAppIdUnique(param.getMchNo(), param.getChannelMchNo(), param.getDouyinAppId(), null); + var entity = DouyinDirectAppConvert.CONVERT.toEntity(param); + douyinDirectAppManager.save(entity); + } + + /// 更新抖音直连商户应用 + public void update(DouyinDirectAppParam param) { + var entity = douyinDirectAppManager.findById(param.getId()) + // 抖音: 直连商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound")); + this.assertScopeMatch(entity, param.getMchNo(), param.getChannelMchNo()); + this.assertDouyinAppIdUnique(entity.getMchNo(), entity.getChannelMchNo(), param.getDouyinAppId(), param.getId()); + DouyinDirectAppConvert.CONVERT.copy(param, entity); + douyinDirectAppManager.updateById(entity); + } + + /// 删除应用(级联删除授权认证配置) + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + douyinDirectAppManager.findById(id) + // 抖音: 直连商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound")); + douyinDirectAppAuthConfigService.deleteByDouyinDirectAppId(id); + douyinDirectAppManager.deleteById(id); + } + + /// 校验同一通道商户下应用ID唯一 + private void assertDouyinAppIdUnique(String mchNo, String channelMchNo, String douyinAppId, Long excludeId) { + if (this.existsDouyinAppIdByChannel(mchNo, channelMchNo, douyinAppId, excludeId)) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.douyin.appIdDuplicate"); + } + } + + /// 校验操作范围与记录归属一致 + private void assertScopeMatch(DouyinDirectApp entity, String mchNo, String channelMchNo) { + if (!entity.getMchNo().equals(mchNo) || !entity.getChannelMchNo().equals(channelMchNo)) { + // 抖音: 直连商户应用不存在或商户号归属不匹配 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.douyin.mchAppNotFound"); + } + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectChannelMerchantService.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectChannelMerchantService.java new file mode 100644 index 000000000..a9d2e7dc2 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectChannelMerchantService.java @@ -0,0 +1,69 @@ +package org.dromara.daxpay.channel.douyin.service.direct; + +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager; +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectChannelMerchant; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectChannelMerchantCreateParam; +import org.dromara.daxpay.channel.douyin.result.direct.DouyinDirectChannelMerchantResult; +import org.dromara.daxpay.payment.channel.dao.mch.ChannelMerchantManager; +import org.dromara.daxpay.payment.channel.entity.mch.ChannelMerchant; +import org.dromara.daxpay.platform.core.code.CommonErrorCode; +import org.dromara.daxpay.platform.core.enums.channel.ChannelMerchantSourceEnum; +import org.dromara.daxpay.platform.core.exception.BizInfoException; +import org.dromara.daxpay.platform.core.exception.DataNotExistException; +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/// # 抖音直连通道商户管理 +/// +/// 一个抖音商户号(dyMchId)对应一个 channelMchNo, 商户的多个应用共享此绑定。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinDirectChannelMerchantService { + + private final ChannelMerchantManager channelMerchantManager; + private final DouyinDirectChannelMerchantManager douyinDirectChannelMerchantManager; + + /// 创建抖音直连通道商户 + @Transactional(rollbackFor = Exception.class) + public void create(DouyinDirectChannelMerchantCreateParam param) { + // 校验同一商户下抖音商户号不重复 + if (douyinDirectChannelMerchantManager.existsByMchNoAndDyMchId( + param.getMchNo(), param.getDyMchId())) { + // 抖音: 同一商户下该抖音商户已存在 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.douyin.directMchDuplicate"); + } + // 生成通道商户号(雪花号) + String channelMchNo = String.valueOf(IdUtil.getSnowflakeNextId()); + // 写通用通道商户主表 + ChannelMerchant channelMerchant = new ChannelMerchant(); + channelMerchant.setMchNo(param.getMchNo()); + channelMerchant.setChannelMchNo(channelMchNo); + channelMerchant.setChannelMerchantName(param.getChannelMerchantName()); + channelMerchant.setProduct(param.getProduct()); + channelMerchant.setSource(ChannelMerchantSourceEnum.MANUAL.getCode()); + channelMerchant.setEnable(true); + channelMerchantManager.save(channelMerchant); + // 写直连绑定表(dyMchId 作为业务字段) + DouyinDirectChannelMerchant entity = new DouyinDirectChannelMerchant(); + entity.setMchNo(param.getMchNo()); + entity.setChannelMchNo(channelMchNo); + entity.setProduct(param.getProduct()); + entity.setDyMchId(param.getDyMchId()); + douyinDirectChannelMerchantManager.save(entity); + } + + /// 根据通道商户号查询直连通道商户配置 + public DouyinDirectChannelMerchantResult findByChannelMchNo(String channelMchNo) { + return douyinDirectChannelMerchantManager.lambdaQuery() + .eq(DouyinDirectChannelMerchant::getChannelMchNo, channelMchNo) + .oneOpt() + .map(DouyinDirectChannelMerchant::toResult) + // 通道: 通道商户配置不存在 + .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectKeyConfigService.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectKeyConfigService.java new file mode 100644 index 000000000..326ffc496 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/service/direct/DouyinDirectKeyConfigService.java @@ -0,0 +1,54 @@ +package org.dromara.daxpay.channel.douyin.service.direct; + +import org.dromara.daxpay.channel.douyin.convert.direct.DouyinDirectKeyConfigConvert; +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager; +import org.dromara.daxpay.channel.douyin.dao.direct.DouyinDirectKeyConfigManager; +import org.dromara.daxpay.channel.douyin.entity.direct.DouyinDirectKeyConfig; +import org.dromara.daxpay.channel.douyin.param.direct.DouyinDirectKeyConfigParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/// # 抖音直连密钥配置 +/// +/// 管理直连商户维度的密钥配置,查询时不存在则创建默认记录,保存时合并敏感字段。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinDirectKeyConfigService { + + private final DouyinDirectKeyConfigManager douyinDirectKeyConfigManager; + private final DouyinDirectChannelMerchantManager douyinDirectChannelMerchantManager; + + /// 根据通道商户号查询密钥配置, 不存在则创建默认记录 + @Transactional(rollbackFor = Exception.class) + public DouyinDirectKeyConfig findByChannelMchNo(String channelMchNo) { + var existing = douyinDirectKeyConfigManager.findByChannelMchNo(channelMchNo); + if (existing.isPresent()) { + return existing.get(); + } + var config = new DouyinDirectKeyConfig() + .setChannelMchNo(channelMchNo); + // 查询通道商户记录填充商户号 + douyinDirectChannelMerchantManager.findByChannelMchNo(channelMchNo) + .ifPresent(mch -> config.setMchNo(mch.getMchNo())); + douyinDirectKeyConfigManager.save(config); + return config; + } + + /// 保存密钥配置(更新) + @Transactional(rollbackFor = Exception.class) + public void save(DouyinDirectKeyConfigParam param) { + var config = this.findByChannelMchNo(param.getChannelMchNo()); + config.setMchNo(param.getMchNo()); + DouyinDirectKeyConfigConvert.CONVERT.copy(param, config); + douyinDirectKeyConfigManager.updateById(config); + } + + /// 根据通道商户号删除密钥配置 + public void deleteByChannelMchNo(String channelMchNo) { + douyinDirectKeyConfigManager.deleteByChannelMchNo(channelMchNo); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinDirectPayStrategy.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinDirectPayStrategy.java new file mode 100644 index 000000000..bd419aebf --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinDirectPayStrategy.java @@ -0,0 +1,28 @@ +package org.dromara.daxpay.channel.douyin.strategy; + +import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum; +import org.dromara.daxpay.payment.pay.bo.trade.PayResultBo; +import org.dromara.daxpay.payment.pay.strategy.AbsPayStrategy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 抖音支付直连支付策略 +/// +/// 抖音支付直连模式的支付策略(虚类,暂未实现具体支付逻辑)。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinDirectPayStrategy extends AbsPayStrategy { + + @Override + public ProductEnum getProduct() { + return ProductEnum.DOUYIN_PAY; + } + + @Override + public PayResultBo doPayHandler() { + throw new UnsupportedOperationException("抖音支付直连暂未实现"); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinPayProductStrategy.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinPayProductStrategy.java index 699fa0983..de8c166d1 100644 --- a/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinPayProductStrategy.java +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/org/dromara/daxpay/channel/douyin/strategy/DouyinPayProductStrategy.java @@ -11,10 +11,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Collections; import java.util.List; import java.util.Map; +import static org.dromara.daxpay.platform.core.enums.pay.channel.PayProviderEnum.DOUYIN; + /// # 抖音支付直连产品策略 /// @Slf4j @@ -22,11 +23,22 @@ import java.util.Map; @RequiredArgsConstructor public class DouyinPayProductStrategy extends AbsProductStrategy { + private static final Map> METHOD_CAP_MAP = Map.of( + PayMethodEnum.DOUYIN_QR, List.of(PayCapabilityEnum.DOUYIN_QR), + PayMethodEnum.DOUYIN_JSAPI, List.of(PayCapabilityEnum.DOUYIN_JSAPI), + PayMethodEnum.DOUYIN_H5, List.of(PayCapabilityEnum.DOUYIN_H5), + PayMethodEnum.DOUYIN_APP, List.of(PayCapabilityEnum.DOUYIN_APP)); + @Override public ProductEnum getProduct() { return ProductEnum.DOUYIN_PAY; } + @Override + public boolean isAllocatable() { + return false; + } + @Override public boolean isSandbox() { return false; @@ -44,11 +56,11 @@ public class DouyinPayProductStrategy extends AbsProductStrategy { @Override public List supportedPayProviders() { - return Collections.emptyList(); + return List.of(DOUYIN); } @Override public Map> methodCapabilityMapping() { - return Collections.emptyMap(); + return METHOD_CAP_MAP; } }