From d4c0b03dd0d889aaf671c228e1e5e379f9b3ba9e Mon Sep 17 00:00:00 2001 From: daxpay Date: Tue, 4 Aug 2026 16:25:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(adapay):=20=E6=96=B0=E5=A2=9E=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=95=86=E5=AF=86=E9=92=A5=E9=85=8D=E7=BD=AE=E4=B8=8E?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=BA=94=E7=94=A8=E7=BB=91=E5=AE=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81,=20=E5=BE=AE=E4=BF=A1=E5=BA=94=E7=94=A8=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E8=83=BD=E5=8A=9B=E6=94=B6=E7=AA=84=E4=B8=8B=E6=B2=89?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AD=96=E7=95=A5=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _config/sql/table.sql | 78 ++++++++++++++++++- _config/sql/update-datas.sql | 6 -- _config/sql/update-tables.sql | 19 +++++ .../credential/AdapaySdkCredential.java | 3 + .../adapay/client/req/AdapayPayReq.java | 3 + .../isv/AdapayIsvKeyConfigController.java | 50 ++++++++++++ .../isv/AdapayIsvKeyConfigConvert.java | 27 +++++++ .../dao/isv/AdapayIsvKeyConfigManager.java | 21 +++++ .../dao/isv/AdapayIsvKeyConfigMapper.java | 10 +++ .../entity/direct/AdapayDirectKeyConfig.java | 5 +- .../adapay/entity/isv/AdapayIsvKeyConfig.java | 52 +++++++++++++ ...dapayDirectChannelMerchantCreateParam.java | 4 - .../param/isv/AdapayIsvKeyConfigParam.java | 31 ++++++++ .../direct/AdapayDirectKeyConfigResult.java | 3 - .../result/isv/AdapayIsvKeyConfigResult.java | 39 ++++++++++ .../AdapayDirectChannelMerchantService.java | 4 +- .../direct/AdapayDirectConfigAssembler.java | 22 +++++- .../isv/AdapayIsvKeyConfigService.java | 62 +++++++++++++++ .../service/payment/pay/AdapayPayService.java | 2 + .../product/AdapayDirectProductStrategy.java | 8 ++ .../service/payment/LeshuaPayService.java | 24 ++++++ .../product/LeshuaProductStrategy.java | 8 ++ .../strategy/product/AbsProductStrategy.java | 9 +++ .../wx/service/WxAppResolveService.java | 54 ++++++------- .../WxPlatformAppCapabilityService.java | 22 +++++- .../i18n/en-US/error/channel/adapay.json | 1 + .../main/resources/i18n/en-US/validation.json | 3 + .../i18n/id-ID/error/channel/adapay.json | 1 + .../main/resources/i18n/id-ID/validation.json | 3 + .../i18n/ja-JP/error/channel/adapay.json | 1 + .../main/resources/i18n/ja-JP/validation.json | 3 + .../i18n/ko-KR/error/channel/adapay.json | 1 + .../main/resources/i18n/ko-KR/validation.json | 3 + .../i18n/ms-MY/error/channel/adapay.json | 1 + .../main/resources/i18n/ms-MY/validation.json | 3 + .../i18n/th-TH/error/channel/adapay.json | 1 + .../main/resources/i18n/th-TH/validation.json | 3 + .../i18n/vi-VN/error/channel/adapay.json | 1 + .../main/resources/i18n/vi-VN/validation.json | 3 + .../i18n/zh-CN/error/channel/adapay.json | 1 + .../main/resources/i18n/zh-CN/validation.json | 3 + .../i18n/zh-HK/error/channel/adapay.json | 1 + .../main/resources/i18n/zh-HK/validation.json | 3 + .../i18n/zh-TW/error/channel/adapay.json | 1 + .../main/resources/i18n/zh-TW/validation.json | 3 + 45 files changed, 551 insertions(+), 55 deletions(-) create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/controller/isv/AdapayIsvKeyConfigController.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/convert/isv/AdapayIsvKeyConfigConvert.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigManager.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigMapper.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/entity/isv/AdapayIsvKeyConfig.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/isv/AdapayIsvKeyConfigParam.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/isv/AdapayIsvKeyConfigResult.java create mode 100644 daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/isv/AdapayIsvKeyConfigService.java diff --git a/_config/sql/table.sql b/_config/sql/table.sql index e3f2c79df..580203cea 100644 --- a/_config/sql/table.sql +++ b/_config/sql/table.sql @@ -27,6 +27,17 @@ MAXVALUE 9223372036854775807 START 1 CACHE 1; +-- ---------------------------- +-- Sequence structure for adapay_isv_key_config_id_seq +-- ---------------------------- +DROP SEQUENCE IF EXISTS "public"."adapay_isv_key_config_id_seq"; +CREATE SEQUENCE "public"."adapay_isv_key_config_id_seq" +INCREMENT 1 +MINVALUE 1 +MAXVALUE 9223372036854775807 +START 1 +CACHE 1; + -- ---------------------------- -- Sequence structure for alipay_direct_app_capability_id_seq -- ---------------------------- @@ -122,12 +133,12 @@ CREATE TABLE "public"."adapay_direct_key_config" ( "last_modifier" int8, "last_modified_time" timestamptz(6), "version" int4 DEFAULT 0, - "deleted" bool DEFAULT false + "deleted" bool DEFAULT false, + "sandbox" bool DEFAULT false ) ; COMMENT ON COLUMN "public"."adapay_direct_key_config"."mch_no" IS '商户号'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."channel_mch_no" IS '通道商户号(创建时录入不可修改)'; -COMMENT ON COLUMN "public"."adapay_direct_key_config"."merchant_no" IS 'Adapay 商户号(创建时录入不可修改)'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."adapay_app_id" IS 'Adapay 应用ID(app_id)'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."api_key" IS 'Adapay API Key(请求头Authorization, 加密存储)'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."private_key" IS '商户RSA私钥(PKCS#8 Base64, 请求签名, 加密存储)'; @@ -138,8 +149,42 @@ COMMENT ON COLUMN "public"."adapay_direct_key_config"."last_modifier" IS '最后 COMMENT ON COLUMN "public"."adapay_direct_key_config"."last_modified_time" IS '最后修改时间'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."version" IS '版本号(乐观锁)'; COMMENT ON COLUMN "public"."adapay_direct_key_config"."deleted" IS '删除标志'; +COMMENT ON COLUMN "public"."adapay_direct_key_config"."sandbox" IS '是否沙箱环境'; COMMENT ON TABLE "public"."adapay_direct_key_config" IS 'Adapay 直连密钥配置'; +-- ---------------------------- +-- Table structure for adapay_isv_key_config +-- ---------------------------- +DROP TABLE IF EXISTS "public"."adapay_isv_key_config"; +CREATE TABLE "public"."adapay_isv_key_config" ( + "id" int8 NOT NULL DEFAULT nextval('adapay_isv_key_config_id_seq'::regclass), + "isv_no" varchar(64) COLLATE "pg_catalog"."default", + "api_key" text COLLATE "pg_catalog"."default", + "private_key" text COLLATE "pg_catalog"."default", + "public_key" text COLLATE "pg_catalog"."default", + "creator" int8, + "create_time" timestamptz(6), + "last_modifier" int8, + "last_modified_time" timestamptz(6), + "version" int4 DEFAULT 0, + "deleted" bool DEFAULT false, + "sandbox" bool DEFAULT false +) +; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."id" IS '主键'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."isv_no" IS '服务商号(平台在汇付的服务商/主体编号)'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."api_key" IS 'Adapay 交易密钥(请求头Authorization, 加密存储)'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."private_key" IS '商户RSA私钥(PKCS#8 Base64, 请求签名, 加密存储)'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."public_key" IS 'Adapay 平台公钥(X509 Base64, 响应验签, 加密存储; 为空使用全局默认)'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."creator" IS '创建者ID'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."create_time" IS '创建时间'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."last_modifier" IS '最后修改者ID'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."last_modified_time" IS '最后修改时间'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."version" IS '版本号(乐观锁)'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."deleted" IS '删除标志'; +COMMENT ON COLUMN "public"."adapay_isv_key_config"."sandbox" IS '是否沙箱环境'; +COMMENT ON TABLE "public"."adapay_isv_key_config" IS 'Adapay 服务商密钥配置'; + -- ---------------------------- -- Table structure for alipay_direct_app -- ---------------------------- @@ -4118,6 +4163,13 @@ ALTER SEQUENCE "public"."adapay_direct_key_config_id_seq" OWNED BY "public"."adapay_direct_key_config"."id"; SELECT setval('"public"."adapay_direct_key_config_id_seq"', 1, false); +-- ---------------------------- +-- Alter sequences owned by +-- ---------------------------- +ALTER SEQUENCE "public"."adapay_isv_key_config_id_seq" +OWNED BY "public"."adapay_isv_key_config"."id"; +SELECT setval('"public"."adapay_isv_key_config_id_seq"', 1, false); + -- ---------------------------- -- Alter sequences owned by -- ---------------------------- @@ -4167,11 +4219,33 @@ ALTER SEQUENCE "public"."pay_sync_record_id_seq" OWNED BY "public"."pay_sync_record"."id"; SELECT setval('"public"."pay_sync_record_id_seq"', 1, false); +-- ---------------------------- +-- Indexes structure for table adapay_direct_key_config +-- ---------------------------- +CREATE UNIQUE INDEX "uk_adapay_direct_key_config_sandbox" ON "public"."adapay_direct_key_config" USING btree ( + "channel_mch_no" "pg_catalog"."text_ops" ASC NULLS LAST, + "sandbox" "pg_catalog"."bool_ops" ASC NULLS LAST +) WHERE deleted = false; +COMMENT ON INDEX "public"."uk_adapay_direct_key_config_sandbox" IS '同一通道商户同一环境密钥唯一'; + +-- ---------------------------- +-- Indexes structure for table adapay_isv_key_config +-- ---------------------------- +CREATE UNIQUE INDEX "uk_adapay_isv_key_config_sandbox" ON "public"."adapay_isv_key_config" USING btree ( + "sandbox" "pg_catalog"."bool_ops" ASC NULLS LAST +) WHERE deleted = false; +COMMENT ON INDEX "public"."uk_adapay_isv_key_config_sandbox" IS '同一环境服务商配置唯一(平台为唯一服务商)'; + -- ---------------------------- -- Primary Key structure for table adapay_direct_key_config -- ---------------------------- ALTER TABLE "public"."adapay_direct_key_config" ADD CONSTRAINT "adapay_direct_key_config_pkey" PRIMARY KEY ("id"); +-- ---------------------------- +-- Primary Key structure for table adapay_isv_key_config +-- ---------------------------- +ALTER TABLE "public"."adapay_isv_key_config" ADD CONSTRAINT "adapay_isv_key_config_pkey" PRIMARY KEY ("id"); + -- ---------------------------- -- Primary Key structure for table alipay_direct_app -- ---------------------------- diff --git a/_config/sql/update-datas.sql b/_config/sql/update-datas.sql index 904b7c45e..e69de29bb 100644 --- a/_config/sql/update-datas.sql +++ b/_config/sql/update-datas.sql @@ -1,6 +0,0 @@ --- ============================ --- 修正 adapay 产品沙箱支持标志 --- 种子数据 pay_md_product.sandbox=false 与 AdapayDirectProductStrategy.isSandbox()=true 声明不一致, --- 导致通道商户列表"环境状态"列显示为"-"。ChannelMerchantService 已改为统一读取产品策略, 此处同步修正存量数据。 --- ============================ -UPDATE pay_md_product SET sandbox = true WHERE code = 'ada_pay'; diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index e69de29bb..4ab2ee3f4 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -0,0 +1,19 @@ +-- ======================================== +-- adapay_direct_key_config 直连密钥配置结构调整 +-- 开发阶段: Adapay 商户身份由 app_id + apiKey + RSA 私钥承载 +-- 无需独立商户号字段, 移除 merchant_no; 新增 sandbox 沙箱环境列与唯一索引 +-- ======================================== + +-- 1. 删除 Adapay 商户号列(身份由 app_id 承载; 该列旧版仅存商户身份快照, 直接删除) +ALTER TABLE "public"."adapay_direct_key_config" DROP COLUMN IF EXISTS "merchant_no"; + +-- 2. 新增沙箱环境列(默认非沙箱, 双环境并存: 同一通道商户同一环境密钥唯一) +ALTER TABLE "public"."adapay_direct_key_config" ADD COLUMN IF NOT EXISTS "sandbox" bool DEFAULT false; +COMMENT ON COLUMN "public"."adapay_direct_key_config"."sandbox" IS '是否沙箱环境'; + +-- 3. 唯一索引: 同一通道商户同一环境密钥唯一(存量数据存在重复时建索引会失败, 需先清理) +CREATE UNIQUE INDEX IF NOT EXISTS "uk_adapay_direct_key_config_sandbox" ON "public"."adapay_direct_key_config" USING btree ( + "channel_mch_no" "pg_catalog"."text_ops" ASC NULLS LAST, + "sandbox" "pg_catalog"."bool_ops" ASC NULLS LAST +) WHERE deleted = false; +COMMENT ON INDEX "public"."uk_adapay_direct_key_config_sandbox" IS '同一通道商户同一环境密钥唯一'; diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/credential/AdapaySdkCredential.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/credential/AdapaySdkCredential.java index 9dce1720f..a9ad090ff 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/credential/AdapaySdkCredential.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/credential/AdapaySdkCredential.java @@ -22,6 +22,9 @@ public class AdapaySdkCredential { /// Adapay 平台公钥(X509 Base64, 响应验签; 为空时子应用用全局默认公钥) private String publicKey; + /// 微信应用 AppId(微信 JSAPI/APP/小程序支付上送 wx_app_id, 其余场景为空) + private String wxAppId; + /// 是否沙箱环境 private Boolean sandbox; } diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/req/AdapayPayReq.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/req/AdapayPayReq.java index 5c0d8deca..38f712400 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/req/AdapayPayReq.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/client/req/AdapayPayReq.java @@ -26,6 +26,9 @@ public class AdapayPayReq { /// 用户标识(微信 openId / 支付宝 buyerId, JSAPI/小程序必填) private String openId; + /// 微信应用 AppId(微信 JSAPI/APP/小程序支付上送 wx_app_id) + private String wxAppId; + /// 付款码(条码支付必填) private String authCode; diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/controller/isv/AdapayIsvKeyConfigController.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/controller/isv/AdapayIsvKeyConfigController.java new file mode 100644 index 000000000..ec02e942a --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/controller/isv/AdapayIsvKeyConfigController.java @@ -0,0 +1,50 @@ +package cn.daxpay.open.channel.adapay.controller.isv; + +import cn.daxpay.open.channel.adapay.convert.isv.AdapayIsvKeyConfigConvert; +import cn.daxpay.open.channel.adapay.param.isv.AdapayIsvKeyConfigParam; +import cn.daxpay.open.channel.adapay.result.isv.AdapayIsvKeyConfigResult; +import cn.daxpay.open.channel.adapay.service.isv.AdapayIsvKeyConfigService; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +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; + +/// # Adapay 服务商密钥配置 +/// +/// 平台为唯一服务商, 同一环境(生产/沙箱)仅一条配置, 与通道商户直连密钥配置并存。 +@PermCode(menuCode = PermCodes.Payment.Isv.MENU) +@Validated +@Tag(name = "Adapay 服务商密钥配置") +@RestController +@RequestMapping("/admin/adapay/isv-key-config") +@RequiredArgsConstructor +public class AdapayIsvKeyConfigController { + + private final AdapayIsvKeyConfigService adapayIsvKeyConfigService; + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "查询Adapay 服务商密钥配置") + @GetMapping("/find-config") + public Result findConfig( + @NotNull(message = "{validation.field.sandbox.notNull}") Boolean sandbox) { + return Res.ok(AdapayIsvKeyConfigConvert.CONVERT.toResult(adapayIsvKeyConfigService.findBySandbox(sandbox))); + } + + @PermCode(code = PermCodes.Action.MANAGE) + @Operation(summary = "保存Adapay 服务商密钥配置") + @PostMapping("/save-config") + public Result saveConfig(@RequestBody @Validated AdapayIsvKeyConfigParam param) { + adapayIsvKeyConfigService.saveConfig(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/convert/isv/AdapayIsvKeyConfigConvert.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/convert/isv/AdapayIsvKeyConfigConvert.java new file mode 100644 index 000000000..d53347a01 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/convert/isv/AdapayIsvKeyConfigConvert.java @@ -0,0 +1,27 @@ +package cn.daxpay.open.channel.adapay.convert.isv; + +import cn.daxpay.open.channel.adapay.entity.isv.AdapayIsvKeyConfig; +import cn.daxpay.open.channel.adapay.param.isv.AdapayIsvKeyConfigParam; +import cn.daxpay.open.channel.adapay.result.isv.AdapayIsvKeyConfigResult; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # Adapay 服务商密钥配置转换 +@Mapper +public interface AdapayIsvKeyConfigConvert { + + AdapayIsvKeyConfigConvert CONVERT = Mappers.getMapper(AdapayIsvKeyConfigConvert.class); + + /// 转换为返回对象 + AdapayIsvKeyConfigResult toResult(AdapayIsvKeyConfig entity); + + /// 转换为实体 + AdapayIsvKeyConfig toEntity(AdapayIsvKeyConfigParam param); + + /// 更新源数据到实体(空值不覆盖, 密钥为空时保留原值) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void copy(AdapayIsvKeyConfigParam param, @MappingTarget AdapayIsvKeyConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigManager.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigManager.java new file mode 100644 index 000000000..4b31e96c8 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigManager.java @@ -0,0 +1,21 @@ +package cn.daxpay.open.channel.adapay.dao.isv; + +import cn.daxpay.open.channel.adapay.entity.isv.AdapayIsvKeyConfig; +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/// # Adapay 服务商密钥配置 Manager +@Slf4j +@Service +public class AdapayIsvKeyConfigManager extends BaseManager { + + /// 根据沙箱环境查询(平台为唯一服务商, 同一环境仅一条配置) + public Optional findBySandbox(boolean sandbox) { + return lambdaQuery() + .eq(AdapayIsvKeyConfig::getSandbox, sandbox) + .oneOpt(); + } +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigMapper.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigMapper.java new file mode 100644 index 000000000..590a79b37 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/dao/isv/AdapayIsvKeyConfigMapper.java @@ -0,0 +1,10 @@ +package cn.daxpay.open.channel.adapay.dao.isv; + +import cn.daxpay.open.channel.adapay.entity.isv.AdapayIsvKeyConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # Adapay 服务商密钥配置 Mapper +@Mapper +public interface AdapayIsvKeyConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/entity/direct/AdapayDirectKeyConfig.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/entity/direct/AdapayDirectKeyConfig.java index ebb2637af..19e553fd0 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/entity/direct/AdapayDirectKeyConfig.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/entity/direct/AdapayDirectKeyConfig.java @@ -17,6 +17,7 @@ import lombok.experimental.Accessors; /// 直连商户维度的通道配置, 含Adapay 支付应用 ID 与签名密钥, 敏感字段(apiKey/privateKey/publicKey)加密存储。 /// channelMchNo(通道商户号) 创建时录入不可修改, adapayAppId/apiKey/privateKey/publicKey 由密钥配置维护。 /// Adapay 签名算法: SHA1withRSA, 私钥签名 / 平台公钥验签, 无需证书。 +/// Adapay 商户身份由 app_id + apiKey(Authorization) + RSA 私钥签名承载, 无需单独的商户号字段。 @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) @@ -27,10 +28,6 @@ public class AdapayDirectKeyConfig extends MchBaseEntity implements ToResult { + + /// 服务商号(平台在汇付的服务商/主体编号) + private String isvNo; + + /// Adapay 交易密钥(请求头 Authorization, 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String apiKey; + + /// 商户 RSA 私钥(PKCS#8 Base64, 请求签名, 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String privateKey; + + /// Adapay 平台公钥(X509 Base64, 响应验签, 加密存储; 为空时使用全局默认公钥) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String publicKey; + + /// 是否沙箱环境 + private Boolean sandbox; + + /// 转换 + @Override + public AdapayIsvKeyConfigResult toResult() { + return AdapayIsvKeyConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/direct/AdapayDirectChannelMerchantCreateParam.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/direct/AdapayDirectChannelMerchantCreateParam.java index c30af0c49..bfaedeaef 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/direct/AdapayDirectChannelMerchantCreateParam.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/direct/AdapayDirectChannelMerchantCreateParam.java @@ -25,8 +25,4 @@ public class AdapayDirectChannelMerchantCreateParam { @NotBlank(message = "{validation.field.product.notBlank}") @Schema(description = "所属支付产品") private String product; - - @NotBlank(message = "{validation.field.merchantNo.notBlank}") - @Schema(description = "Adapay 商户号") - private String merchantNo; } diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/isv/AdapayIsvKeyConfigParam.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/isv/AdapayIsvKeyConfigParam.java new file mode 100644 index 000000000..98a06bed8 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/param/isv/AdapayIsvKeyConfigParam.java @@ -0,0 +1,31 @@ +package cn.daxpay.open.channel.adapay.param.isv; + +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; + +/// # Adapay 服务商密钥配置保存参数 +@Data +@Accessors(chain = true) +@Schema(title = "Adapay 服务商密钥配置保存参数") +public class AdapayIsvKeyConfigParam { + + @NotBlank(message = "{validation.field.isvNo.notBlank}") + @Schema(description = "服务商号(平台在汇付的服务商/主体编号)") + private String isvNo; + + @Schema(description = "Adapay 交易密钥(加密存储)") + private String apiKey; + + @Schema(description = "商户RSA私钥(PKCS#8 Base64, 加密存储)") + private String privateKey; + + @Schema(description = "Adapay 平台公钥(X509 Base64, 加密存储; 为空使用全局默认)") + private String publicKey; + + @NotNull(message = "{validation.field.sandbox.notNull}") + @Schema(description = "是否沙箱环境") + private Boolean sandbox; +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/direct/AdapayDirectKeyConfigResult.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/direct/AdapayDirectKeyConfigResult.java index de9a59798..b1de2b0cb 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/direct/AdapayDirectKeyConfigResult.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/direct/AdapayDirectKeyConfigResult.java @@ -15,9 +15,6 @@ public class AdapayDirectKeyConfigResult { /// 是否沙箱环境 private Boolean sandbox; - /// Adapay 商户号 - private String merchantNo; - /// Adapay 支付应用 ID private String adapayAppId; diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/isv/AdapayIsvKeyConfigResult.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/isv/AdapayIsvKeyConfigResult.java new file mode 100644 index 000000000..101807547 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/result/isv/AdapayIsvKeyConfigResult.java @@ -0,0 +1,39 @@ +package cn.daxpay.open.channel.adapay.result.isv; + +import cn.daxpay.open.platform.common.json.sensitive.SensitiveInfo; +import cn.daxpay.open.platform.core.result.BaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # Adapay 服务商密钥配置结果 +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "Adapay 服务商密钥配置") +public class AdapayIsvKeyConfigResult extends BaseResult { + + /// 服务商号(平台在汇付的服务商/主体编号) + @Schema(description = "服务商号") + private String isvNo; + + /// Adapay 交易密钥(脱敏返回) + @SensitiveInfo(front = 6, end = 6) + @Schema(description = "Adapay 交易密钥(脱敏返回)") + private String apiKey; + + /// 商户 RSA 私钥(脱敏返回) + @SensitiveInfo(front = 24, end = 24) + @Schema(description = "商户RSA私钥(脱敏返回)") + private String privateKey; + + /// Adapay 平台公钥(脱敏返回) + @SensitiveInfo(front = 24, end = 24) + @Schema(description = "Adapay 平台公钥(脱敏返回)") + private String publicKey; + + /// 是否沙箱环境 + @Schema(description = "是否沙箱环境") + private Boolean sandbox; +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectChannelMerchantService.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectChannelMerchantService.java index 88f3de7db..1a8758014 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectChannelMerchantService.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectChannelMerchantService.java @@ -53,10 +53,10 @@ public class AdapayDirectChannelMerchantService { boolean sandbox = payProductConfigManager.isSandboxActive(param.getProduct()); channelMerchant.setSandbox(sandbox); channelMerchantManager.save(channelMerchant); - // 写Adapay 直连配置(merchantNo 创建时录入不可修改, adapayAppId/apiKey/privateKey/publicKey 由密钥配置单独维护) + // 写Adapay 直连配置(adapayAppId/apiKey/privateKey/publicKey 由密钥配置单独维护) var keyConfig = new AdapayDirectKeyConfig() .setChannelMchNo(channelMchNo) - .setMerchantNo(param.getMerchantNo()); + .setSandbox(sandbox); keyConfig.setMchNo(param.getMchNo()); adapayDirectKeyConfigManager.save(keyConfig); } diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectConfigAssembler.java index daec818fc..9da619031 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/direct/AdapayDirectConfigAssembler.java @@ -2,8 +2,13 @@ package cn.daxpay.open.channel.adapay.service.direct; import cn.daxpay.open.channel.adapay.client.credential.AdapaySdkCredential; import cn.daxpay.open.channel.adapay.entity.direct.AdapayDirectKeyConfig; +import cn.daxpay.open.channel.adapay.strategy.product.AdapayDirectProductStrategy; import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager; import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant; +import cn.daxpay.open.payment.wx.facade.WxAppFacade; +import cn.daxpay.open.payment.wx.facade.WxAppView; +import cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum; +import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; import cn.daxpay.open.platform.core.exception.DataNotExistException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,7 +23,10 @@ import org.springframework.stereotype.Service; /// 沙箱标识直接读通道商户固化的 [ChannelMerchant#isSandbox] /// (创建时按当时产品 activeEnv 写入, 不随产品切换改变), 据此选择对应环境的密钥与网关地址。 /// -/// Adapay 为聚合支付, 不区分应用能力(capability 参数保留但不使用)。 +/// Adapay 为聚合支付, 不区分应用能力(capability 参数保留但不使用); +/// 微信 JSAPI/小程序支付需上送 wx_app_id, 通过 [WxAppFacade] 解析 +/// (商户档优先, 平台档兜底), 需要解析的能力范围由产品策略 +/// [AdapayDirectProductStrategy#wxAppRequiredCapabilities] 声明。 @Slf4j @Service @RequiredArgsConstructor @@ -26,12 +34,14 @@ public class AdapayDirectConfigAssembler { private final AdapayDirectKeyConfigService keyConfigService; private final ChannelMerchantManager channelMerchantManager; + private final WxAppFacade wxAppFacade; + private final AdapayDirectProductStrategy productStrategy; /// 组装直连商户的通道调用凭证(下发给子应用) /// /// @param mchNo 商户号(定位通用通道商户主表) /// @param channelMchNo 通道商户号(定位直连配置) - /// @param capability 支付能力编码(Adapay 不使用, 保留对齐签名) + /// @param capability 支付能力编码(产品策略声明需要绑定微信应用的能力时解析微信应用, 其余能力不使用) /// @return Adapay SDK 凭证 public AdapaySdkCredential buildConfig(String mchNo, String channelMchNo, String capability) { // 1. 通用通道商户主表(取 sandbox 固化标识) @@ -50,6 +60,14 @@ public class AdapayDirectConfigAssembler { credential.setPrivateKey(keyConfig.getPrivateKey()); credential.setPublicKey(keyConfig.getPublicKey()); credential.setSandbox(sandbox); + + // 5. 产品策略声明需要绑定微信应用的能力(JSAPI/小程序)需上送 wx_app_id, 解析商户/平台级微信应用 + // (sync/close/refund 场景 capability 为空, 跳过解析不影响凭证复用) + PayCapabilityEnum cap = PayCapabilityEnum.findByCode(capability); + if (cap != null && productStrategy.wxAppRequiredCapabilities().contains(cap)) { + WxAppView app = wxAppFacade.resolve(mchNo, channelMchNo, capability, null, ProductEnum.ADA_PAY.getCode()); + credential.setWxAppId(app.wxAppId()); + } return credential; } } diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/isv/AdapayIsvKeyConfigService.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/isv/AdapayIsvKeyConfigService.java new file mode 100644 index 000000000..ee08ef459 --- /dev/null +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/isv/AdapayIsvKeyConfigService.java @@ -0,0 +1,62 @@ +package cn.daxpay.open.channel.adapay.service.isv; + +import cn.daxpay.open.channel.adapay.convert.isv.AdapayIsvKeyConfigConvert; +import cn.daxpay.open.channel.adapay.dao.isv.AdapayIsvKeyConfigManager; +import cn.daxpay.open.channel.adapay.entity.isv.AdapayIsvKeyConfig; +import cn.daxpay.open.channel.adapay.param.isv.AdapayIsvKeyConfigParam; +import cn.daxpay.open.platform.core.exception.BizInfoException; +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; + +/// # Adapay 服务商密钥配置 +/// +/// 管理服务商密钥配置, 查询时不存在则创建默认记录(平台为唯一服务商, 同一环境仅一条)。 +/// 服务商配置与通道商户直连密钥配置([cn.daxpay.open.channel.adapay.entity.direct.AdapayDirectKeyConfig])并存, +/// 本配置承载平台服务商主体身份(服务商号/交易密钥/签名密钥), 供服务商台账与进件等场景使用。 +@Slf4j +@Service +@RequiredArgsConstructor +public class AdapayIsvKeyConfigService { + + private final AdapayIsvKeyConfigManager adapayIsvKeyConfigManager; + + /// 根据沙箱环境查询密钥配置, 不存在则创建默认记录 + /// + /// @param sandbox 沙箱标志(生产/沙箱双环境并存, 按环境分别存一份服务商配置) + @Transactional(rollbackFor = Exception.class) + public AdapayIsvKeyConfig findBySandbox(boolean sandbox) { + var existing = adapayIsvKeyConfigManager.findBySandbox(sandbox); + if (existing.isPresent()) { + return existing.get(); + } + var config = new AdapayIsvKeyConfig().setSandbox(sandbox); + adapayIsvKeyConfigManager.save(config); + return config; + } + + /// 保存服务商密钥配置(更新, 空值不覆盖) + @Transactional(rollbackFor = Exception.class) + public void saveConfig(AdapayIsvKeyConfigParam param) { + boolean sandbox = Boolean.TRUE.equals(param.getSandbox()); + var config = this.findBySandbox(sandbox); + AdapayIsvKeyConfigConvert.CONVERT.copy(param, config); + adapayIsvKeyConfigManager.updateById(config); + } + + /// 台账/进件场景查询服务商密钥(必填校验, 不创建记录) + /// + /// 记录不存在或关键字段(isvNo/apiKey/privateKey)任一为空时 fail-fast, + /// 避免空凭证下发到子应用后子应用才发现问题。 + public AdapayIsvKeyConfig getForPay(boolean sandbox) { + AdapayIsvKeyConfig config = adapayIsvKeyConfigManager.findBySandbox(sandbox) + // Adapay: 服务商密钥未配置 + .orElseThrow(() -> new BizInfoException("error.channel.adapay.isvKeyNotConfigured")); + if (StrUtil.hasBlank(config.getIsvNo(), config.getApiKey(), config.getPrivateKey())) { + throw new BizInfoException("error.channel.adapay.isvKeyNotConfigured"); + } + return config; + } +} diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/payment/pay/AdapayPayService.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/payment/pay/AdapayPayService.java index 506a21627..38d17a7ac 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/payment/pay/AdapayPayService.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/service/payment/pay/AdapayPayService.java @@ -49,6 +49,8 @@ public class AdapayPayService { req.setAuthCode(payParam.getAuthCode()); req.setClientIp(payParam.getClientIp()); req.setNotifyUrl(this.buildNotifyUrl(order, payParam.getChannelMchNo())); + // 微信 JSAPI/APP/小程序支付上送 wx_app_id(由配置组装阶段解析, 其余场景为空) + req.setWxAppId(credential.getWxAppId()); req.setCredential(credential); // 调用子应用 diff --git a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/strategy/product/AdapayDirectProductStrategy.java b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/strategy/product/AdapayDirectProductStrategy.java index 25d6fdaa6..8a2349834 100644 --- a/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/strategy/product/AdapayDirectProductStrategy.java +++ b/daxpay-channel/daxpay-channel-adapay/src/main/java/cn/daxpay/open/channel/adapay/strategy/product/AdapayDirectProductStrategy.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.Set; /// # Adapay 直连支付产品策略 /// @@ -88,4 +89,11 @@ public class AdapayDirectProductStrategy extends AbsProductStrategy { public Map> methodCapabilityMapping() { return METHOD_CAP_MAP; } + + /// Adapay 聚合通道: 微信侧仅 JSAPI/小程序支付需要 appid(上送 wx_app_id), + /// 其余微信能力(扫码/H5/APP/付款码)无需绑定应用, 收窄弹窗候选与支付链路的解析范围 + @Override + public Set wxAppRequiredCapabilities() { + return Set.of(PayCapabilityEnum.WECHAT_JSAPI, PayCapabilityEnum.WECHAT_MINI); + } } diff --git a/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/service/payment/LeshuaPayService.java b/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/service/payment/LeshuaPayService.java index b98bdef3e..d403f27d4 100644 --- a/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/service/payment/LeshuaPayService.java +++ b/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/service/payment/LeshuaPayService.java @@ -6,13 +6,18 @@ import cn.daxpay.open.channel.leshua.client.enums.LeshuaPayBodyType; import cn.daxpay.open.channel.leshua.client.enums.LeshuaPayMethod; import cn.daxpay.open.channel.leshua.client.req.LeshuaPayReq; import cn.daxpay.open.channel.leshua.client.resp.LeshuaPayResp; +import cn.daxpay.open.channel.leshua.strategy.product.LeshuaProductStrategy; import cn.daxpay.open.payment.common.result.DaxResult; +import cn.daxpay.open.payment.strategy.ProductStrategySupport; import cn.daxpay.open.payment.trade.runtime.bo.PayTradeResultBo; import cn.daxpay.open.payment.trade.order.entity.PayTrade; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam; +import cn.daxpay.open.payment.wx.facade.WxAppFacade; +import cn.daxpay.open.payment.wx.facade.WxAppView; import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.code.DaxPayErrorCode; import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum; +import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.system.service.config.infra.PlatformUrlConfigService; @@ -33,6 +38,8 @@ public class LeshuaPayService { private final LeshuaChannelClient leshuaChannelClient; private final PlatformUrlConfigService platformUrlConfigService; + private final WxAppFacade wxAppFacade; + private final LeshuaProductStrategy productStrategy; /// 执行乐刷支付 public PayTradeResultBo pay(PayTrade order, NormalPayParam payParam, LeshuaSdkCredential credential) { @@ -56,6 +63,15 @@ public class LeshuaPayService { req.setClientIp(payParam.getClientIp()); req.setNotifyUrl(this.buildNotifyUrl(order, payParam.getChannelMchNo())); req.setCredential(credential); + // 微信 JSAPI/小程序支付需 appid(乐刷 get_tdcode WXZF 场景, 拉起微信收银台); + // 通过微信开放应用门面解析商户/平台级微信应用(通道能力绑 → 产品级默认绑 → 平台应用推导), + // 付款码支付据 authCode 自动识别底层渠道, 无需 appid + if (isWechatJsapiOrMini(payParam.getMethod())) { + WxAppView wxApp = wxAppFacade.resolve( + order.getMchNo(), payParam.getChannelMchNo(), + payParam.getCapability(), payParam.getChannelAppId(), ProductEnum.LESHUA_PAY.getCode()); + req.setWxAppId(wxApp.wxAppId()); + } // 调用子应用 DaxResult result = leshuaChannelClient.pay(req); @@ -78,6 +94,14 @@ public class LeshuaPayService { base, order.getMchNo(), channelMchNo); } + /// 是否微信 JSAPI/小程序支付(乐刷 get_tdcode WXZF, 需要 appid 拉起微信收银台; + /// 判定范围由产品策略 [LeshuaProductStrategy#wxAppRequiredCapabilities] 声明) + private boolean isWechatJsapiOrMini(String methodCode) { + PayMethodEnum method = PayMethodEnum.findByCode(methodCode); + return ProductStrategySupport.capabilitiesForMethod(productStrategy, method).stream() + .anyMatch(productStrategy.wxAppRequiredCapabilities()::contains); + } + /// 平台支付方式([PayMethodEnum] code) → 乐刷三要素 private static MethodMapping mapMethod(String methodCode) { PayMethodEnum m = PayMethodEnum.findByCode(methodCode); diff --git a/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/strategy/product/LeshuaProductStrategy.java b/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/strategy/product/LeshuaProductStrategy.java index 51eca6801..38a8b9959 100644 --- a/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/strategy/product/LeshuaProductStrategy.java +++ b/daxpay-channel/daxpay-channel-leshua/src/main/java/cn/daxpay/open/channel/leshua/strategy/product/LeshuaProductStrategy.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.Set; /// # 乐刷支付产品策略 /// @@ -72,4 +73,11 @@ public class LeshuaProductStrategy extends AbsProductStrategy { public Map> methodCapabilityMapping() { return METHOD_CAP_MAP; } + + /// 乐刷聚合服务商: 微信侧仅 JSAPI/小程序支付需要 appid(拉起微信收银台), + /// 其余微信能力(扫码/付款码等)无需绑定应用, 收窄弹窗候选与支付链路的解析范围 + @Override + public Set wxAppRequiredCapabilities() { + return Set.of(PayCapabilityEnum.WECHAT_JSAPI, PayCapabilityEnum.WECHAT_MINI); + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/product/AbsProductStrategy.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/product/AbsProductStrategy.java index 667a6281d..b4eaf0384 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/product/AbsProductStrategy.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/strategy/product/AbsProductStrategy.java @@ -9,6 +9,7 @@ import cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum; import java.util.List; import java.util.Map; +import java.util.Set; /// # 产品策略抽象类 /// @@ -32,6 +33,14 @@ public abstract class AbsProductStrategy implements PaymentStrategy { /// 目录支付方式 → 本产品在该方式下可用的支付能力列表(发版契约,对齐 PayMethodEnum / PayCapabilityEnum) public abstract Map> methodCapabilityMapping(); + /// 需要绑定微信应用(解析 wxAppId)的支付能力集 + /// + /// 返回 null 表示该产品所有微信支付能力均可绑定应用(不限制); + /// 聚合类产品(如 Adapay/乐刷)仅部分微信能力需要 appid, 覆写返回收窄子集, + /// 弹窗候选([WxPlatformAppCapabilityService#listSupportedCapabilities])与 + /// 支付链路(通道 Assembler/PayService)统一读取此声明。 + public Set wxAppRequiredCapabilities() { return null; } + /// 基础模式通道路由可用的支付渠道列表(各产品策略必须显式声明) public abstract List supportedPayProviders(); } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java index 893852e1d..8d20421eb 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/WxAppResolveService.java @@ -2,16 +2,16 @@ package cn.daxpay.open.payment.wx.service; import cn.daxpay.open.payment.wx.dao.channel.WxChannelAppCapabilityManager; import cn.daxpay.open.payment.wx.dao.merchant.WxMchAppManager; +import cn.daxpay.open.payment.wx.dao.platform.WxPlatformAppCapabilityManager; import cn.daxpay.open.payment.wx.dao.platform.WxPlatformAppManager; import cn.daxpay.open.payment.auth.core.AppScopeEnum; import cn.daxpay.open.payment.wx.entity.merchant.WxMchApp; import cn.daxpay.open.payment.wx.entity.platform.WxPlatformApp; +import cn.daxpay.open.payment.wx.entity.platform.WxPlatformAppCapability; -import cn.daxpay.open.payment.wx.enums.WxAppTypeEnum; import cn.daxpay.open.payment.wx.facade.WxAppFacade; import cn.daxpay.open.payment.wx.facade.WxAppView; import cn.daxpay.open.payment.wx.facade.WxIsvAppPair; -import cn.daxpay.open.platform.common.i18n.util.I18nUtil; import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; @@ -21,7 +21,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.List; +import java.util.Optional; /// # 微信开放应用解析服务 /// @@ -35,6 +35,7 @@ public class WxAppResolveService implements WxAppFacade { private final WxPlatformAppManager wxPlatformAppManager; private final WxMchAppManager wxMchAppManager; private final WxChannelAppCapabilityManager wxChannelAppCapabilityManager; + private final WxPlatformAppCapabilityManager wxPlatformAppCapabilityManager; /// 按档位与主键加载应用视图(含 Auth) @Override @@ -59,7 +60,7 @@ public class WxAppResolveService implements WxAppFacade { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.payment.wx.scopeNotExist"); } - /// 解析单应用:显式 channelAppId → 通道能力绑 → appType 推导(平台应用唯一命中) + /// 解析单应用:显式 channelAppId → 通道能力绑 → 产品级平台默认绑 /// /// 直连产品(WECHAT_PAY)禁止回退到平台档应用: OAuth 拿到的 openId 必须与 /// 直连商户号同主体, 使用平台应用会导致 appid 与 mchid 主体不一致。 @@ -87,11 +88,11 @@ public class WxAppResolveService implements WxAppFacade { } } } - // 3. appType 推导:要求该类型平台应用唯一命中(仅非直连) + // 3. 产品级平台默认能力绑(wx_platform_app_capability 按 (product, capability) 取;仅非直连) if (!direct) { - WxAppView platformFallback = this.resolvePlatformFallback(capability); - if (platformFallback != null) { - return platformFallback; + WxAppView productDefault = this.resolveProductDefault(product, capability); + if (productDefault != null) { + return productDefault; } } // 微信: 直连商户未配置商户档应用 / 未配置该能力对应的应用 @@ -122,9 +123,9 @@ public class WxAppResolveService implements WxAppFacade { merchant = this.getById(AppScopeEnum.MERCHANT, merchantBind.get().getWxAppRefId()); } } - // 平台侧回退:appType 推导(平台应用唯一命中) + // 平台侧回退:产品级平台默认绑(按 product) if (platform == null) { - platform = this.resolvePlatformFallback(capability); + platform = this.resolveProductDefault(product, capability); } // channelAppId 命中某侧时按命中侧覆盖 if (StrUtil.isNotBlank(channelAppId)) { @@ -177,31 +178,22 @@ public class WxAppResolveService implements WxAppFacade { throw new DataNotExistException("error.payment.wx.channelAppIdNotFound", channelAppId); } - /// 平台档 appType 推导兜底:按兼容类型优先级遍历, 首个唯一命中的平台应用返回 + /// 产品级平台默认能力绑兜底:按 (product, capability) 查 wx_platform_app_capability /// - /// - 某类型恰好 1 个:返回该应用 - /// - 某类型多个:直接报错(不猜测, 要求显式配置能力绑定) - /// - 某类型无:跳过, 继续下一个兼容类型 - /// - 全部兼容类型均未唯一命中:返回 null, 由调用方走最终报错 - private WxAppView resolvePlatformFallback(String capability) { - if (StrUtil.isBlank(capability)) { + /// 供服务商/聚合类产品(如 leshua_pay)在通道商户未绑平台应用时, 回退到运营端配置的产品默认平台应用; + /// product 为空(如 UMS 传 null)或未配置绑定返回 null, 由调用方报错。 + private WxAppView resolveProductDefault(String product, String capability) { + if (StrUtil.hasBlank(product, capability)) { return null; } - // 遍历兼容 appType(按优先级), 首个唯一命中即返回; 某类型存在多个则报错, 避免猜测 - for (String appType : WxAppTypeEnum.resolveCompatibleAppTypes(capability)) { - List apps = wxPlatformAppManager.listByAppType(appType); - if (apps.size() == 1) { - return this.toPlatformView(apps.getFirst()); - } - if (apps.size() > 1) { - // 该类型存在多个平台应用, 要求显式配置能力绑定 - WxAppTypeEnum typeEnum = WxAppTypeEnum.findByCode(appType); - String appTypeLabel = typeEnum != null ? I18nUtil.getEnumName(typeEnum) : appType; - throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, - "error.payment.wx.appNotUnique", appTypeLabel); - } + Optional rel = wxPlatformAppCapabilityManager.findByProductAndCapability(product, capability); + if (rel.isEmpty()) { + return null; } - return null; + WxPlatformApp app = wxPlatformAppManager.findById(rel.get().getWxPlatformAppId()) + // 微信: 平台应用不存在 + .orElseThrow(() -> new DataNotExistException("error.payment.wx.appNotFound")); + return this.toPlatformView(app); } /// 平台应用 → View diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/platform/WxPlatformAppCapabilityService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/platform/WxPlatformAppCapabilityService.java index c0f70b59f..95cab34f7 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/platform/WxPlatformAppCapabilityService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/wx/service/platform/WxPlatformAppCapabilityService.java @@ -2,6 +2,8 @@ package cn.daxpay.open.payment.wx.service.platform; import cn.daxpay.open.payment.masterdata.dao.capability.PayProductCapabilityManager; import cn.daxpay.open.payment.masterdata.entity.product.PayProductCapability; +import cn.daxpay.open.payment.strategy.PaymentStrategyFactory; +import cn.daxpay.open.payment.strategy.product.AbsProductStrategy; import cn.daxpay.open.payment.wx.convert.platform.WxPlatformAppCapabilityConvert; import cn.daxpay.open.payment.wx.dao.platform.WxPlatformAppCapabilityManager; import cn.daxpay.open.payment.wx.dao.platform.WxPlatformAppManager; @@ -35,7 +37,7 @@ import java.util.stream.Collectors; /// # 平台微信应用默认能力绑定 /// /// 按支付产品管理「支付能力 → 平台微信应用」绑定。 -/// 支付时通过 [WxAppResolveService] 解析:通道绑 > 本产品默认绑 > appType 推导。 +/// 支付时通过 [WxAppResolveService] 解析:通道绑 > 本产品默认绑。 /// @Slf4j @Service @@ -117,13 +119,26 @@ public class WxPlatformAppCapabilityService { } /// 按支付产品查询可绑定的能力候选(pay_md_product_capability 白名单) + /// + /// 仅返回「微信相关能力」(WxAppTypeEnum 有应用类型映射的能力), 避免聚合类产品 + /// (如乐刷)把支付宝/云闪付等非微信能力带入微信应用绑定弹窗渲染空下拉; + /// 是否进一步收窄由产品策略声明决定([AbsProductStrategy#wxAppRequiredCapabilities]): + /// 返回 null 表示全部微信能力可绑定, 返回子集则仅子集可绑定(如乐刷/Adapay 聚合产品 + /// 微信侧仅 JSAPI/小程序支付需要 appid)。 public List listSupportedCapabilities(String product) { if (StrUtil.isBlank(product)) { return List.of(); } + // 产品策略声明: 需要绑定微信应用的能力子集(null 表示不限制, 全部微信能力可绑定) + Set wxAppRequired = PaymentStrategyFactory + .findOptionallyByProduct(product, AbsProductStrategy.class) + .map(AbsProductStrategy::wxAppRequiredCapabilities) + .orElse(null); return payProductCapabilityManager.listByProduct(product).stream() .map(PayProductCapability::getCapabilityCode) .filter(StrUtil::isNotBlank) + .filter(this::isWechatCapability) + .filter(code -> wxAppRequired == null || wxAppRequired.contains(PayCapabilityEnum.findByCode(code))) .distinct() .map(code -> { PayCapabilityEnum cap = PayCapabilityEnum.findByCode(code); @@ -133,6 +148,11 @@ public class WxPlatformAppCapabilityService { .toList(); } + /// 是否为微信相关能力(有微信应用类型映射的能力) + private boolean isWechatCapability(String capability) { + return !WxAppTypeEnum.resolveCompatibleAppTypes(capability).isEmpty(); + } + /// 填充关联结果的应用展示字段 private WxPlatformAppCapabilityResult fillResult(WxPlatformAppCapability rel, WxPlatformApp app) { WxPlatformAppCapabilityResult result = WxPlatformAppCapabilityConvert.CONVERT.toResult(rel); diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/adapay.json index 0d1139f01..85ba14e66 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Adapay payment error: {0}", "closeFailed": "Adapay close order error: {0}", + "isvKeyNotConfigured": "Adapay ISV key not configured", "unsupportedPayMethod": "Unsupported payment method for Adapay: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/validation.json index fafff6ccd..d8ed520cf 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/validation.json @@ -679,6 +679,9 @@ "dougongAppId": { "notBlank": "Merchant AppId cannot be blank" }, + "isvNo": { + "notBlank": "ISV number cannot be blank" + }, "sandAppId": { "notBlank": "Sand appId cannot be blank" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/adapay.json index 8c7e2be58..fc9608c73 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Kesalahan pembayaran Adapay: {0}", "closeFailed": "Kesalahan menutup pesanan Adapay: {0}", + "isvKeyNotConfigured": "Kunci ISV Adapay belum dikonfigurasi", "unsupportedPayMethod": "Metode pembayaran tidak didukung untuk Adapay: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/validation.json index 4b96c505c..fd627b4fa 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "AppId Penjual tidak boleh kosong" }, + "isvNo": { + "notBlank": "Nomor ISV wajib diisi" + }, "sandAppId": { "notBlank": "Sand appId tidak boleh kosong" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/adapay.json index 82b1cfecd..8fd2dc8b1 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Adapay支払エラー: {0}", "closeFailed": "Adapay注文クローズエラー: {0}", + "isvKeyNotConfigured": "Adapay サービス事業者キーが設定されていません", "unsupportedPayMethod": "Adapayでサポートされていない支払方法: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/validation.json index 591113e50..8e90ee823 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "販売者の AppId を空白にすることはできません" }, + "isvNo": { + "notBlank": "サービス事業者番号は必須です" + }, "sandAppId": { "notBlank": "Sand appIdは必須です" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/adapay.json index a23f91eaa..bb058f824 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Adapay 결제 오류: {0}", "closeFailed": "Adapay 주문 종료 오류: {0}", + "isvKeyNotConfigured": "Adapay ISV 키가 설정되지 않았습니다", "unsupportedPayMethod": "Adapay에서 지원하지 않는 결제 방식: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/validation.json index 83a89bf08..b3d72bc8e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "판매자 앱 ID는 비워둘 수 없습니다." }, + "isvNo": { + "notBlank": "ISV 번호는 필수입니다" + }, "sandAppId": { "notBlank": "Sand appId는 필수입니다" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/adapay.json index 4e50a2362..80a8954cc 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Ralat pembayaran Adapay: {0}", "closeFailed": "Ralat tutup pesanan Adapay: {0}", + "isvKeyNotConfigured": "Kunci ISV Adapay belum dikonfigurasi", "unsupportedPayMethod": "Kaedah pembayaran tidak disokong untuk Adapay: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/validation.json index 14399502a..61939eff8 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "Merchant AppId tidak boleh kosong" }, + "isvNo": { + "notBlank": "Nombor ISV wajib diisi" + }, "sandAppId": { "notBlank": "Sand appId tidak boleh kosong" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/adapay.json index 335183e85..f9ef319f5 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "ข้อผิดพลาดการชำระเงิน Adapay: {0}", "closeFailed": "ข้อผิดพลาดการปิดคำสั่ง Adapay: {0}", + "isvKeyNotConfigured": "ยังไม่ได้กำหนดค่าคีย์ ISV ของ Adapay", "unsupportedPayMethod": "วิธีการชำระเงินไม่ได้รับการสนับสนุนสำหรับ Adapay: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/validation.json index 7c85b951f..7620c945b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "Merchant AppId ไม่สามารถเว้นว่างได้" }, + "isvNo": { + "notBlank": "ต้องระบุหมายเลข ISV" + }, "sandAppId": { "notBlank": "Sand appId ต้องไม่ว่าง" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/adapay.json index 4108b1ddd..f5598589f 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "Lỗi thanh toán Adapay: {0}", "closeFailed": "Lỗi đóng đơn hàng Adapay: {0}", + "isvKeyNotConfigured": "Chưa cấu hình khóa ISV Adapay", "unsupportedPayMethod": "Phương thức thanh toán không được hỗ trợ cho Adapay: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/validation.json index 3a62d1513..9af8c7871 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "AppId người bán không được để trống" }, + "isvNo": { + "notBlank": "Số ISV không được để trống" + }, "sandAppId": { "notBlank": "Sand appId không được để trống" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/adapay.json index 316fc431b..b5ebac39e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "汇付支付异常: {0}", "closeFailed": "汇付关闭订单异常: {0}", + "isvKeyNotConfigured": "汇付服务商密钥未配置", "unsupportedPayMethod": "汇付不支持的支付方式: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/validation.json index b1d9aa4e7..4c7fdc8a7 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/validation.json @@ -679,6 +679,9 @@ "dougongAppId": { "notBlank": "商户AppId不可为空" }, + "isvNo": { + "notBlank": "服务商号不可为空" + }, "sandAppId": { "notBlank": "杉德 appId 不能为空" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/adapay.json index 26da83821..c048c897f 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "匯付支付異常: {0}", "closeFailed": "匯付關閉訂單異常: {0}", + "isvKeyNotConfigured": "匯付服務商密鑰未設定", "unsupportedPayMethod": "匯付不支援的支付方式: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/validation.json index be21a8eb2..25bc9fa2a 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "商户AppId不可為空" }, + "isvNo": { + "notBlank": "服務商號不可為空" + }, "sandAppId": { "notBlank": "杉德 appId 不能為空" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/adapay.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/adapay.json index 26da83821..c048c897f 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/adapay.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/adapay.json @@ -1,5 +1,6 @@ { "payFailed": "匯付支付異常: {0}", "closeFailed": "匯付關閉訂單異常: {0}", + "isvKeyNotConfigured": "匯付服務商密鑰未設定", "unsupportedPayMethod": "匯付不支援的支付方式: {0}" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/validation.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/validation.json index 43f6351f8..0fdfceabd 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/validation.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/validation.json @@ -675,6 +675,9 @@ "dougongAppId": { "notBlank": "商戶AppId不可為空" }, + "isvNo": { + "notBlank": "服務商號不可為空" + }, "sandAppId": { "notBlank": "杉德 appId 不能為空" },