diff --git a/_config/sql/iam_perm_menu.sql b/_config/sql/iam_perm_menu.sql index f93009fda..69225452e 100644 --- a/_config/sql/iam_perm_menu.sql +++ b/_config/sql/iam_perm_menu.sql @@ -12,7 +12,7 @@ Target Server Version : 160014 (160014) File Encoding : 65001 - Date: 03/07/2026 18:32:43 + Date: 05/07/2026 10:35:00 */ @@ -155,6 +155,7 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040116, 401, 'payment:platform:pro INSERT INTO "public"."iam_perm_menu" VALUES (309, 308, 'system:notify:notice', 'admin', 'SystemNotify', '通知公告', 'Notification', 'menu.system.notify.notice', 'lucide:megaphone', 'f', 'f', '/system/notify/notice/NoticeList', '/system/notify/notice', NULL, 20, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, '2026-06-24 16:00:00+00', '2026-06-24 16:00:00+00', NULL); INSERT INTO "public"."iam_perm_menu" VALUES (308, 3, 'system:notify', 'admin', 'SystemNotice', '通知中心', 'Notification Center', 'menu.system.notify', 'lucide:bell', 'f', 'f', NULL, '/system/notify', '/system/notify/notice', 20, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-01 07:16:30.927645+00', '2026-07-01 07:16:30.927645+00'); INSERT INTO "public"."iam_perm_menu" VALUES (603, 6, 'payment:refund', 'admin', 'RefundOrderList', '退款订单', 'Refund Orders', 'menu.trade.refundOrder', 'lucide:rotate-ccw', 'f', 'f', '/payment/order/RefundOrderList', '/trade/refund-order', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-03 16:00:00+00', '2026-07-03 16:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (4040119, 40401, 'channel:wechat:app', 'admin', 'WechatIsvMchAppManage', '微信服务商通道商户应用', 'WeChat ISV Channel Merchant App', 'menu.payment.merchant.channelMerchant.wechatIsvMchApp', NULL, 't', 'f', '/payment/channel/wechat/manage/mch/isv-app/WechatIsvMchAppManage', '/payment/merchant/channel-merchant/wechat-isv-mch-app-manage', NULL, 14, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-05 01:49:32.557374+00', '2026-07-05 01:49:32.557374+00'); -- ---------------------------- -- Primary Key structure for table iam_perm_menu diff --git a/_config/sql/update-datas.sql b/_config/sql/update-datas.sql index 1ba9fc8f6..a9c13f60e 100644 --- a/_config/sql/update-datas.sql +++ b/_config/sql/update-datas.sql @@ -3,3 +3,44 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040118, 40401, 'merchant:notify_co -- 退款订单: 菜单(权限码 payment:refund) INSERT INTO "public"."iam_perm_menu" VALUES (603, 6, 'payment:refund', 'admin', 'RefundOrderList', '退款订单', 'Refund Orders', 'menu.trade.refundOrder', 'lucide:rotate-ccw', 'f', 'f', '/payment/order/RefundOrderList', '/trade/refund-order', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-03 16:00:00+00', '2026-07-03 16:00:00+00'); + +-- 微信服务商通道商户应用(子商户应用): 菜单(权限码 channel:wechat:app) +INSERT INTO "public"."iam_perm_menu" VALUES (4040119, 40401, 'channel:wechat:app', 'admin', 'WechatIsvMchAppManage', '微信服务商通道商户应用', 'WeChat ISV Channel Merchant App', 'menu.payment.merchant.channelMerchant.wechatIsvMchApp', NULL, 't', 'f', '/payment/channel/wechat/manage/mch/isv-app/WechatIsvMchAppManage', '/payment/merchant/channel-merchant/wechat-isv-mch-app-manage', NULL, 14, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-05 00:00:00+00', '2026-07-05 00:00:00+00'); + +-- ============================================================ +-- 菜单标题与 i18n_key 统一重命名 +-- ============================================================ + +-- 支付产品详情 → 产品配置详情 +UPDATE "public"."iam_perm_menu" SET title_cn='产品配置详情', title_en='Product Config Detail', + i18n_key='menu.payment.config.detail' WHERE id=40501; +-- 支付宝服务商应用: i18n_key 归入 config.* +UPDATE "public"."iam_perm_menu" SET i18n_key='menu.payment.config.alipayApp' WHERE id=40502; +-- 微信服务商应用: i18n_key 归入 config.* +UPDATE "public"."iam_perm_menu" SET i18n_key='menu.payment.config.wechatApp' WHERE id=40503; + +-- 商户信息(列表入口) → 商户列表 +UPDATE "public"."iam_perm_menu" SET title_cn='商户列表', title_en='Merchant List', + i18n_key='menu.payment.merchant.list' WHERE id=40401; +-- 商户管理(工作台) → 商户工作台 +UPDATE "public"."iam_perm_menu" SET title_cn='商户工作台', title_en='Merchant Workbench', + i18n_key='menu.payment.merchant.workbench' WHERE id=4040101; +-- 对接配置: i18n_key 去掉中间 manage 层 +UPDATE "public"."iam_perm_menu" SET i18n_key='menu.payment.merchant.credential' WHERE id=4040102; +-- 商户信息(详情) → 商户资料 +UPDATE "public"."iam_perm_menu" SET title_cn='商户资料', title_en='Merchant Profile', + i18n_key='menu.payment.merchant.profile' WHERE id=4040103; +-- 通道路由: i18n_key 从 app.* 移出(实为商户级配置) +UPDATE "public"."iam_perm_menu" SET i18n_key='menu.payment.merchant.payRoute' WHERE id=4040111; +-- 通道商户详情 → 通道商户管理(静态通用名) +UPDATE "public"."iam_perm_menu" SET title_cn='通道商户管理', title_en='Channel Merchant Management' + WHERE id=4040112; +-- 微信通道商户应用 → 微信应用 +UPDATE "public"."iam_perm_menu" SET title_cn='微信应用', title_en='WeChat App' WHERE id=4040113; +-- 支付宝通道商户应用 → 支付宝应用 +UPDATE "public"."iam_perm_menu" SET title_cn='支付宝应用', title_en='Alipay App' WHERE id=4040114; +-- 抖音通道商户应用 → 抖音应用 +UPDATE "public"."iam_perm_menu" SET title_cn='抖音应用', title_en='Douyin App' WHERE id=4040115; +-- 微信服务商通道商户应用 → 微信服务商子商户应用 +UPDATE "public"."iam_perm_menu" SET title_cn='微信服务商子商户应用', title_en='WeChat ISV Sub-merchant App', + i18n_key='menu.payment.merchant.channelMerchant.wechatIsvApp' WHERE id=4040119; diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index 037d3a2fa..0cfe40024 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -29,3 +29,101 @@ COMMENT ON COLUMN "public"."iam_user_dashboard_preference"."deleted" IS '逻辑 CREATE UNIQUE INDEX IF NOT EXISTS "uk_user_das_pref_user_client" ON "public"."iam_user_dashboard_preference" ("user_id", "client_code") WHERE "deleted" = false; + +-- ============================================================ +-- 微信服务商子商户应用 / 能力绑定 / 授权配置 +-- ============================================================ + +-- 微信服务商通道商户应用(子商户自己的微信应用, channelMchNo 维度) +CREATE TABLE IF NOT EXISTS "public"."wechat_isv_mch_app" ( + "id" bigint NOT NULL PRIMARY KEY, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "app_name" varchar(128) NOT NULL, + "app_type" varchar(32) NOT NULL, + "wx_app_id" varchar(64) NOT NULL, + "creator" bigint, + "create_time" timestamptz(6), + "last_modifier" bigint, + "last_modified_time" timestamptz(6), + "version" integer NOT NULL DEFAULT 0, + "deleted" boolean NOT NULL DEFAULT false +); +COMMENT ON TABLE "public"."wechat_isv_mch_app" IS '微信服务商通道商户应用(子商户自己的微信应用)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."id" IS '主键'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."mch_no" IS '商户号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."channel_mch_no" IS '通道商户号(服务商特约商户)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."app_name" IS '应用名称'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."app_type" IS '应用类型(official_account/mini_program/mobile_app)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."wx_app_id" IS '微信应用AppId(对应微信支付sub_appid)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."creator" IS '创建人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."create_time" IS '创建时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."last_modifier" IS '最后修改人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."last_modified_time" IS '最后修改时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."version" IS '乐观锁版本号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app"."deleted" IS '逻辑删除标志'; +CREATE UNIQUE INDEX IF NOT EXISTS "uk_wechat_isv_mch_app_channel_wxappid" + ON "public"."wechat_isv_mch_app" ("channel_mch_no", "wx_app_id") + WHERE "deleted" = false; + +-- 微信服务商通道商户应用支付能力关联(channelMchNo 维度, 仅存子商户显式选自己应用的记录) +CREATE TABLE IF NOT EXISTS "public"."wechat_isv_mch_app_capability" ( + "id" bigint NOT NULL PRIMARY KEY, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "capability" varchar(64) NOT NULL, + "wechat_isv_mch_app_id" bigint NOT NULL, + "creator" bigint, + "create_time" timestamptz(6), + "last_modifier" bigint, + "last_modified_time" timestamptz(6), + "version" integer NOT NULL DEFAULT 0, + "deleted" boolean NOT NULL DEFAULT false +); +COMMENT ON TABLE "public"."wechat_isv_mch_app_capability" IS '微信服务商通道商户应用支付能力关联'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."id" IS '主键'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."mch_no" IS '商户号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."channel_mch_no" IS '通道商户号(服务商特约商户)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."capability" IS '支付能力编码'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."wechat_isv_mch_app_id" IS '关联服务商通道商户应用ID(指向wechat_isv_mch_app.id)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."creator" IS '创建人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."create_time" IS '创建时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."last_modifier" IS '最后修改人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."last_modified_time" IS '最后修改时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."version" IS '乐观锁版本号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_capability"."deleted" IS '逻辑删除标志'; +CREATE UNIQUE INDEX IF NOT EXISTS "uk_wechat_isv_mch_app_cap_channel_cap" + ON "public"."wechat_isv_mch_app_capability" ("channel_mch_no", "capability") + WHERE "deleted" = false; + +-- 微信服务商通道商户应用授权配置(appSecret等, 一对一) +CREATE TABLE IF NOT EXISTS "public"."wechat_isv_mch_app_auth_config" ( + "id" bigint NOT NULL PRIMARY KEY, + "mch_no" varchar(32) NOT NULL, + "channel_mch_no" varchar(64) NOT NULL, + "wechat_isv_mch_app_id" bigint NOT NULL, + "app_secret" varchar(512), + "auth_callback_url" varchar(512), + "creator" bigint, + "create_time" timestamptz(6), + "last_modifier" bigint, + "last_modified_time" timestamptz(6), + "version" integer NOT NULL DEFAULT 0, + "deleted" boolean NOT NULL DEFAULT false +); +COMMENT ON TABLE "public"."wechat_isv_mch_app_auth_config" IS '微信服务商通道商户应用授权配置'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."id" IS '主键'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."mch_no" IS '商户号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."channel_mch_no" IS '通道商户号(服务商特约商户)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."wechat_isv_mch_app_id" IS '关联服务商通道商户应用ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."app_secret" IS '应用密钥(加密存储)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."auth_callback_url" IS '授权回调地址(仅公众号)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."creator" IS '创建人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."create_time" IS '创建时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."last_modifier" IS '最后修改人ID'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."last_modified_time" IS '最后修改时间(UTC)'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."version" IS '乐观锁版本号'; +COMMENT ON COLUMN "public"."wechat_isv_mch_app_auth_config"."deleted" IS '逻辑删除标志'; +CREATE UNIQUE INDEX IF NOT EXISTS "uk_wechat_isv_mch_app_auth_channel_app" + ON "public"."wechat_isv_mch_app_auth_config" ("channel_mch_no", "wechat_isv_mch_app_id") + WHERE "deleted" = false; diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/payment/pay/AlipayPayService.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/payment/pay/AlipayPayService.java index 0b5c44086..0e101df3a 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/payment/pay/AlipayPayService.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/payment/pay/AlipayPayService.java @@ -15,6 +15,7 @@ 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.unipay.PayBodyTypeEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.daxpay.open.platform.system.service.config.PlatformUrlConfigService; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,6 +31,7 @@ import org.springframework.stereotype.Service; public class AlipayPayService { private final AlipayChannelClient alipayChannelClient; + private final PlatformUrlConfigService platformUrlConfigService; /// 执行支付宝支付 /// @@ -50,6 +52,8 @@ public class AlipayPayService { // 付款码(BARCODE) / 买家标识(JSAPI) 通道专属参数透传 req.setAuthCode(payParam.getAuthCode()); req.setOpenId(payParam.getOpenId()); + // 通道通知地址: 始终使用平台生成的回调地址(支付宝→平台), 不使用 payParam.notifyUrl(语义为平台→商户) + req.setNotifyUrl(this.buildNotifyUrl(order)); req.setCredential(credential); // 调用子应用 @@ -61,6 +65,21 @@ public class AlipayPayService { return toPayResult(result.getData()); } + /// 生成支付宝支付异步通知地址(支付宝→平台) + /// + /// 沿用商业版旧版路径约定: `{backendBaseUrl}/unipay/callback/{mchNo}/{appId}/alipay` + /// backendBaseUrl 来自平台端点配置 [PlatformUrlConfig], 与社交登录回调地址同源 + /// 注: 支付宝支付/退款共用同一回调端点(旧版设计), 无 /pay 后缀, 与微信路径不同 + private String buildNotifyUrl(PayTrade order) { + String base = platformUrlConfigService.getUrlConfig().getBackendBaseUrl(); + if (StrUtil.isBlank(base)) { + // backendBaseUrl 未配置时抛清晰异常, 避免 null 透传到支付宝导致无异步回调 + throw new IllegalStateException("平台后端访问地址(backendBaseUrl)未配置, 无法生成支付宝回调地址"); + } + return StrUtil.format("{}/unipay/callback/{}/{}/alipay", + base, order.getMchNo(), order.getAppId()); + } + /// 平台支付方式([PayMethodEnum] code) -> 支付宝通道支付方式 private static AlipayPayMethod mapMethod(String methodCode) { PayMethodEnum m = PayMethodEnum.findByCode(methodCode); diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvAppController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvAppController.java index 852dad50f..87f6a0e3e 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvAppController.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvAppController.java @@ -97,7 +97,7 @@ public class WechatIsvAppController { @Operation(summary = "查询应用授权认证配置") @GetMapping("/find-auth-config-by-app-id") public Result findAuthConfigByAppId( - @NotNull(message = "{validation.field.id.notNull}") Long wechatIsvAppId) { + @NotNull(message = "{validation.field.wechatIsvAppId.notNull}") Long wechatIsvAppId) { var config = wechatIsvAppAuthConfigService.findByWechatIsvAppId(wechatIsvAppId); return Res.ok(WechatIsvAppAuthConfigConvert.CONVERT.toResult(config)); } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppCapabilityController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppCapabilityController.java new file mode 100644 index 000000000..2407499d3 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppCapabilityController.java @@ -0,0 +1,60 @@ +package cn.daxpay.open.channel.wechat.controller.isv; + +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppCapabilityBatchParam; +import cn.daxpay.open.channel.wechat.result.WechatCapabilityOption; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppCapabilityResult; +import cn.daxpay.open.channel.wechat.service.isv.WechatIsvMchAppCapabilityService; +import cn.daxpay.open.platform.core.annotation.PermCode; +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.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// # 微信服务商通道商户应用支付能力关联管理 +/// +/// 提供通道商户(特约商户)维度下「支付能力 → 子商户应用」绑定关系的查询、批量保存及能力候选查询。 +/// +@PermCode(menuCode = "channel:wechat:app") +@Validated +@Tag(name = "微信服务商通道商户应用支付能力关联管理") +@RestController +@RequestMapping("/admin/wechat/isv-mch-app/capability") +@RequiredArgsConstructor +public class WechatIsvMchAppCapabilityController { + + private final WechatIsvMchAppCapabilityService wechatIsvMchAppCapabilityService; + + @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(wechatIsvMchAppCapabilityService.listByChannelMchNo(mchNo, channelMchNo)); + } + + @PermCode(code = "manage", nameCn = "通道商户管理", nameEn = "Channel Merchant Manage") + @Operation(summary = "全量保存能力应用关联") + @PostMapping("/save-batch") + public Result saveBatch(@RequestBody @Validated WechatIsvMchAppCapabilityBatchParam param) { + wechatIsvMchAppCapabilityService.saveBatch(param); + return Res.ok(); + } + + @PermCode(code = "view", nameCn = "通道商户查看", nameEn = "Channel Merchant View") + @Operation(summary = "查询微信服务商支持的支付能力候选") + @GetMapping("/list-supported-capabilities") + public Result> listSupportedCapabilities() { + return Res.ok(wechatIsvMchAppCapabilityService.listSupportedCapabilities()); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppController.java new file mode 100644 index 000000000..4180804db --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/isv/WechatIsvMchAppController.java @@ -0,0 +1,123 @@ +package cn.daxpay.open.channel.wechat.controller.isv; + +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppParam; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppAuthConfigParam; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppAuthConfigResult; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppResult; +import cn.daxpay.open.channel.wechat.service.isv.WechatIsvMchAppAuthConfigService; +import cn.daxpay.open.channel.wechat.service.isv.WechatIsvMchAppService; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import cn.daxpay.open.platform.core.util.ValidationUtil; +import cn.daxpay.open.platform.core.validation.ValidationGroup; +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 = "channel:wechat:app") +@Validated +@Tag(name = "微信服务商通道商户应用管理") +@RestController +@RequestMapping("/admin/wechat/isv-mch-app") +@RequiredArgsConstructor +public class WechatIsvMchAppController { + + private final WechatIsvMchAppService wechatIsvMchAppService; + private final WechatIsvMchAppAuthConfigService wechatIsvMchAppAuthConfigService; + + @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(wechatIsvMchAppService.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(wechatIsvMchAppService.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(wechatIsvMchAppService.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(wechatIsvMchAppService.existsWxAppIdByChannel(mchNo, channelMchNo, wxAppId, id)); + } + + @PermCode(code = "manage", nameCn = "通道商户管理", nameEn = "Channel Merchant Manage") + @Operation(summary = "新增服务商通道商户应用") + @PostMapping("/add") + public Result add(@RequestBody @Validated(ValidationGroup.add.class) WechatIsvMchAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.add.class); + wechatIsvMchAppService.add(param); + return Res.ok(); + } + + @PermCode(code = "manage", nameCn = "通道商户管理", nameEn = "Channel Merchant Manage") + @Operation(summary = "修改服务商通道商户应用") + @PostMapping("/update") + public Result update(@RequestBody @Validated(ValidationGroup.edit.class) WechatIsvMchAppParam param) { + ValidationUtil.validateParam(param, ValidationGroup.edit.class); + wechatIsvMchAppService.update(param); + return Res.ok(); + } + + @PermCode(code = "manage", nameCn = "通道商户管理", nameEn = "Channel Merchant Manage") + @Operation(summary = "删除服务商通道商户应用") + @PostMapping("/delete") + public Result delete(@NotNull(message = "{validation.field.id.notNull}") Long id) { + wechatIsvMchAppService.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.wechatIsvMchAppId.notNull}") Long wechatIsvMchAppId) { + var config = wechatIsvMchAppAuthConfigService.findByWechatIsvMchAppId(wechatIsvMchAppId); + return Res.ok(config.toResult()); + } + + @PermCode(code = "manage", nameCn = "通道商户管理", nameEn = "Channel Merchant Manage") + @Operation(summary = "保存应用授权认证配置") + @PostMapping("/save-auth-config") + public Result saveAuthConfig(@RequestBody @Validated WechatIsvMchAppAuthConfigParam param) { + wechatIsvMchAppAuthConfigService.save(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppAuthConfigConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppAuthConfigConvert.java new file mode 100644 index 000000000..03c12c7df --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppAuthConfigConvert.java @@ -0,0 +1,30 @@ +package cn.daxpay.open.channel.wechat.convert.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppAuthConfig; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppAuthConfigParam; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppAuthConfigResult; +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 WechatIsvMchAppAuthConfigConvert { + + WechatIsvMchAppAuthConfigConvert CONVERT = Mappers.getMapper(WechatIsvMchAppAuthConfigConvert.class); + + /// 转换为返回对象 + WechatIsvMchAppAuthConfigResult toResult(WechatIsvMchAppAuthConfig entity); + + /// 转换为实体 + WechatIsvMchAppAuthConfig toEntity(WechatIsvMchAppAuthConfigParam param); + + /// 更新源数据到实体(空值不覆盖) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void copy(WechatIsvMchAppAuthConfigParam param, @MappingTarget WechatIsvMchAppAuthConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppCapabilityConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppCapabilityConvert.java new file mode 100644 index 000000000..b847585dc --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppCapabilityConvert.java @@ -0,0 +1,19 @@ +package cn.daxpay.open.channel.wechat.convert.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppCapability; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppCapabilityResult; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/// # 微信服务商通道商户应用支付能力关联转换 +/// +/// MapStruct转换器,负责关联记录在实体与返回结果之间的转换。 +/// +@Mapper +public interface WechatIsvMchAppCapabilityConvert { + + WechatIsvMchAppCapabilityConvert CONVERT = Mappers.getMapper(WechatIsvMchAppCapabilityConvert.class); + + /// 转换为返回对象 + WechatIsvMchAppCapabilityResult toResult(WechatIsvMchAppCapability entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppConvert.java new file mode 100644 index 000000000..12229bf9c --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/isv/WechatIsvMchAppConvert.java @@ -0,0 +1,27 @@ +package cn.daxpay.open.channel.wechat.convert.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppParam; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppResult; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/// # 微信服务商通道商户应用转换 +/// +/// MapStruct转换器,负责微信服务商通道商户应用在实体、参数和返回结果之间的相互转换。 +/// +@Mapper +public interface WechatIsvMchAppConvert { + + WechatIsvMchAppConvert CONVERT = Mappers.getMapper(WechatIsvMchAppConvert.class); + + /// 转换为返回对象 + WechatIsvMchAppResult toResult(WechatIsvMchApp entity); + + /// 转换为实体 + WechatIsvMchApp toEntity(WechatIsvMchAppParam param); + + /// 更新源数据到实体(忽略空值) + void copy(WechatIsvMchAppParam param, @MappingTarget WechatIsvMchApp entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java new file mode 100644 index 000000000..2c565c71f --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigManager.java @@ -0,0 +1,29 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppAuthConfig; +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 微信服务商通道商户应用授权认证配置 Manager +/// +/// 服务商通道商户应用授权认证配置数据访问管理器,提供按应用ID查询和删除授权认证配置的方法。 +/// +@Repository +public class WechatIsvMchAppAuthConfigManager extends BaseManager { + + /// 根据应用ID查询授权认证配置 + public Optional findByWechatIsvMchAppId(Long wechatIsvMchAppId) { + return lambdaQuery() + .eq(WechatIsvMchAppAuthConfig::getWechatIsvMchAppId, wechatIsvMchAppId) + .oneOpt(); + } + + /// 根据应用ID删除授权认证配置 + public void deleteByWechatIsvMchAppId(Long wechatIsvMchAppId) { + lambdaUpdate() + .eq(WechatIsvMchAppAuthConfig::getWechatIsvMchAppId, wechatIsvMchAppId) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigMapper.java new file mode 100644 index 000000000..6c59335fc --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppAuthConfigMapper.java @@ -0,0 +1,13 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppAuthConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信服务商通道商户应用授权认证配置 +/// +/// 微信服务商通道商户应用授权认证配置 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatIsvMchAppAuthConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java new file mode 100644 index 000000000..3df3d6738 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityManager.java @@ -0,0 +1,51 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppCapability; +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +/// # 微信服务商通道商户应用支付能力关联 Manager +/// +/// 关联记录数据访问管理器,提供按通道商户号查询/删除、按能力查应用、按应用清理等方法。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatIsvMchAppCapabilityManager extends BaseManager { + + /// 根据通道商户号查询全部关联(按创建时间升序) + public List listByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(WechatIsvMchAppCapability::getChannelMchNo, channelMchNo) + .orderByAsc(WechatIsvMchAppCapability::getCreateTime) + .orderByAsc(WechatIsvMchAppCapability::getId) + .list(); + } + + /// 根据通道商户号与支付能力查询单条关联(支付时调用) + public Optional findOne(String channelMchNo, String capability) { + return lambdaQuery() + .eq(WechatIsvMchAppCapability::getChannelMchNo, channelMchNo) + .eq(WechatIsvMchAppCapability::getCapability, capability) + .oneOpt(); + } + + /// 根据通道商户号删除全部关联(批量保存时先清后插) + public void deleteByChannelMchNo(String channelMchNo) { + lambdaUpdate() + .eq(WechatIsvMchAppCapability::getChannelMchNo, channelMchNo) + .remove(); + } + + /// 根据应用ID删除关联(应用被删除时级联清理,避免悬空引用) + public void deleteByWechatIsvMchAppId(Long wechatIsvMchAppId) { + lambdaUpdate() + .eq(WechatIsvMchAppCapability::getWechatIsvMchAppId, wechatIsvMchAppId) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityMapper.java new file mode 100644 index 000000000..822b5b41e --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppCapabilityMapper.java @@ -0,0 +1,13 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppCapability; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信服务商通道商户应用支付能力关联 +/// +/// 微信服务商通道商户应用支付能力关联 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatIsvMchAppCapabilityMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppManager.java new file mode 100644 index 000000000..c86be3ff5 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppManager.java @@ -0,0 +1,54 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/// # 微信服务商通道商户应用 Manager +/// +/// 服务商通道商户应用(子商户应用)数据访问管理器,提供按商户号和通道商户号查询列表、 +/// 首个应用查询、应用类型推导查询及同一通道下应用AppId唯一性校验等方法。 +/// +@Repository +public class WechatIsvMchAppManager extends BaseManager { + + /// 根据商户号和通道商户号查询应用列表(按创建时间升序) + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + return lambdaQuery() + .eq(WechatIsvMchApp::getMchNo, mchNo) + .eq(WechatIsvMchApp::getChannelMchNo, channelMchNo) + .orderByAsc(WechatIsvMchApp::getCreateTime) + .orderByAsc(WechatIsvMchApp::getId) + .list(); + } + + /// 按通道商户号查询首个应用(能力解析兜底时使用) + public Optional findFirstByChannelMchNo(String channelMchNo) { + return firstOpt(q -> q + .eq(WechatIsvMchApp::getChannelMchNo, channelMchNo) + .orderByAsc(WechatIsvMchApp::getCreateTime) + .orderByAsc(WechatIsvMchApp::getId)); + } + + /// 按通道商户号与应用类型查询首个应用(能力→应用类型推导时使用) + public Optional findFirstByChannelMchNoAndAppType(String channelMchNo, String appType) { + return firstOpt(q -> q + .eq(WechatIsvMchApp::getChannelMchNo, channelMchNo) + .eq(WechatIsvMchApp::getAppType, appType) + .orderByAsc(WechatIsvMchApp::getCreateTime) + .orderByAsc(WechatIsvMchApp::getId)); + } + + /// 校验同一通道商户下wxAppId是否已存在(排除自身) + public boolean existsByChannelMchNoAndWxAppId(String mchNo, String channelMchNo, String wxAppId, Long excludeId) { + return lambdaQuery() + .eq(WechatIsvMchApp::getMchNo, mchNo) + .eq(WechatIsvMchApp::getChannelMchNo, channelMchNo) + .eq(WechatIsvMchApp::getWxAppId, wxAppId) + .ne(excludeId != null, WechatIsvMchApp::getId, excludeId) + .exists(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppMapper.java new file mode 100644 index 000000000..c1c690b34 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/isv/WechatIsvMchAppMapper.java @@ -0,0 +1,13 @@ +package cn.daxpay.open.channel.wechat.dao.isv; + +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信服务商通道商户应用 +/// +/// 微信服务商通道商户应用 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatIsvMchAppMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvAppAuthConfig.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvAppAuthConfig.java index 59dca67e7..b7860a4e7 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvAppAuthConfig.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvAppAuthConfig.java @@ -2,7 +2,7 @@ package cn.daxpay.open.channel.wechat.entity.isv; import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvAppAuthConfigConvert; import cn.daxpay.open.channel.wechat.result.isv.WechatIsvAppAuthConfigResult; -import cn.daxpay.open.payment.common.entity.merchant.MchBaseEntity; +import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity; import cn.daxpay.open.platform.common.mybatisplus.function.ToResult; import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler; import com.baomidou.mybatisplus.annotation.FieldStrategy; @@ -14,17 +14,14 @@ import lombok.experimental.Accessors; /// # 微信服务商应用授权认证配置 /// -/// 继承商户基础实体,自动填充商户号用于多商户隔离。 +/// 服务商应用(WechatIsvApp)本身为平台级全局配置, 不挂商户维度, +/// 其授权认证配置(appSecret/回调地址)同样为全局配置。 /// @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) @TableName(value = "wechat_isv_app_auth_config", autoResultMap = true) -public class WechatIsvAppAuthConfig extends MchBaseEntity implements ToResult { - - /// 通道商户号 - @TableField(updateStrategy = FieldStrategy.NEVER) - private String channelMchNo; +public class WechatIsvAppAuthConfig extends MpBaseEntity implements ToResult { /// 微信服务商应用ID @TableField(updateStrategy = FieldStrategy.NEVER) diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchApp.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchApp.java new file mode 100644 index 000000000..94d0133be --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchApp.java @@ -0,0 +1,44 @@ +package cn.daxpay.open.channel.wechat.entity.isv; + +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppConvert; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppResult; +import cn.daxpay.open.payment.common.entity.merchant.MchBaseEntity; +import cn.daxpay.open.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; + +/// # 微信服务商通道商户应用 +/// +/// 表示微信服务商模式下特约商户(子商户)注册的微信应用实体,挂在通道商户号(channelMchNo)维度下。 +/// 每个应用对应一个微信AppId,支付时作为 sub_appid 使用。同一通道商户下微信应用AppId唯一。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "wechat_isv_mch_app", autoResultMap = true) +public class WechatIsvMchApp extends MchBaseEntity implements ToResult { + + /// 通道商户号(服务商特约商户) + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 应用名称 + private String appName; + + /// 应用类型 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String appType; + + /// 微信应用AppId(对应微信支付sub_appid) + private String wxAppId; + + /// 转换 + @Override + public WechatIsvMchAppResult toResult() { + return WechatIsvMchAppConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppAuthConfig.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppAuthConfig.java new file mode 100644 index 000000000..9e2bff6dd --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppAuthConfig.java @@ -0,0 +1,46 @@ +package cn.daxpay.open.channel.wechat.entity.isv; + +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppAuthConfigConvert; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppAuthConfigResult; +import cn.daxpay.open.payment.common.entity.merchant.MchBaseEntity; +import cn.daxpay.open.platform.common.mybatisplus.function.ToResult; +import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 微信服务商通道商户应用授权认证配置 +/// +/// 配置服务商通道商户应用(子商户应用)的应用密钥和授权回调地址,用于微信OAuth授权流程中的身份验证与回调跳转。 +/// 一个应用对应一份授权配置(由唯一约束 channel_mch_no + wechat_isv_mch_app_id 保证)。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "wechat_isv_mch_app_auth_config", autoResultMap = true) +public class WechatIsvMchAppAuthConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号(服务商特约商户) + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 关联服务商通道商户应用ID + @TableField(updateStrategy = FieldStrategy.NEVER) + private Long wechatIsvMchAppId; + + /// 应用密钥(加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) + private String appSecret; + + /// 授权回调地址 + private String authCallbackUrl; + + /// 转换 + @Override + public WechatIsvMchAppAuthConfigResult toResult() { + return WechatIsvMchAppAuthConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppCapability.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppCapability.java new file mode 100644 index 000000000..736f797bb --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/isv/WechatIsvMchAppCapability.java @@ -0,0 +1,44 @@ +package cn.daxpay.open.channel.wechat.entity.isv; + +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppCapabilityConvert; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppCapabilityResult; +import cn.daxpay.open.payment.common.entity.merchant.MchBaseEntity; +import cn.daxpay.open.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; + +/// # 微信服务商通道商户应用支付能力关联 +/// +/// 建立通道商户维度下「支付能力 → 子商户应用」的绑定关系,供支付时选择正确的微信应用(sub_appid)。 +/// 本表只存子商户显式选择自己应用的记录,某能力未配置时自动回退到全局服务商应用配置。 +/// 同一通道商户下,一个支付能力只能绑定一个应用(由唯一约束 channel_mch_no + capability 保证)。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName(value = "wechat_isv_mch_app_capability", autoResultMap = true) +public class WechatIsvMchAppCapability extends MchBaseEntity implements ToResult { + + /// 通道商户号(服务商特约商户) + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 支付能力编码 + /// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum + @TableField(updateStrategy = FieldStrategy.NEVER) + private String capability; + + /// 关联微信服务商通道商户应用ID(指向 wechat_isv_mch_app.id) + @TableField(updateStrategy = FieldStrategy.NEVER) + private Long wechatIsvMchAppId; + + /// 转换 + @Override + public WechatIsvMchAppCapabilityResult toResult() { + return WechatIsvMchAppCapabilityConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvAppAuthConfigParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvAppAuthConfigParam.java index 80775b190..96e2af49b 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvAppAuthConfigParam.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvAppAuthConfigParam.java @@ -13,7 +13,7 @@ import lombok.experimental.Accessors; @Schema(title = "微信服务商应用授权认证配置保存参数") public class WechatIsvAppAuthConfigParam { - @NotNull(message = "{validation.field.id.notNull}") + @NotNull(message = "{validation.field.wechatIsvAppId.notNull}") @Schema(description = "微信服务商应用ID") private Long wechatIsvAppId; diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppAuthConfigParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppAuthConfigParam.java new file mode 100644 index 000000000..09aa2366a --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppAuthConfigParam.java @@ -0,0 +1,37 @@ +package cn.daxpay.open.channel.wechat.param.isv; + +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 WechatIsvMchAppAuthConfigParam { + + @NotNull(message = "{validation.field.wechatIsvMchAppId.notNull}") + @Schema(description = "关联应用ID") + private Long wechatIsvMchAppId; + + @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; + + @Size(max = 512, message = "{validation.field.authCallbackUrl.size}") + @Schema(description = "授权回调地址(仅公众号,非必填)") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityBatchParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityBatchParam.java new file mode 100644 index 000000000..157d2e120 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityBatchParam.java @@ -0,0 +1,33 @@ +package cn.daxpay.open.channel.wechat.param.isv; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/// # 微信服务商通道商户支付能力关联应用批量保存参数 +/// +/// 全量覆盖某通道商户下「支付能力 → 子商户应用」的绑定关系:先清除旧记录,再按 items 批量插入。 +/// items 为空表示清空该通道商户下所有绑定(该能力的应用将自动回退到全局服务商配置)。 +/// 仅存子商户显式选择自己应用的记录,选「服务商默认应用」的能力不在此参数中。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "微信服务商通道商户支付能力关联应用批量保存参数") +public class WechatIsvMchAppCapabilityBatchParam { + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @Valid + @Schema(description = "支付能力关联应用列表(仅含选子商户应用的项,选服务商默认的不传)") + private List items; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityItem.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityItem.java new file mode 100644 index 000000000..3d649746e --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppCapabilityItem.java @@ -0,0 +1,25 @@ +package cn.daxpay.open.channel.wechat.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; + +/// # 微信服务商通道商户支付能力关联应用单项 +/// +/// 用于批量保存时,声明单个支付能力绑定的服务商通道商户应用(子商户应用)。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "微信服务商通道商户支付能力关联应用单项") +public class WechatIsvMchAppCapabilityItem { + + @NotBlank(message = "{validation.field.capability.notBlank}") + @Schema(description = "支付能力编码") + private String capability; + + @NotNull(message = "{validation.field.wechatIsvMchAppId.notNull}") + @Schema(description = "关联微信服务商通道商户应用ID") + private Long wechatIsvMchAppId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppParam.java new file mode 100644 index 000000000..bd63d3809 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/isv/WechatIsvMchAppParam.java @@ -0,0 +1,42 @@ +package cn.daxpay.open.channel.wechat.param.isv; + +import cn.daxpay.open.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 WechatIsvMchAppParam { + + @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.appType.notBlank}") + @Schema(description = "应用类型") + private String appType; + + @NotBlank(message = "{validation.field.wxAppId.notBlank}") + @Schema(description = "微信应用AppId") + private String wxAppId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvAppAuthConfigResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvAppAuthConfigResult.java index fa4e8735a..4957b73fc 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvAppAuthConfigResult.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvAppAuthConfigResult.java @@ -15,12 +15,6 @@ import lombok.experimental.Accessors; @Schema(title = "微信服务商应用授权认证配置") public class WechatIsvAppAuthConfigResult extends BaseResult { - @Schema(description = "商户号") - private String mchNo; - - @Schema(description = "通道商户号") - private String channelMchNo; - @Schema(description = "微信服务商应用ID") private Long wechatIsvAppId; diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppAuthConfigResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppAuthConfigResult.java new file mode 100644 index 000000000..de9b2f9de --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppAuthConfigResult.java @@ -0,0 +1,36 @@ +package cn.daxpay.open.channel.wechat.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; + +/// # 微信服务商通道商户应用授权认证配置 +/// +/// 微信服务商通道商户应用授权认证配置的返回结果对象,含商户号、通道商户号、应用密钥(脱敏)和授权回调地址。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "微信服务商通道商户应用授权认证配置") +public class WechatIsvMchAppAuthConfigResult extends BaseResult { + + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "关联应用ID") + private Long wechatIsvMchAppId; + + // 应用密钥(序列化时脱敏) + @SensitiveInfo + @Schema(description = "应用密钥(已脱敏)") + private String appSecret; + + @Schema(description = "授权回调地址") + private String authCallbackUrl; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppCapabilityResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppCapabilityResult.java new file mode 100644 index 000000000..9bde6ac03 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppCapabilityResult.java @@ -0,0 +1,37 @@ +package cn.daxpay.open.channel.wechat.result.isv; + +import cn.daxpay.open.payment.common.result.MchBaseResult; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/// # 微信服务商通道商户应用支付能力关联 +/// +/// 展示通道商户(特约商户)下各支付能力绑定的子商户应用信息,含应用名称/AppId/类型等冗余字段(由 Service 填充)。 +/// 注:「服务商默认应用」回退信息由前端另行调用全局 wechat_isv_app_capability 接口获取,不在本结果中冗余。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "微信服务商通道商户应用支付能力关联") +public class WechatIsvMchAppCapabilityResult extends MchBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "支付能力编码") + private String capability; + + @Schema(description = "关联微信服务商通道商户应用ID") + private Long wechatIsvMchAppId; + + @Schema(description = "应用名称(冗余展示)") + private String appName; + + @Schema(description = "微信应用AppId(冗余展示)") + private String wxAppId; + + @Schema(description = "应用类型(冗余展示): official_account/mini_program/mobile_app") + private String appType; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppResult.java new file mode 100644 index 000000000..7d036a0f9 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/isv/WechatIsvMchAppResult.java @@ -0,0 +1,30 @@ +package cn.daxpay.open.channel.wechat.result.isv; + +import cn.daxpay.open.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 WechatIsvMchAppResult extends MchTradeBaseResult { + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "应用名称") + private String appName; + + @Schema(description = "应用类型") + private String appType; + + @Schema(description = "微信应用AppId") + private String wxAppId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java index ac7942a7f..2fa691013 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java @@ -2,7 +2,9 @@ package cn.daxpay.open.channel.wechat.service.direct; import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential; import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectAppManager; +import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectApp; +import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectKeyConfig; import cn.daxpay.open.payment.masterdata.constants.product.dao.PayProductConfigManager; import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; @@ -28,6 +30,7 @@ import org.springframework.stereotype.Service; public class WechatDirectConfigAssembler { private final WechatDirectAppManager wechatDirectAppManager; + private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager; private final WechatDirectKeyConfigService wechatDirectKeyConfigService; private final WechatDirectAppCapabilityService wechatDirectAppCapabilityService; private final PayProductConfigManager payProductConfigManager; @@ -35,16 +38,20 @@ public class WechatDirectConfigAssembler { /// 组装直连商户的通道调用凭证(下发给子应用) /// /// @param mchNo 商户号(兜底定位应用) - /// @param channelMchNo 通道商户号(微信商户号, 密钥查询与应用定位主键) + /// @param channelMchNo 通道商户号(系统雪花号, 密钥查询与应用定位主键, 不等于微信商户号) /// @param capability 支付能力编码(用于选择匹配的应用) /// @return 微信 SDK 凭证, 字段对齐子应用 WechatSdkCredential public WechatSdkCredential buildConfig(String mchNo, String channelMchNo, String capability) { WechatDirectApp app = resolveApp(mchNo, channelMchNo, capability); WechatDirectKeyConfig keyConfig = wechatDirectKeyConfigService.findByChannelMchNo(channelMchNo); + // 从通道商户绑定表取真实微信商户号(channelMchNo 是系统雪花号, 不等于 wxMchId) + WechatDirectChannelMerchant channelMerchant = wechatDirectChannelMerchantManager.findByChannelMchNo(channelMchNo) + // 微信: 通道商户配置不存在 + .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); + WechatSdkCredential credential = new WechatSdkCredential(); - // 微信商户号 = 通道商户号 - credential.setWxMchId(app.getChannelMchNo()); + credential.setWxMchId(channelMerchant.getWxMchId()); credential.setWxAppId(app.getWxAppId()); // 密钥与证书 credential.setApiKeyV3(keyConfig.getApiKeyV3()); diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppAuthConfigService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppAuthConfigService.java new file mode 100644 index 000000000..897feb402 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppAuthConfigService.java @@ -0,0 +1,66 @@ +package cn.daxpay.open.channel.wechat.service.isv; + +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppAuthConfigConvert; +import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvMchAppAuthConfigManager; +import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvMchAppManager; +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppAuthConfig; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppAuthConfigParam; +import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.daxpay.open.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 WechatIsvMchAppAuthConfigService { + + private final WechatIsvMchAppAuthConfigManager wechatIsvMchAppAuthConfigManager; + private final WechatIsvMchAppManager wechatIsvMchAppManager; + + /// 根据应用ID查询授权认证配置, 不存在则创建默认记录 + @Transactional(rollbackFor = Exception.class) + public WechatIsvMchAppAuthConfig findByWechatIsvMchAppId(Long wechatIsvMchAppId) { + var existing = wechatIsvMchAppAuthConfigManager.findByWechatIsvMchAppId(wechatIsvMchAppId); + if (existing.isPresent()) { + return existing.get(); + } + var app = wechatIsvMchAppManager.findById(wechatIsvMchAppId) + // 微信: 服务商通道商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.mchAppNotFound")); + var config = new WechatIsvMchAppAuthConfig() + .setChannelMchNo(app.getChannelMchNo()) + .setWechatIsvMchAppId(wechatIsvMchAppId); + config.setMchNo(app.getMchNo()); + wechatIsvMchAppAuthConfigManager.save(config); + return config; + } + + /// 保存应用授权认证配置(更新) + @Transactional(rollbackFor = Exception.class) + public void save(WechatIsvMchAppAuthConfigParam param) { + var app = wechatIsvMchAppManager.findById(param.getWechatIsvMchAppId()) + // 微信: 服务商通道商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.mchAppNotFound")); + if (!app.getMchNo().equals(param.getMchNo()) || !app.getChannelMchNo().equals(param.getChannelMchNo())) { + // 微信: 服务商通道商户应用不存在或商户号归属不匹配 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.mchAppNotFound"); + } + var config = this.findByWechatIsvMchAppId(param.getWechatIsvMchAppId()); + WechatIsvMchAppAuthConfigConvert.CONVERT.copy(param, config); + wechatIsvMchAppAuthConfigManager.updateById(config); + } + + /// 删除应用授权认证配置 + public void deleteByWechatIsvMchAppId(Long wechatIsvMchAppId) { + wechatIsvMchAppAuthConfigManager.deleteByWechatIsvMchAppId(wechatIsvMchAppId); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppCapabilityService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppCapabilityService.java new file mode 100644 index 000000000..e463a7886 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppCapabilityService.java @@ -0,0 +1,162 @@ +package cn.daxpay.open.channel.wechat.service.isv; + +import cn.daxpay.open.channel.wechat.code.WechatAppTypeCode; +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppCapabilityConvert; +import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvMchAppCapabilityManager; +import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvMchAppManager; +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchAppCapability; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppCapabilityBatchParam; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppCapabilityItem; +import cn.daxpay.open.channel.wechat.result.WechatCapabilityOption; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppCapabilityResult; +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; +import cn.daxpay.open.payment.core.strategy.PaymentStrategyFactory; +import cn.daxpay.open.payment.core.strategy.product.AbsProductStrategy; +import cn.hutool.core.collection.CollUtil; +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.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// # 微信服务商通道商户应用支付能力关联 +/// +/// 管理通道商户(特约商户)维度下「支付能力 → 子商户应用」的绑定关系。 +/// 本表只存子商户显式选择自己应用的记录;某能力未配置时,支付解析返回 empty, +/// 由调用方(未来的微信服务商支付策略/配置组装器)回退到全局服务商应用配置 +/// ([cn.daxpay.open.channel.wechat.service.isv.WechatIsvAppCapabilityService])。 +/// +/// 支付时通过 [resolveApp] 解析当前能力对应的应用:显式配置 > appType自动推导 > 通道商户首个兜底, +/// 三者均未命中返回 empty(语义为"使用全局服务商配置")。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatIsvMchAppCapabilityService { + + private final WechatIsvMchAppCapabilityManager capabilityManager; + private final WechatIsvMchAppManager wechatIsvMchAppManager; + + /// 查询通道商户下的能力关联列表,并填充子商户应用展示信息(应用名称/AppId/类型) + public List listByChannelMchNo(String mchNo, String channelMchNo) { + List rels = capabilityManager.listByChannelMchNo(channelMchNo); + if (rels.isEmpty()) { + return List.of(); + } + // 批量查询该通道商户下全部子商户应用,构建 id → app 映射用于填充展示字段 + Map appMap = wechatIsvMchAppManager.listByMchNoAndChannelMchNo(mchNo, channelMchNo) + .stream() + .collect(Collectors.toMap(WechatIsvMchApp::getId, Function.identity())); + return rels.stream() + .map(rel -> fillResult(rel, appMap.get(rel.getWechatIsvMchAppId()))) + .toList(); + } + + /// 全量保存能力关联(先清后插),校验应用归属与能力唯一 + /// + /// items 仅含子商户显式选择自己应用的项;选「服务商默认应用」的能力不在此参数中, + /// 先清后插时会自然移除其历史记录,支付时自动回退到全局服务商配置。 + @Transactional(rollbackFor = Exception.class) + public void saveBatch(WechatIsvMchAppCapabilityBatchParam param) { + String mchNo = param.getMchNo(); + String channelMchNo = param.getChannelMchNo(); + List items = param.getItems(); + // 先清空旧关联 + capabilityManager.deleteByChannelMchNo(channelMchNo); + if (CollUtil.isEmpty(items)) { + return; + } + // 查询该通道商户下全部子商户应用,校验应用归属 + List apps = wechatIsvMchAppManager.listByMchNoAndChannelMchNo(mchNo, channelMchNo); + Map appMap = apps.stream() + .collect(Collectors.toMap(WechatIsvMchApp::getId, Function.identity())); + HashSet capabilitySet = new HashSet<>(); + for (WechatIsvMchAppCapabilityItem item : items) { + // 能力不可重复 + if (!capabilitySet.add(item.getCapability())) { + // 微信: 支付能力[{0}]重复配置 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.capabilityDuplicate", item.getCapability()); + } + // 应用归属校验 + if (!appMap.containsKey(item.getWechatIsvMchAppId())) { + // 微信: 服务商通道商户应用不存在 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.mchAppNotFound"); + } + var entity = new WechatIsvMchAppCapability() + .setChannelMchNo(channelMchNo) + .setCapability(item.getCapability()) + .setWechatIsvMchAppId(item.getWechatIsvMchAppId()); + entity.setMchNo(mchNo); + capabilityManager.save(entity); + } + } + + /// 应用被删除时级联清理关联,避免悬空引用 + public void deleteByWechatIsvMchAppId(Long wechatIsvMchAppId) { + capabilityManager.deleteByWechatIsvMchAppId(wechatIsvMchAppId); + } + + /// 支付时解析当前支付能力对应的子商户应用:显式配置 > appType自动推导 > 通道商户首个兜底 + /// + /// 三者均未命中返回 empty,语义为"该能力未在子商户维度配置,请回退到全局服务商应用配置"。 + /// 调用方(未来的微信服务商配置组装器)应在此基础上回退 [WechatIsvAppCapabilityService.resolveApp]。 + /// + /// @param channelMchNo 通道商户号(服务商特约商户) + /// @param capability 支付能力编码 + /// @return 命中的子商户应用;未命中返回 empty(应回退全局服务商配置) + public Optional resolveApp(String channelMchNo, String capability) { + if (StrUtil.hasBlank(channelMchNo, capability)) { + return Optional.empty(); + } + // 1. 显式配置优先 + var rel = capabilityManager.findOne(channelMchNo, capability); + if (rel.isPresent()) { + return wechatIsvMchAppManager.findById(rel.get().getWechatIsvMchAppId()); + } + // 2. 未配置则按能力 → appType 自动推导 + String appType = WechatAppTypeCode.resolveAppType(capability); + if (appType != null) { + Optional byType = wechatIsvMchAppManager.findFirstByChannelMchNoAndAppType(channelMchNo, appType); + if (byType.isPresent()) { + return byType; + } + } + // 3. 最终兜底:按通道商户号取首个子商户应用;仍未命中返回 empty(回退全局) + return wechatIsvMchAppManager.findFirstByChannelMchNo(channelMchNo); + } + + /// 查询微信服务商产品支持的支付能力候选列表(含国际化名称) + public List listSupportedCapabilities() { + AbsProductStrategy strategy = PaymentStrategyFactory.createByProduct( + ProductEnum.WECHAT_ISV.getCode(), AbsProductStrategy.class); + return strategy.methodCapabilityMapping().values().stream() + .flatMap(List::stream) + .distinct() + .map(cap -> new WechatCapabilityOption(cap.getCode(), I18nUtil.getEnumName(cap))) + .toList(); + } + + /// 填充关联结果的应用展示字段 + private WechatIsvMchAppCapabilityResult fillResult(WechatIsvMchAppCapability rel, WechatIsvMchApp app) { + WechatIsvMchAppCapabilityResult result = WechatIsvMchAppCapabilityConvert.CONVERT.toResult(rel); + if (app != null) { + result.setAppName(app.getAppName()) + .setWxAppId(app.getWxAppId()) + .setAppType(app.getAppType()); + } + return result; + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppService.java new file mode 100644 index 000000000..6d8f7195d --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/isv/WechatIsvMchAppService.java @@ -0,0 +1,112 @@ +package cn.daxpay.open.channel.wechat.service.isv; + +import cn.daxpay.open.channel.wechat.code.WechatIsvAppTypeEnum; +import cn.daxpay.open.channel.wechat.convert.isv.WechatIsvMchAppConvert; +import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvMchAppManager; +import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp; +import cn.daxpay.open.channel.wechat.param.isv.WechatIsvMchAppParam; +import cn.daxpay.open.channel.wechat.result.isv.WechatIsvMchAppResult; +import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil; +import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.daxpay.open.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.Arrays; +import java.util.List; + +/// # 微信服务商通道商户应用管理 +/// +/// 提供服务商通道商户应用(子商户应用)的增删改查功能,包含同一通道商户下应用AppId的唯一性校验、 +/// 应用类型校验、操作范围校验(防止跨商户操作),以及删除时级联清理授权配置与能力关联。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatIsvMchAppService { + + private final WechatIsvMchAppManager wechatIsvMchAppManager; + private final WechatIsvMchAppAuthConfigService wechatIsvMchAppAuthConfigService; + private final WechatIsvMchAppCapabilityService wechatIsvMchAppCapabilityService; + + /// 根据商户号和通道商户号查询应用列表 + public List listByMchNoAndChannelMchNo(String mchNo, String channelMchNo) { + var list = wechatIsvMchAppManager.listByMchNoAndChannelMchNo(mchNo, channelMchNo); + return MpUtil.toListResult(list); + } + + /// 根据ID查询应用详情 + public WechatIsvMchAppResult findById(Long id) { + return wechatIsvMchAppManager.findById(id) + .map(WechatIsvMchApp::toResult) + // 微信: 服务商通道商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.mchAppNotFound")); + } + + /// 判断同一通道商户下微信应用AppId是否已存在 + public boolean existsWxAppIdByChannel(String mchNo, String channelMchNo, String wxAppId, Long excludeId) { + if (StrUtil.hasBlank(mchNo, channelMchNo, wxAppId)) { + return false; + } + return wechatIsvMchAppManager.existsByChannelMchNoAndWxAppId(mchNo, channelMchNo, wxAppId, excludeId); + } + + /// 新增服务商通道商户应用 + public void add(WechatIsvMchAppParam param) { + this.assertWxAppIdUnique(param.getMchNo(), param.getChannelMchNo(), param.getWxAppId(), null); + this.validateAppType(param.getAppType()); + var entity = WechatIsvMchAppConvert.CONVERT.toEntity(param); + wechatIsvMchAppManager.save(entity); + } + + /// 更新服务商通道商户应用 + public void update(WechatIsvMchAppParam param) { + var entity = wechatIsvMchAppManager.findById(param.getId()) + // 微信: 服务商通道商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.mchAppNotFound")); + this.assertScopeMatch(entity, param.getMchNo(), param.getChannelMchNo()); + this.assertWxAppIdUnique(entity.getMchNo(), entity.getChannelMchNo(), param.getWxAppId(), param.getId()); + this.validateAppType(param.getAppType()); + WechatIsvMchAppConvert.CONVERT.copy(param, entity); + wechatIsvMchAppManager.updateById(entity); + } + + /// 删除应用(级联删除授权配置与能力关联) + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + wechatIsvMchAppManager.findById(id) + // 微信: 服务商通道商户应用不存在 + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.mchAppNotFound")); + wechatIsvMchAppAuthConfigService.deleteByWechatIsvMchAppId(id); + wechatIsvMchAppCapabilityService.deleteByWechatIsvMchAppId(id); + wechatIsvMchAppManager.deleteById(id); + } + + /// 校验同一通道商户下应用AppId唯一 + 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(WechatIsvMchApp entity, String mchNo, String channelMchNo) { + if (!entity.getMchNo().equals(mchNo) || !entity.getChannelMchNo().equals(channelMchNo)) { + // 微信: 服务商通道商户应用不存在或商户号归属不匹配 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.mchAppNotFound"); + } + } + + /// 校验应用类型(复用服务商应用类型枚举) + private void validateAppType(String appType) { + boolean valid = Arrays.stream(WechatIsvAppTypeEnum.values()) + .anyMatch(item -> item.getCode().equals(appType)); + if (!valid) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.appTypeInvalid"); + } + } +} diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_method.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_method.json index 56a6863ef..26aeabac5 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_method.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/enum/pay_method.json @@ -1,13 +1,13 @@ { - "aggregate_pay_qrcode": "Aggregate QR Code Payment", - "aggregate_pay_barcode": "Aggregate Barcode Payment", + "aggregate_pay_qrcode": "Aggregate QR Code", + "aggregate_pay_barcode": "Aggregate Barcode", "wechat_cashier": "WeChat Mini Program Cashier", "wechat_qr": "WeChat Native", "wechat_jsapi": "WeChat JSAPI", "wechat_mini": "WeChat Mini Program", "wechat_h5": "WeChat H5", "wechat_app": "WeChat App", - "wechat_barcode": "WeChat Barcode Payment", + "wechat_barcode": "WeChat Barcode", "alipay_qr": "Alipay QR", "alipay_order_qr": "Alipay Order QR", "alipay_jsapi": "Alipay JSAPI", @@ -15,15 +15,15 @@ "alipay_pc": "Alipay PC", "alipay_h5": "Alipay H5", "alipay_app": "Alipay App", - "alipay_barcode": "Alipay Barcode Payment", + "alipay_barcode": "Alipay Barcode", "union_qr": "UnionPay QR", "union_jsapi": "UnionPay JSAPI", "union_h5": "UnionPay H5", - "union_pay_barcode": "UnionPay Barcode Payment", - "douyin_qr": "Douyin QR Pay", - "douyin_jsapi": "Douyin JSAPI Pay", - "douyin_h5": "Douyin H5 Pay", - "douyin_app": "Douyin APP Pay", + "union_pay_barcode": "UnionPay Barcode", + "douyin_qr": "Douyin QR", + "douyin_jsapi": "Douyin JSAPI", + "douyin_h5": "Douyin H5", + "douyin_app": "Douyin App", "visa_card_gateway": "Visa Gateway", "visa_card_present": "Visa Card Present", "mastercard_card_gateway": "Mastercard Gateway", 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 f4d9c3b8c..bc9b9e5dd 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 @@ -103,6 +103,9 @@ "alipayDirectAppId": { "notNull": "Alipay direct app id cannot be null" }, + "wechatIsvAppId": { + "notNull": "WeChat ISV app id cannot be null" + }, "account": { "notBlank": "Account cannot be blank", "size": "Account length must be between 6 and 20 characters", @@ -508,6 +511,9 @@ "wechatDirectAppId": { "notNull": "WeChat direct app ID cannot be blank" }, + "wechatIsvMchAppId": { + "notNull": "WeChat ISV channel merchant app ID cannot be blank" + }, "appSecret": { "size": "App secret cannot exceed 512 characters" }, diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_method.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_method.json index 1754326a6..f76926818 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_method.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/enum/pay_method.json @@ -1,32 +1,32 @@ { - "aggregate_pay_qrcode": "聚合扫码支付", - "aggregate_pay_barcode": "聚合付款码支付", + "aggregate_pay_qrcode": "聚合扫码", + "aggregate_pay_barcode": "聚合付款码", "wechat_cashier": "微信小程序收银台", - "wechat_qr": "Native 支付", - "wechat_jsapi": "微信jsapi", + "wechat_qr": "微信Native", + "wechat_jsapi": "微信JSAPI", "wechat_mini": "微信小程序", "wechat_h5": "微信H5", - "wechat_app": "微信应用支付", - "wechat_barcode": "微信付款码支付", + "wechat_app": "微信APP", + "wechat_barcode": "微信付款码", "alipay_qr": "支付宝扫码", "alipay_order_qr": "支付宝订单码", - "alipay_jsapi": "支付宝jsapi", + "alipay_jsapi": "支付宝JSAPI", "alipay_mini": "支付宝小程序", - "alipay_pc": "支付宝电脑支付", + "alipay_pc": "支付宝PC", "alipay_h5": "支付宝H5", - "alipay_app": "支付宝应用支付", - "alipay_barcode": "支付宝付款码支付", + "alipay_app": "支付宝APP", + "alipay_barcode": "支付宝付款码", "union_qr": "银联扫码", - "union_jsapi": "银联jsapi", + "union_jsapi": "银联JSAPI", "union_h5": "银联H5", - "union_pay_barcode": "银联付款码支付", - "douyin_qr": "抖音扫码支付", - "douyin_jsapi": "抖音JSAPI支付", - "douyin_h5": "抖音H5支付", - "douyin_app": "抖音APP支付", - "visa_card_gateway": "Visa 网关支付", - "visa_card_present": "Visa 刷卡支付", - "mastercard_card_gateway": "万事达网关支付", - "mastercard_card_present": "万事达刷卡支付", - "other": "其他支付方式" + "union_pay_barcode": "银联付款码", + "douyin_qr": "抖音扫码", + "douyin_jsapi": "抖音JSAPI", + "douyin_h5": "抖音H5", + "douyin_app": "抖音APP", + "visa_card_gateway": "Visa网关", + "visa_card_present": "Visa刷卡", + "mastercard_card_gateway": "万事达网关", + "mastercard_card_present": "万事达刷卡", + "other": "其他" } 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 362c5d39d..38121e7f7 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 @@ -103,6 +103,9 @@ "alipayDirectAppId": { "notNull": "支付宝直连应用ID不可为空" }, + "wechatIsvAppId": { + "notNull": "微信服务商应用ID不可为空" + }, "account": { "notBlank": "登录账号不可为空", "size": "账号长度为6-20个字符", @@ -511,6 +514,9 @@ "wechatDirectAppId": { "notNull": "微信直连应用ID不可为空" }, + "wechatIsvMchAppId": { + "notNull": "微信服务商通道商户应用ID不可为空" + }, "appSecret": { "size": "应用密钥不可超过512位" }, diff --git a/daxpay-platform/daxpay-platform-core/src/main/java/cn/daxpay/open/platform/core/enums/pay/channel/PayMethodEnum.java b/daxpay-platform/daxpay-platform-core/src/main/java/cn/daxpay/open/platform/core/enums/pay/channel/PayMethodEnum.java index 0b0db1a7b..e40bfb592 100644 --- a/daxpay-platform/daxpay-platform-core/src/main/java/cn/daxpay/open/platform/core/enums/pay/channel/PayMethodEnum.java +++ b/daxpay-platform/daxpay-platform-core/src/main/java/cn/daxpay/open/platform/core/enums/pay/channel/PayMethodEnum.java @@ -17,23 +17,23 @@ import java.util.Objects; public enum PayMethodEnum implements I18nSupport { /* 聚合支付 */ - /// 聚合扫码支付 + /// 聚合扫码 AGGREGATE_PAY_QRCODE("aggregate_pay_qrcode", PayProviderEnum.AGGREGATE_PAY), - /// 聚合付款码支付 + /// 聚合付款码 AGGREGATE_PAY_BARCODE("aggregate_pay_barcode", PayProviderEnum.AGGREGATE_PAY), /* 微信 */ /// 微信小程序收银台 WECHAT_CASHIER("wechat_cashier", PayProviderEnum.WECHAT), - /// 微信扫码 + /// 微信Native WECHAT_QR("wechat_qr", PayProviderEnum.WECHAT), - /// 微信jsapi + /// 微信JSAPI WECHAT_JSAPI("wechat_jsapi", PayProviderEnum.WECHAT), /// 微信小程序 WECHAT_MINI("wechat_mini", PayProviderEnum.WECHAT), /// 微信H5 WECHAT_H5("wechat_h5", PayProviderEnum.WECHAT), - /// 微信应用支付 + /// 微信APP WECHAT_APP("wechat_app", PayProviderEnum.WECHAT), /// 微信付款码 WECHAT_BARCODE("wechat_barcode", PayProviderEnum.WECHAT), @@ -43,15 +43,15 @@ public enum PayMethodEnum implements I18nSupport { ALIPAY_QR("alipay_qr", PayProviderEnum.ALIPAY), /// 支付宝订单码 ALIPAY_ORDER_QR("alipay_order_qr", PayProviderEnum.ALIPAY), - /// 支付宝jsapi + /// 支付宝JSAPI ALIPAY_JSAPI("alipay_jsapi", PayProviderEnum.ALIPAY), /// 支付宝小程序 ALIPAY_MINI("alipay_mini", PayProviderEnum.ALIPAY), - /// 支付宝电脑支付 + /// 支付宝PC ALIPAY_PC("alipay_pc", PayProviderEnum.ALIPAY), /// 支付宝H5 ALIPAY_H5("alipay_h5", PayProviderEnum.ALIPAY), - /// 支付宝应用支付 + /// 支付宝APP ALIPAY_APP("alipay_app", PayProviderEnum.ALIPAY), /// 支付宝付款码 ALIPAY_BARCODE("alipay_barcode", PayProviderEnum.ALIPAY), @@ -59,7 +59,7 @@ public enum PayMethodEnum implements I18nSupport { /* 银联 */ /// 银联扫码 UNION_QR("union_qr", PayProviderEnum.UNION_PAY), - /// 银联jsapi + /// 银联JSAPI UNION_JSAPI("union_jsapi", PayProviderEnum.UNION_PAY), /// 银联H5 UNION_H5("union_h5", PayProviderEnum.UNION_PAY), @@ -67,26 +67,26 @@ public enum PayMethodEnum implements I18nSupport { UNION_PAY_BARCODE("union_pay_barcode", PayProviderEnum.UNION_PAY), /* 抖音 */ - /// 抖音扫码支付 + /// 抖音扫码 DOUYIN_QR("douyin_qr", PayProviderEnum.DOUYIN), - /// 抖音JSAPI支付 + /// 抖音JSAPI DOUYIN_JSAPI("douyin_jsapi", PayProviderEnum.DOUYIN), - /// 抖音H5支付 + /// 抖音H5 DOUYIN_H5("douyin_h5", PayProviderEnum.DOUYIN), - /// 抖音APP支付 + /// 抖音APP DOUYIN_APP("douyin_app", PayProviderEnum.DOUYIN), /* 卡组 */ - /// Visa 网关支付 + /// Visa网关 VISA_CARD_GATEWAY("visa_card_gateway", PayProviderEnum.VISA), - /// Visa 刷卡支付 + /// Visa刷卡 VISA_CARD_PRESENT("visa_card_present", PayProviderEnum.VISA), - /// 万事达网关支付 + /// 万事达网关 MASTERCARD_CARD_GATEWAY("mastercard_card_gateway", PayProviderEnum.MASTERCARD), - /// 万事达刷卡支付 + /// 万事达刷卡 MASTERCARD_CARD_PRESENT("mastercard_card_present", PayProviderEnum.MASTERCARD), - /// 其他支付方式(无固定渠道归属) + /// 其他(无固定渠道归属) OTHER("other", null), ;