From 14395a52288c2409a0da045d10aa9e5fcaaea802 Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Mon, 15 Jun 2026 11:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=AE=E4=BF=A1=E7=9B=B4=E8=BF=9E?= =?UTF-8?q?=E9=80=9A=E9=81=93=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _config/sql/update-tables.sql | 431 +++++++++--------- .../channel/wechat/code/WechatCode.java | 22 + .../direct/WechatDirectAppController.java | 125 +++++ ...WechatDirectChannelMerchantController.java | 20 + .../WechatDirectAppAuthConfigConvert.java | 30 ++ .../direct/WechatDirectAppConvert.java | 27 ++ .../direct/WechatDirectKeyConfigConvert.java | 30 ++ .../WechatDirectAppAuthConfigManager.java | 29 ++ .../WechatDirectAppAuthConfigMapper.java | 13 + .../dao/direct/WechatDirectAppManager.java | 35 ++ .../dao/direct/WechatDirectAppMapper.java | 13 + .../WechatDirectChannelMerchantManager.java | 9 + .../direct/WechatDirectKeyConfigManager.java | 29 ++ .../direct/WechatDirectKeyConfigMapper.java | 13 + .../wechat/entity/direct/WechatDirectApp.java | 39 ++ .../direct/WechatDirectAppAuthConfig.java | 45 ++ .../entity/direct/WechatDirectKeyConfig.java | 56 +++ .../WechatDirectAppAuthConfigParam.java | 38 ++ .../param/direct/WechatDirectAppParam.java | 38 ++ .../direct/WechatDirectKeyConfigParam.java | 42 ++ .../WechatDirectAppAuthConfigResult.java | 33 ++ .../result/direct/WechatDirectAppResult.java | 27 ++ .../direct/WechatDirectKeyConfigResult.java | 47 ++ .../WechatDirectAppAuthConfigService.java | 64 +++ .../direct/WechatDirectAppService.java | 93 ++++ .../direct/WechatDirectKeyConfigService.java | 54 +++ 26 files changed, 1182 insertions(+), 220 deletions(-) create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/code/WechatCode.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectAppController.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppAuthConfigConvert.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppConvert.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectKeyConfigConvert.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigMapper.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppManager.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppMapper.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigManager.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigMapper.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectApp.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectAppAuthConfig.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectKeyConfig.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppAuthConfigParam.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppParam.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectKeyConfigParam.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppAuthConfigResult.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppResult.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectKeyConfigResult.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppAuthConfigService.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppService.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectKeyConfigService.java diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index 7f240f0bc..d68810a6e 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -1,27 +1,28 @@ -- ===================================================================== --- 支付宝通道相关表 (DROP + CREATE 重建) +-- 微信通道相关表 (DROP + CREATE 重建) -- 对应实体: --- org.dromara.daxpay.channel.alipay.entity.isv.AlipayIsvApp --- org.dromara.daxpay.channel.alipay.entity.isv.AlipayIsvAppKeyConfig --- org.dromara.daxpay.channel.alipay.entity.isv.AlipayIsvAppAuthConfig --- org.dromara.daxpay.channel.alipay.entity.isv.AlipayIsvChannelMerchant --- org.dromara.daxpay.channel.alipay.entity.direct.AlipayDirectApp --- org.dromara.daxpay.channel.alipay.entity.direct.AlipayDirectAppKeyConfig --- org.dromara.daxpay.channel.alipay.entity.direct.AlipayDirectAppAuthConfig --- org.dromara.daxpay.channel.alipay.entity.direct.AlipayDirectChannelMerchant +-- 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. 支付宝服务商应用 alipay_isv_app +-- 1. 微信服务商应用 wechat_isv_app -- ========================= -DROP TABLE IF EXISTS "public"."alipay_isv_app"; +DROP TABLE IF EXISTS "public"."wechat_isv_app"; -CREATE TABLE "public"."alipay_isv_app" ( +CREATE TABLE "public"."wechat_isv_app" ( "id" int8 NOT NULL, "app_name" varchar(64), - "ali_app_id" varchar(64), + "app_type" varchar(32), + "wx_app_id" varchar(64), "creator" int8, "create_time" timestamptz(6), "last_modifier" int8, @@ -30,74 +31,31 @@ CREATE TABLE "public"."alipay_isv_app" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_isv_app" IS '支付宝服务商应用'; -COMMENT ON COLUMN "public"."alipay_isv_app"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_isv_app"."app_name" IS '应用名称'; -COMMENT ON COLUMN "public"."alipay_isv_app"."ali_app_id" IS '支付宝应用ID'; -COMMENT ON COLUMN "public"."alipay_isv_app"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_isv_app"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_isv_app"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_isv_app"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_isv_app"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_isv_app"."deleted" IS '逻辑删除标志'; +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"."alipay_isv_app" - ADD CONSTRAINT "pk_alipay_isv_app" PRIMARY KEY ("id"); +ALTER TABLE "public"."wechat_isv_app" + ADD CONSTRAINT "pk_wechat_isv_app" PRIMARY KEY ("id"); -- ========================= --- 2. 支付宝服务商应用密钥配置 alipay_isv_app_key_config +-- 2. 微信服务商应用授权配置 wechat_isv_app_auth_config -- ========================= -DROP TABLE IF EXISTS "public"."alipay_isv_app_key_config"; +DROP TABLE IF EXISTS "public"."wechat_isv_app_auth_config"; -CREATE TABLE "public"."alipay_isv_app_key_config" ( +CREATE TABLE "public"."wechat_isv_app_auth_config" ( "id" int8 NOT NULL, - "alipay_isv_app_id" int8, - "auth_type" varchar(32), - "alipay_public_key" text, - "private_key" text, - "app_cert" text, - "alipay_cert" text, - "alipay_root_cert" text, - "secret_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"."alipay_isv_app_key_config" IS '支付宝服务商应用密钥配置'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."alipay_isv_app_id" IS '支付宝服务商应用ID(指向 alipay_isv_app.id)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."auth_type" IS '认证类型'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."alipay_public_key" IS '支付宝公钥'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."private_key" IS '应用私钥(加密存储)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."app_cert" IS '应用公钥证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."alipay_cert" IS '支付宝公钥证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."alipay_root_cert" IS '支付宝CA根证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."secret_key" IS 'AES通信密钥(加密存储)'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_isv_app_key_config"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."alipay_isv_app_key_config" - ADD CONSTRAINT "pk_alipay_isv_app_key_config" PRIMARY KEY ("id"); - - --- ========================= --- 3. 支付宝服务商应用授权配置 alipay_isv_app_auth_config --- ========================= -DROP TABLE IF EXISTS "public"."alipay_isv_app_auth_config"; - -CREATE TABLE "public"."alipay_isv_app_auth_config" ( - "id" int8 NOT NULL, - "alipay_isv_app_id" int8, - "user_id_type" varchar(32), + "wechat_isv_app_id" int8, + "app_secret" text, "auth_callback_url" varchar(255), "creator" int8, "create_time" timestamptz(6), @@ -107,38 +65,78 @@ CREATE TABLE "public"."alipay_isv_app_auth_config" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_isv_app_auth_config" IS '支付宝服务商应用授权配置'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."alipay_isv_app_id" IS '支付宝服务商应用ID(指向 alipay_isv_app.id)'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."user_id_type" IS '用户标识类型'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."auth_callback_url" IS '授权回调地址'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_isv_app_auth_config"."deleted" IS '逻辑删除标志'; +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"."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"."alipay_isv_app_auth_config" - ADD CONSTRAINT "pk_alipay_isv_app_auth_config" PRIMARY KEY ("id"); +ALTER TABLE "public"."wechat_isv_app_auth_config" + ADD CONSTRAINT "pk_wechat_isv_app_auth_config" PRIMARY KEY ("id"); -- ========================= --- 4. 支付宝服务商通道商户绑定 alipay_isv_channel_merchant --- 一条记录代表子商户挂靠在某个服务商应用下的授权关系 --- 同一子商户挂不同应用 = 不同行(不同 channel_mch_no) +-- 3. 微信服务商密钥配置 wechat_isv_key_config -- ========================= -DROP TABLE IF EXISTS "public"."mch_alipay_isv_channel_merchant"; -DROP TABLE IF EXISTS "public"."alipay_isv_channel_merchant"; +DROP TABLE IF EXISTS "public"."wechat_isv_key_config"; -CREATE TABLE "public"."alipay_isv_channel_merchant" ( +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, - "isv_app_id" int8, - "alipay_user_id" varchar(64), - "app_auth_token" varchar(128), + "sub_mch_id" varchar(32), "creator" int8, "create_time" timestamptz(6), "last_modifier" int8, @@ -147,37 +145,72 @@ CREATE TABLE "public"."alipay_isv_channel_merchant" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_isv_channel_merchant" IS '支付宝服务商通道商户绑定'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."channel_mch_no" IS '通道商户号(AISV+雪花)'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."product" IS '所属支付产品'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."isv_app_id" IS '关联服务商应用ID(指向 alipay_isv_app.id)'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."alipay_user_id" IS '子商户支付宝识别码(2088开头的16位数字)'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."app_auth_token" IS '应用授权令牌(服务商代子商户调用接口的凭据, 会过期/刷新)'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_isv_channel_merchant"."deleted" IS '逻辑删除标志'; +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"."alipay_isv_channel_merchant" - ADD CONSTRAINT "pk_alipay_isv_channel_merchant" PRIMARY KEY ("id"); +ALTER TABLE "public"."mch_wechat_isv_channel_merchant" + ADD CONSTRAINT "pk_mch_wechat_isv_channel_merchant" PRIMARY KEY ("id"); -- ========================= --- 5. 支付宝直连商户应用 alipay_direct_app --- 每个应用关联一个通道商户号, 拥有独立的支付宝应用ID +-- 5. 微信直连通道商户绑定 mch_wechat_direct_channel_merchant +-- 一个微信商户号(wx_mch_id)对应一个channelMchNo, 商户的多个应用共享此绑定 -- ========================= -DROP TABLE IF EXISTS "public"."alipay_direct_app"; +DROP TABLE IF EXISTS "public"."mch_wechat_direct_channel_merchant"; -CREATE TABLE "public"."alipay_direct_app" ( +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), - "ali_app_id" varchar(64), + "wx_app_id" varchar(64), "creator" int8, "create_time" timestamptz(6), "last_modifier" int8, @@ -186,41 +219,39 @@ CREATE TABLE "public"."alipay_direct_app" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_direct_app" IS '支付宝直连商户应用'; -COMMENT ON COLUMN "public"."alipay_direct_app"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_direct_app"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app"."app_name" IS '应用名称'; -COMMENT ON COLUMN "public"."alipay_direct_app"."ali_app_id" IS '支付宝应用ID'; -COMMENT ON COLUMN "public"."alipay_direct_app"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_direct_app"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_direct_app"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_direct_app"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_direct_app"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_direct_app"."deleted" IS '逻辑删除标志'; +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"."alipay_direct_app" - ADD CONSTRAINT "pk_alipay_direct_app" PRIMARY KEY ("id"); +ALTER TABLE "public"."wechat_direct_app" + ADD CONSTRAINT "pk_wechat_direct_app" PRIMARY KEY ("id"); -- ========================= --- 6. 支付宝直连商户应用密钥配置 alipay_direct_app_key_config --- 支持公钥模式和证书模式两种认证方式, 敏感字段加密存储 +-- 7. 微信直连密钥配置 wechat_direct_key_config +-- 直连商户维度的APIv3密钥和证书, 按通道商户号关联, 与具体应用无关, 敏感字段加密存储 -- ========================= -DROP TABLE IF EXISTS "public"."alipay_direct_app_key_config"; +DROP TABLE IF EXISTS "public"."wechat_direct_key_config"; -CREATE TABLE "public"."alipay_direct_app_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, - "alipay_direct_app_id" int8, - "auth_type" varchar(32), - "alipay_public_key" text, + "api_key_v3" text, + "public_key" text, + "public_key_id" varchar(128), "private_key" text, - "app_cert" text, - "alipay_cert" text, - "alipay_root_cert" text, - "secret_key" text, + "private_cert" text, + "cert_serial_no" varchar(128), "creator" int8, "create_time" timestamptz(6), "last_modifier" int8, @@ -229,40 +260,38 @@ CREATE TABLE "public"."alipay_direct_app_key_config" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_direct_app_key_config" IS '支付宝直连商户应用密钥配置'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."alipay_direct_app_id" IS '关联应用ID(指向 alipay_direct_app.id)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."auth_type" IS '认证类型'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."alipay_public_key" IS '支付宝公钥'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."private_key" IS '应用私钥(加密存储)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."app_cert" IS '应用公钥证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."alipay_cert" IS '支付宝公钥证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."alipay_root_cert" IS '支付宝CA根证书(加密存储)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."secret_key" IS 'AES通信密钥(加密存储)'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_direct_app_key_config"."deleted" IS '逻辑删除标志'; +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"."alipay_direct_app_key_config" - ADD CONSTRAINT "pk_alipay_direct_app_key_config" PRIMARY KEY ("id"); +ALTER TABLE "public"."wechat_direct_key_config" + ADD CONSTRAINT "pk_wechat_direct_key_config" PRIMARY KEY ("id"); -- ========================= --- 7. 支付宝直连商户应用授权配置 alipay_direct_app_auth_config +-- 8. 微信直连商户应用授权配置 wechat_direct_app_auth_config -- ========================= -DROP TABLE IF EXISTS "public"."alipay_direct_app_auth_config"; +DROP TABLE IF EXISTS "public"."wechat_direct_app_auth_config"; -CREATE TABLE "public"."alipay_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, - "alipay_direct_app_id" int8, - "user_id_type" varchar(32), + "wechat_direct_app_id" int8, + "app_secret" text, "auth_callback_url" varchar(255), "creator" int8, "create_time" timestamptz(6), @@ -272,57 +301,19 @@ CREATE TABLE "public"."alipay_direct_app_auth_config" ( "deleted" bool DEFAULT false NOT NULL ); -COMMENT ON TABLE "public"."alipay_direct_app_auth_config" IS '支付宝直连商户应用授权配置'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."channel_mch_no" IS '通道商户号'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."alipay_direct_app_id" IS '关联应用ID(指向 alipay_direct_app.id)'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."user_id_type" IS '用户标识类型'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."auth_callback_url" IS '授权回调地址'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_direct_app_auth_config"."deleted" IS '逻辑删除标志'; +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"."alipay_direct_app_auth_config" - ADD CONSTRAINT "pk_alipay_direct_app_auth_config" PRIMARY KEY ("id"); - - --- ========================= --- 8. 支付宝直连通道商户绑定 alipay_direct_channel_merchant --- 一个商户PID对应一个channelMchNo, 商户的多个应用共享此绑定 --- ========================= -DROP TABLE IF EXISTS "public"."mch_alipay_direct_channel_merchant"; -DROP TABLE IF EXISTS "public"."alipay_direct_channel_merchant"; - -CREATE TABLE "public"."alipay_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, - "alipay_user_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"."alipay_direct_channel_merchant" IS '支付宝直连通道商户绑定'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."id" IS '主键'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."mch_no" IS '商户号'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."channel_mch_no" IS '通道商户号(系统生成雪花号)'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."product" IS '所属支付产品'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."alipay_user_id" IS '支付宝商家唯一识别码(2088开头的16位数字)'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."creator" IS '创建人'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."create_time" IS '创建时间'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."last_modifier" IS '最后修改人'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."last_modified_time" IS '最后修改时间'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."version" IS '乐观锁版本号'; -COMMENT ON COLUMN "public"."alipay_direct_channel_merchant"."deleted" IS '逻辑删除标志'; - -ALTER TABLE "public"."alipay_direct_channel_merchant" - ADD CONSTRAINT "pk_alipay_direct_channel_merchant" PRIMARY KEY ("id"); +ALTER TABLE "public"."wechat_direct_app_auth_config" + ADD CONSTRAINT "pk_wechat_direct_app_auth_config" PRIMARY KEY ("id"); diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/code/WechatCode.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/code/WechatCode.java new file mode 100644 index 000000000..a4262b189 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/code/WechatCode.java @@ -0,0 +1,22 @@ +package org.dromara.daxpay.channel.wechat.code; + +/// # 微信支付常量 +/// +/// 定义微信支付相关的常量,包括认证方式(仅APIv3证书模式)和用户标识类型等。 +/// +public interface WechatCode { + + /// # 认证方式 + /// + interface AuthType { + /// APIv3 证书模式 + String AUTH_TYPE_CERT = "cert"; + } + + /// # 用户标识类型 + /// + interface UserIdType { + /// 用户标识(OpenId) + String OPENID = "openid"; + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectAppController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectAppController.java new file mode 100644 index 000000000..0f87e3229 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectAppController.java @@ -0,0 +1,125 @@ +package org.dromara.daxpay.channel.wechat.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.platform.core.util.ValidationUtil; +import org.dromara.daxpay.platform.core.validation.ValidationGroup; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppParam; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppAuthConfigParam; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppResult; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppAuthConfigResult; +import org.dromara.daxpay.channel.wechat.service.direct.WechatDirectAppService; +import org.dromara.daxpay.channel.wechat.service.direct.WechatDirectAppAuthConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +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; + +import java.util.List; + +/// # 微信直连商户应用管理 +/// +/// 提供直连商户应用及其密钥配置、授权认证配置的 REST API,支持按商户号和通道商户号查询列表。 +/// +@PermCode(menuCode = "payment:wechat:mch-app") +@Validated +@Tag(name = "微信直连商户应用管理") +@RestController +@RequestMapping("/admin/wechat/mch-app") +@RequiredArgsConstructor +public class WechatDirectAppController { + + private final WechatDirectAppService wechatDirectAppService; + private final WechatDirectAppAuthConfigService wechatDirectAppAuthConfigService; + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "根据商户号和通道商户号查询应用列表") + @GetMapping("/list-by-channel-mch-no") + public Result> listByChannelMchNo( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) { + return Res.ok(wechatDirectAppService.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(wechatDirectAppService.findById(id)); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "同一通道商户下微信应用ID是否已存在") + @GetMapping("/exists-wx-app-id-by-channel") + public Result existsWxAppIdByChannel( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo, + @NotBlank(message = "{validation.field.wxAppId.notBlank}") String wxAppId) { + return Res.ok(wechatDirectAppService.existsWxAppIdByChannel(mchNo, channelMchNo, wxAppId, null)); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "同一通道商户下微信应用ID是否已存在(排除自身)") + @GetMapping("/exists-wx-app-id-by-channel-not-id") + public Result existsWxAppIdByChannelNotId( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo, + @NotBlank(message = "{validation.field.wxAppId.notBlank}") String wxAppId, + @NotNull(message = "{validation.field.id.notNull}") Long id) { + return Res.ok(wechatDirectAppService.existsWxAppIdByChannel(mchNo, channelMchNo, wxAppId, id)); + } + + @PermCode(code = "add", nameCn = "通道商户新增", nameEn = "Channel Merchant Add") + @Operation(summary = "新增直连商户应用") + @PostMapping("/add") + public Result add(@RequestBody @Validated(ValidationGroup.add.class) WechatDirectAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.add.class); + wechatDirectAppService.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) WechatDirectAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.edit.class); + wechatDirectAppService.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) { + wechatDirectAppService.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.wechatDirectAppId.notNull}") Long wechatDirectAppId) { + var config = wechatDirectAppAuthConfigService.findByWechatDirectAppId(wechatDirectAppId); + var result = config.toResult(); + result.setAppSecretConfigured(config.getAppSecret() != null); + return Res.ok(result); + } + + @PermCode(code = "edit", nameCn = "通道商户编辑", nameEn = "Channel Merchant Edit") + @Operation(summary = "保存应用授权认证配置") + @PostMapping("/save-auth-config") + public Result saveAuthConfig(@RequestBody @Validated WechatDirectAppAuthConfigParam param) { + wechatDirectAppAuthConfigService.save(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java index db4e826a0..ec32a80c5 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java @@ -4,8 +4,11 @@ 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.wechat.param.direct.WechatDirectChannelMerchantCreateParam; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectKeyConfigParam; import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectChannelMerchantResult; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectKeyConfigResult; import org.dromara.daxpay.channel.wechat.service.direct.WechatDirectChannelMerchantService; +import org.dromara.daxpay.channel.wechat.service.direct.WechatDirectKeyConfigService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotBlank; @@ -28,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController; public class WechatDirectChannelMerchantController { private final WechatDirectChannelMerchantService wechatDirectChannelMerchantService; + private final WechatDirectKeyConfigService wechatDirectKeyConfigService; @PermCode(code = "view", nameCn = "商户通道商户查看", nameEn = "Merchant Channel Merchant View") @Operation(summary = "根据通道商户号查询微信直连通道商户配置") @@ -44,4 +48,20 @@ public class WechatDirectChannelMerchantController { wechatDirectChannelMerchantService.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) { + return Res.ok(wechatDirectKeyConfigService.findByChannelMchNo(channelMchNo).toResult()); + } + + @PermCode(code = "edit", nameCn = "商户通道商户编辑", nameEn = "Merchant Channel Merchant Edit") + @Operation(summary = "保存密钥配置") + @PostMapping("/save-key-config") + public Result saveKeyConfig(@RequestBody @Validated WechatDirectKeyConfigParam param) { + wechatDirectKeyConfigService.save(param); + return Res.ok(); + } } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppAuthConfigConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppAuthConfigConvert.java new file mode 100644 index 000000000..e4a616148 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppAuthConfigConvert.java @@ -0,0 +1,30 @@ +package org.dromara.daxpay.channel.wechat.convert.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectAppAuthConfig; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppAuthConfigParam; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppAuthConfigResult; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 微信直连商户应用授权认证配置转换 +/// +/// MapStruct转换器,负责微信直连商户应用授权认证配置在实体、参数和返回结果之间的转换,更新时空值不覆盖。 +/// +@Mapper +public interface WechatDirectAppAuthConfigConvert { + + WechatDirectAppAuthConfigConvert CONVERT = Mappers.getMapper(WechatDirectAppAuthConfigConvert.class); + + /// 转换为返回对象 + WechatDirectAppAuthConfigResult toResult(WechatDirectAppAuthConfig entity); + + /// 转换为实体 + WechatDirectAppAuthConfig toEntity(WechatDirectAppAuthConfigParam param); + + /// 更新源数据到实体(空值不覆盖) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void copy(WechatDirectAppAuthConfigParam param, @MappingTarget WechatDirectAppAuthConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppConvert.java new file mode 100644 index 000000000..6d055d533 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectAppConvert.java @@ -0,0 +1,27 @@ +package org.dromara.daxpay.channel.wechat.convert.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppParam; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppResult; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/// # 微信直连商户应用转换 +/// +/// MapStruct转换器,负责微信直连商户应用在实体、参数和返回结果之间的相互转换。 +/// +@Mapper +public interface WechatDirectAppConvert { + + WechatDirectAppConvert CONVERT = Mappers.getMapper(WechatDirectAppConvert.class); + + /// 转换为返回对象 + WechatDirectAppResult toResult(WechatDirectApp entity); + + /// 转换为实体 + WechatDirectApp toEntity(WechatDirectAppParam param); + + /// 更新源数据到实体(忽略空值) + void copy(WechatDirectAppParam param, @MappingTarget WechatDirectApp entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectKeyConfigConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectKeyConfigConvert.java new file mode 100644 index 000000000..8f66ead1a --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/convert/direct/WechatDirectKeyConfigConvert.java @@ -0,0 +1,30 @@ +package org.dromara.daxpay.channel.wechat.convert.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectKeyConfig; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectKeyConfigParam; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectKeyConfigResult; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 微信直连密钥配置转换 +/// +/// MapStruct转换器,负责微信直连密钥配置在实体、参数和返回结果之间的转换,更新时空值不覆盖。 +/// +@Mapper +public interface WechatDirectKeyConfigConvert { + + WechatDirectKeyConfigConvert CONVERT = Mappers.getMapper(WechatDirectKeyConfigConvert.class); + + /// 转换为返回对象 + WechatDirectKeyConfigResult toResult(WechatDirectKeyConfig entity); + + /// 转换为实体 + WechatDirectKeyConfig toEntity(WechatDirectKeyConfigParam param); + + /// 更新源数据到实体(空值不覆盖,密钥/证书为空时保留原值) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void copy(WechatDirectKeyConfigParam param, @MappingTarget WechatDirectKeyConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java new file mode 100644 index 000000000..3689d1a6a --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigManager.java @@ -0,0 +1,29 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectAppAuthConfig; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 微信直连商户应用授权认证配置 +/// +/// 直连商户应用授权认证配置数据访问管理器,提供按应用ID查询和删除授权认证配置的方法。 +/// +@Repository +public class WechatDirectAppAuthConfigManager extends BaseManager { + + /// 根据应用ID查询授权认证配置 + public Optional findByWechatDirectAppId(Long wechatDirectAppId) { + return lambdaQuery() + .eq(WechatDirectAppAuthConfig::getWechatDirectAppId, wechatDirectAppId) + .oneOpt(); + } + + /// 根据应用ID删除授权认证配置 + public void deleteByWechatDirectAppId(Long wechatDirectAppId) { + lambdaUpdate() + .eq(WechatDirectAppAuthConfig::getWechatDirectAppId, wechatDirectAppId) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigMapper.java new file mode 100644 index 000000000..b18ef88fd --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppAuthConfigMapper.java @@ -0,0 +1,13 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectAppAuthConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信直连商户应用授权认证配置 +/// +/// 微信直连商户应用授权认证配置 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatDirectAppAuthConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppManager.java new file mode 100644 index 000000000..6dbd1f25d --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppManager.java @@ -0,0 +1,35 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/// # 微信直连商户应用 +/// +/// 直连商户应用数据访问管理器,提供按商户号和通道商户号查询列表、同一通道下应用ID唯一性校验等方法。 +/// +@Repository +public class WechatDirectAppManager extends BaseManager { + + /// 根据商户号和通道商户号查询应用列表(按创建时间升序) + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + return lambdaQuery() + .eq(WechatDirectApp::getMchNo, mchNo) + .eq(WechatDirectApp::getChannelMchNo, channelMchNo) + .orderByAsc(WechatDirectApp::getCreateTime) + .orderByAsc(WechatDirectApp::getId) + .list(); + } + + /// 校验同一通道商户下wxAppId是否已存在(排除自身) + public boolean existsByChannelMchNoAndWxAppId(String mchNo, String channelMchNo, String wxAppId, Long excludeId) { + return lambdaQuery() + .eq(WechatDirectApp::getMchNo, mchNo) + .eq(WechatDirectApp::getChannelMchNo, channelMchNo) + .eq(WechatDirectApp::getWxAppId, wxAppId) + .ne(excludeId != null, WechatDirectApp::getId, excludeId) + .exists(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppMapper.java new file mode 100644 index 000000000..7ed6ce0ba --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectAppMapper.java @@ -0,0 +1,13 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信直连商户应用 +/// +/// 微信直连商户应用 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatDirectAppMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectChannelMerchantManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectChannelMerchantManager.java index 9fca44b19..ac742e993 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectChannelMerchantManager.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectChannelMerchantManager.java @@ -4,6 +4,8 @@ import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectChannelMercha import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; import org.springframework.stereotype.Repository; +import java.util.Optional; + /// # 微信直连通道商户绑定 /// @Repository @@ -16,4 +18,11 @@ public class WechatDirectChannelMerchantManager extends BaseManager findByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(WechatDirectChannelMerchant::getChannelMchNo, channelMchNo) + .oneOpt(); + } } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigManager.java new file mode 100644 index 000000000..53ff63bf4 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigManager.java @@ -0,0 +1,29 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectKeyConfig; +import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 微信直连密钥配置 +/// +/// 直连密钥配置数据访问管理器,提供按通道商户号查询和删除密钥配置的方法。 +/// +@Repository +public class WechatDirectKeyConfigManager extends BaseManager { + + /// 根据通道商户号查询密钥配置 + public Optional findByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(WechatDirectKeyConfig::getChannelMchNo, channelMchNo) + .oneOpt(); + } + + /// 根据通道商户号删除密钥配置 + public void deleteByChannelMchNo(String channelMchNo) { + lambdaUpdate() + .eq(WechatDirectKeyConfig::getChannelMchNo, channelMchNo) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigMapper.java new file mode 100644 index 000000000..a580030e2 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/dao/direct/WechatDirectKeyConfigMapper.java @@ -0,0 +1,13 @@ +package org.dromara.daxpay.channel.wechat.dao.direct; + +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectKeyConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信直连密钥配置 +/// +/// 微信直连密钥配置 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatDirectKeyConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectApp.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectApp.java new file mode 100644 index 000000000..810b5fc3a --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectApp.java @@ -0,0 +1,39 @@ +package org.dromara.daxpay.channel.wechat.entity.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectAppConvert; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppResult; +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 = "wechat_direct_app", autoResultMap = true) +public class WechatDirectApp extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 应用名称 + private String appName; + + /// 微信应用AppId + private String wxAppId; + + /// 转换 + @Override + public WechatDirectAppResult toResult() { + return WechatDirectAppConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectAppAuthConfig.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectAppAuthConfig.java new file mode 100644 index 000000000..9a02de829 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectAppAuthConfig.java @@ -0,0 +1,45 @@ +package org.dromara.daxpay.channel.wechat.entity.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectAppAuthConfigConvert; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppAuthConfigResult; +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; + +/// # 微信直连商户应用授权认证配置 +/// +/// 配置直连商户应用的应用密钥和授权回调地址,用于微信OAuth授权流程中的身份验证与回调跳转。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "wechat_direct_app_auth_config", autoResultMap = true) +public class WechatDirectAppAuthConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 关联应用ID + @TableField(updateStrategy = FieldStrategy.NEVER) + private Long wechatDirectAppId; + + /// 应用密钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String appSecret; + + /// 授权回调地址 + private String authCallbackUrl; + + /// 转换 + @Override + public WechatDirectAppAuthConfigResult toResult() { + return WechatDirectAppAuthConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectKeyConfig.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectKeyConfig.java new file mode 100644 index 000000000..5a21d4cfa --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/entity/direct/WechatDirectKeyConfig.java @@ -0,0 +1,56 @@ +package org.dromara.daxpay.channel.wechat.entity.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectKeyConfigConvert; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectKeyConfigResult; +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; + +/// # 微信直连密钥配置 +/// +/// 直连商户维度的APIv3密钥和证书配置,一个微信商户号共享一套密钥/证书,与具体应用无关,敏感字段加密存储。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "wechat_direct_key_config", autoResultMap = true) +public class WechatDirectKeyConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// APIv3密钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String apiKeyV3; + + /// 支付公钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String publicKey; + + /// 支付公钥ID + private String publicKeyId; + + /// 商户私钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String privateKey; + + /// 商户证书(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String privateCert; + + /// 证书序列号 + private String certSerialNo; + + /// 转换 + @Override + public WechatDirectKeyConfigResult toResult() { + return WechatDirectKeyConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppAuthConfigParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppAuthConfigParam.java new file mode 100644 index 000000000..7aef9f6a5 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppAuthConfigParam.java @@ -0,0 +1,38 @@ +package org.dromara.daxpay.channel.wechat.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 WechatDirectAppAuthConfigParam { + + @NotNull(message = "{validation.field.wechatDirectAppId.notNull}") + @Schema(description = "关联应用ID") + private Long wechatDirectAppId; + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "应用密钥") + private String appSecret; + + @NotBlank(message = "{validation.field.authCallbackUrl.notBlank}") + @Size(max = 512, message = "{validation.field.authCallbackUrl.size}") + @Schema(description = "授权回调地址") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppParam.java new file mode 100644 index 000000000..a0b62c9ed --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectAppParam.java @@ -0,0 +1,38 @@ +package org.dromara.daxpay.channel.wechat.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 WechatDirectAppParam { + + @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.wxAppId.notBlank}") + @Schema(description = "微信应用AppId") + private String wxAppId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectKeyConfigParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectKeyConfigParam.java new file mode 100644 index 000000000..99f2c87d3 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/param/direct/WechatDirectKeyConfigParam.java @@ -0,0 +1,42 @@ +package org.dromara.daxpay.channel.wechat.param.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 微信直连密钥配置保存参数 +/// +/// 保存/更新微信直连密钥和证书时接收的请求参数,含通道商户号和APIv3密钥等信息。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "微信直连密钥配置保存参数") +public class WechatDirectKeyConfigParam { + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "APIv3密钥") + private String apiKeyV3; + + @Schema(description = "支付公钥") + private String publicKey; + + @Schema(description = "支付公钥ID") + private String publicKeyId; + + @Schema(description = "商户私钥") + private String privateKey; + + @Schema(description = "商户证书") + private String privateCert; + + @Schema(description = "证书序列号") + private String certSerialNo; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppAuthConfigResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppAuthConfigResult.java new file mode 100644 index 000000000..1d52e24b2 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppAuthConfigResult.java @@ -0,0 +1,33 @@ +package org.dromara.daxpay.channel.wechat.result.direct; + +import org.dromara.daxpay.platform.core.result.BaseResult; +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 WechatDirectAppAuthConfigResult extends BaseResult { + + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "关联应用ID") + private Long wechatDirectAppId; + + @Schema(description = "是否已配置应用密钥") + private Boolean appSecretConfigured; + + @Schema(description = "授权回调地址") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppResult.java new file mode 100644 index 000000000..2973d70f6 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectAppResult.java @@ -0,0 +1,27 @@ +package org.dromara.daxpay.channel.wechat.result.direct; + +import org.dromara.daxpay.payment.common.result.MchTradeBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 微信直连商户应用 +/// +/// 微信直连商户应用的返回结果对象,包含通道商户号、应用名称和微信应用AppId等信息。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "微信直连商户应用") +public class WechatDirectAppResult extends MchTradeBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "微信应用AppId") + private String wxAppId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectKeyConfigResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectKeyConfigResult.java new file mode 100644 index 000000000..3a78efac1 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/result/direct/WechatDirectKeyConfigResult.java @@ -0,0 +1,47 @@ +package org.dromara.daxpay.channel.wechat.result.direct; + +import org.dromara.daxpay.platform.common.json.sensitive.SensitiveInfo; +import org.dromara.daxpay.platform.core.result.BaseResult; +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 WechatDirectKeyConfigResult extends BaseResult { + + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "通道商户号") + private String channelMchNo; + + @SensitiveInfo(front = 12, end = 12) + @Schema(description = "APIv3密钥") + private String apiKeyV3; + + @SensitiveInfo(front = 12, end = 12) + @Schema(description = "支付公钥") + private String publicKey; + + @Schema(description = "支付公钥ID") + private String publicKeyId; + + @SensitiveInfo(front = 12, end = 12) + @Schema(description = "商户私钥") + private String privateKey; + + @SensitiveInfo(front = 12, end = 12) + @Schema(description = "商户证书") + private String privateCert; + + @Schema(description = "证书序列号") + private String certSerialNo; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppAuthConfigService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppAuthConfigService.java new file mode 100644 index 000000000..41ea3b606 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppAuthConfigService.java @@ -0,0 +1,64 @@ +package org.dromara.daxpay.channel.wechat.service.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectAppAuthConfigConvert; +import org.dromara.daxpay.channel.wechat.dao.direct.WechatDirectAppManager; +import org.dromara.daxpay.channel.wechat.dao.direct.WechatDirectAppAuthConfigManager; +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp; +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectAppAuthConfig; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppAuthConfigParam; +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 WechatDirectAppAuthConfigService { + + private final WechatDirectAppAuthConfigManager wechatDirectAppAuthConfigManager; + private final WechatDirectAppManager wechatDirectAppManager; + + /// 根据应用ID查询授权认证配置, 不存在则创建默认记录 + @Transactional(rollbackFor = Exception.class) + public WechatDirectAppAuthConfig findByWechatDirectAppId(Long wechatDirectAppId) { + var app = wechatDirectAppManager.findById(wechatDirectAppId) + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); + var existing = wechatDirectAppAuthConfigManager.findByWechatDirectAppId(wechatDirectAppId); + if (existing.isPresent()) { + return existing.get(); + } + var config = new WechatDirectAppAuthConfig() + .setChannelMchNo(app.getChannelMchNo()) + .setWechatDirectAppId(wechatDirectAppId); + config.setMchNo(app.getMchNo()); + wechatDirectAppAuthConfigManager.save(config); + return config; + } + + /// 保存应用授权认证配置(更新) + @Transactional(rollbackFor = Exception.class) + public void save(WechatDirectAppAuthConfigParam param) { + var app = wechatDirectAppManager.findById(param.getWechatDirectAppId()) + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); + if (!app.getMchNo().equals(param.getMchNo()) || !app.getChannelMchNo().equals(param.getChannelMchNo())) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.appNotFound"); + } + var config = this.findByWechatDirectAppId(param.getWechatDirectAppId()); + config.setAuthCallbackUrl(param.getAuthCallbackUrl()); + WechatDirectAppAuthConfigConvert.CONVERT.copy(param, config); + wechatDirectAppAuthConfigManager.updateById(config); + } + + /// 删除应用授权认证配置 + public void deleteByWechatDirectAppId(Long wechatDirectAppId) { + wechatDirectAppAuthConfigManager.deleteByWechatDirectAppId(wechatDirectAppId); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppService.java new file mode 100644 index 000000000..09abe4017 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectAppService.java @@ -0,0 +1,93 @@ +package org.dromara.daxpay.channel.wechat.service.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectAppConvert; +import org.dromara.daxpay.channel.wechat.dao.direct.WechatDirectAppManager; +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectApp; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectAppParam; +import org.dromara.daxpay.channel.wechat.result.direct.WechatDirectAppResult; +import org.dromara.daxpay.channel.wechat.service.direct.WechatDirectAppAuthConfigService; +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 WechatDirectAppService { + + private final WechatDirectAppManager wechatDirectAppManager; + private final WechatDirectAppAuthConfigService wechatDirectAppAuthConfigService; + + /// 根据商户号和通道商户号查询应用列表 + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + var list = wechatDirectAppManager.listByMchNoAndChannelMchNo(mchNo, channelMchNo); + return MpUtil.toListResult(list); + } + + /// 根据ID查询应用详情 + public WechatDirectAppResult findById(Long id) { + return wechatDirectAppManager.findById(id) + .map(WechatDirectApp::toResult) + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); + } + + /// 判断同一通道商户下微信应用ID是否已存在 + public boolean existsWxAppIdByChannel(String mchNo, String channelMchNo, String wxAppId, Long excludeId) { + if (StrUtil.hasBlank(mchNo, channelMchNo, wxAppId)) { + return false; + } + return wechatDirectAppManager.existsByChannelMchNoAndWxAppId(mchNo, channelMchNo, wxAppId, excludeId); + } + + /// 新增微信直连商户应用 + public void add(WechatDirectAppParam param) { + this.assertWxAppIdUnique(param.getMchNo(), param.getChannelMchNo(), param.getWxAppId(), null); + var entity = WechatDirectAppConvert.CONVERT.toEntity(param); + wechatDirectAppManager.save(entity); + } + + /// 更新微信直连商户应用 + public void update(WechatDirectAppParam param) { + var entity = wechatDirectAppManager.findById(param.getId()) + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); + this.assertScopeMatch(entity, param.getMchNo(), param.getChannelMchNo()); + this.assertWxAppIdUnique(entity.getMchNo(), entity.getChannelMchNo(), param.getWxAppId(), param.getId()); + WechatDirectAppConvert.CONVERT.copy(param, entity); + wechatDirectAppManager.updateById(entity); + } + + /// 删除应用(级联删除授权认证配置) + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + wechatDirectAppManager.findById(id) + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound")); + wechatDirectAppAuthConfigService.deleteByWechatDirectAppId(id); + wechatDirectAppManager.deleteById(id); + } + + /// 校验同一通道商户下应用ID唯一 + private void assertWxAppIdUnique(String mchNo, String channelMchNo, String wxAppId, Long excludeId) { + if (this.existsWxAppIdByChannel(mchNo, channelMchNo, wxAppId, excludeId)) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.appIdDuplicate"); + } + } + + /// 校验操作范围与记录归属一致 + private void assertScopeMatch(WechatDirectApp entity, String mchNo, String channelMchNo) { + if (!entity.getMchNo().equals(mchNo) || !entity.getChannelMchNo().equals(channelMchNo)) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.appNotFound"); + } + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectKeyConfigService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectKeyConfigService.java new file mode 100644 index 000000000..79b50adcd --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/org/dromara/daxpay/channel/wechat/service/direct/WechatDirectKeyConfigService.java @@ -0,0 +1,54 @@ +package org.dromara.daxpay.channel.wechat.service.direct; + +import org.dromara.daxpay.channel.wechat.convert.direct.WechatDirectKeyConfigConvert; +import org.dromara.daxpay.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; +import org.dromara.daxpay.channel.wechat.dao.direct.WechatDirectKeyConfigManager; +import org.dromara.daxpay.channel.wechat.entity.direct.WechatDirectKeyConfig; +import org.dromara.daxpay.channel.wechat.param.direct.WechatDirectKeyConfigParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/// # 微信直连密钥配置 +/// +/// 管理直连商户维度的密钥和证书配置,查询时不存在则创建默认记录,保存时合并敏感字段。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatDirectKeyConfigService { + + private final WechatDirectKeyConfigManager wechatDirectKeyConfigManager; + private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager; + + /// 根据通道商户号查询密钥配置, 不存在则创建默认记录 + @Transactional(rollbackFor = Exception.class) + public WechatDirectKeyConfig findByChannelMchNo(String channelMchNo) { + var existing = wechatDirectKeyConfigManager.findByChannelMchNo(channelMchNo); + if (existing.isPresent()) { + return existing.get(); + } + var config = new WechatDirectKeyConfig() + .setChannelMchNo(channelMchNo); + // 查询通道商户记录填充商户号 + wechatDirectChannelMerchantManager.findByChannelMchNo(channelMchNo) + .ifPresent(mch -> config.setMchNo(mch.getMchNo())); + wechatDirectKeyConfigManager.save(config); + return config; + } + + /// 保存密钥配置(更新) + @Transactional(rollbackFor = Exception.class) + public void save(WechatDirectKeyConfigParam param) { + var config = this.findByChannelMchNo(param.getChannelMchNo()); + config.setMchNo(param.getMchNo()); + WechatDirectKeyConfigConvert.CONVERT.copy(param, config); + wechatDirectKeyConfigManager.updateById(config); + } + + /// 根据通道商户号删除密钥配置 + public void deleteByChannelMchNo(String channelMchNo) { + wechatDirectKeyConfigManager.deleteByChannelMchNo(channelMchNo); + } +}