From d53b310df2e714fa1e65db962d1c71537468eb38 Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Fri, 17 Jul 2026 22:48:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(code-pay):=20method=20=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=20needOpenId=EF=BC=8CDIRECT=20=E6=8C=89=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E5=8F=8D=E6=8E=A8=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GatewayCodeConfigAdminController.java | 24 +++++++++ .../common/util/PayMethodOpenIdSupport.java | 51 +++++++++++++++++++ .../gateway/ClientEnvPayResolveService.java | 27 ++++++++-- .../gateway/CodePayResolveService.java | 28 ++++++++-- .../service/runtime/PayRouteService.java | 6 ++- .../pay/gateway/AggregatePayService.java | 15 ++---- .../client/service/CodePayAssistService.java | 42 +++++++-------- .../i18n/en-US/error/device/qrcode.json | 5 +- .../i18n/id-ID/error/device/qrcode.json | 5 +- .../i18n/ja-JP/error/device/qrcode.json | 5 +- .../i18n/ko-KR/error/device/qrcode.json | 5 +- .../i18n/ms-MY/error/device/qrcode.json | 5 +- .../i18n/th-TH/error/device/qrcode.json | 5 +- .../i18n/vi-VN/error/device/qrcode.json | 5 +- .../i18n/zh-CN/error/device/qrcode.json | 5 +- .../i18n/zh-HK/error/device/qrcode.json | 5 +- .../i18n/zh-TW/error/device/qrcode.json | 5 +- 17 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/common/util/PayMethodOpenIdSupport.java diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/gateway/GatewayCodeConfigAdminController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/gateway/GatewayCodeConfigAdminController.java index df055965f..006b0e012 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/gateway/GatewayCodeConfigAdminController.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/merchant/gateway/GatewayCodeConfigAdminController.java @@ -3,10 +3,12 @@ package cn.daxpay.open.payment.admin.controller.merchant.gateway; import cn.daxpay.open.payment.merchant.param.gateway.GatewayCodeConfigParam; import cn.daxpay.open.payment.merchant.result.gateway.GatewayCodeConfigResult; import cn.daxpay.open.payment.merchant.service.gateway.GatewayCodeConfigService; +import cn.daxpay.open.payment.route.service.support.PayRouteStrategyCapabilitySupport; 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 cn.daxpay.open.platform.core.rest.dto.LabelValue; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotBlank; @@ -14,6 +16,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.List; + /// # 码牌支付策略配置(管理) @PermCode(menuCode = PermCodes.Merchant.GatewayCode.MENU) @Validated @@ -24,6 +28,7 @@ import org.springframework.web.bind.annotation.*; public class GatewayCodeConfigAdminController { private final GatewayCodeConfigService gatewayCodeConfigService; + private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport; @PermCode(code = PermCodes.Action.VIEW) @Operation(summary = "按应用查询码牌支付配置") @@ -40,4 +45,23 @@ public class GatewayCodeConfigAdminController { gatewayCodeConfigService.saveOrUpdate(param); return Res.ok(); } + + /// DIRECT 模式: 按商户+支付渠道列通道商户(不绑死默认 JSAPI method) + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "直接指定-通道商户候选") + @GetMapping("/direct-channel-mch-candidates") + public Result> listDirectChannelMchCandidates( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.provider.notBlank}") String provider) { + return Res.ok(payRouteStrategyCapabilitySupport.listDirectChannelMchCandidates(mchNo, provider)); + } + + /// DIRECT 模式: 按通道商户列全部已挂载支付能力(不按默认 method 过滤,便于选 H5/主扫等) + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "直接指定-支付能力候选") + @GetMapping("/direct-capability-candidates") + public Result> listDirectCapabilityCandidates( + @NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo) { + return Res.ok(payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo)); + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/common/util/PayMethodOpenIdSupport.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/common/util/PayMethodOpenIdSupport.java new file mode 100644 index 000000000..a3d178511 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/common/util/PayMethodOpenIdSupport.java @@ -0,0 +1,51 @@ +package cn.daxpay.open.payment.common.util; + +import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum; +import cn.hutool.core.util.StrUtil; + +import java.util.Set; + +/// # 支付方式是否需要买家 openId / userId +/// +/// 供码牌 H5 OAuth 门控、聚合 meta、下单前校验共用。 +/// 与交互形态绑定(JSAPI/小程序需用户标识;H5/主扫等一般不需要),**不**挂产品/Gateway 策略。 +/// +/// 换票时序:授权回跳页立即 code→openId,支付只传 openId,禁止支付时再换 code。 +public final class PayMethodOpenIdSupport { + + /// 需要买家标识的支付方式(与聚合历史白名单一致) + private static final Set METHODS_NEED_OPEN_ID = Set.of( + PayMethodEnum.WECHAT_JSAPI.getCode(), + PayMethodEnum.WECHAT_MINI.getCode(), + PayMethodEnum.ALIPAY_JSAPI.getCode(), + PayMethodEnum.UNION_JSAPI.getCode(), + PayMethodEnum.DOUYIN_JSAPI.getCode() + ); + + private PayMethodOpenIdSupport() { + } + + /// 该支付方式下单前是否需要 openId/userId + /// + /// @param methodCode 支付方式编码,空则 false + public static boolean needsOpenId(String methodCode) { + if (StrUtil.isBlank(methodCode)) { + return false; + } + if (METHODS_NEED_OPEN_ID.contains(methodCode)) { + return true; + } + try { + PayMethodEnum method = PayMethodEnum.findByCode(methodCode); + return switch (method) { + case WECHAT_JSAPI, WECHAT_MINI, ALIPAY_JSAPI, UNION_JSAPI, DOUYIN_JSAPI -> true; + default -> false; + }; + } + catch (Exception e) { + // 未知扩展 method: 含 jsapi/mini 视作需要 + String lower = methodCode.toLowerCase(); + return lower.contains("jsapi") || lower.contains("mini"); + } + } +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/ClientEnvPayResolveService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/ClientEnvPayResolveService.java index 102b99511..c836e839a 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/ClientEnvPayResolveService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/ClientEnvPayResolveService.java @@ -7,7 +7,10 @@ import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateConfig; import cn.daxpay.open.payment.merchant.enums.AggregateConfigLevelEnum; import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum; import cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum; +import cn.daxpay.open.payment.route.service.runtime.PayRouteService; +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.PayCapabilityEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; @@ -19,6 +22,7 @@ import org.springframework.stereotype.Service; /// 读应用级**聚合**扫码配置(AUTO/METHOD/DIRECT), 输出 method / channelMchNo / capability。 /// 仅供 [cn.daxpay.open.payment.trade.runtime.service.pay.gateway.AggregatePayService] 使用。 /// 码牌支付请使用 [CodePayResolveService], 不再读本服务。 +/// DIRECT: 与路由直接指定对齐——method 空时由 channelMch+capability 反推。 @Slf4j @Service @RequiredArgsConstructor @@ -26,6 +30,7 @@ public class ClientEnvPayResolveService { private final GatewayAggregateConfigManager configManager; private final GatewayAggregateClientEnvManager clientEnvManager; + private final PayRouteService payRouteService; /// 解析结果 public record Resolved(String method, String channelMchNo, String capability) {} @@ -55,13 +60,27 @@ public class ClientEnvPayResolveService { } case DIRECT -> { GatewayAggregateClientEnv envConfig = requireEnvConfig(config, clientEnv); - if (StrUtil.isBlank(envConfig.getChannelMchNo())) { + if (StrUtil.isBlank(envConfig.getChannelMchNo()) || StrUtil.isBlank(envConfig.getCapability())) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.gateway.clientEnvNotConfigured"); } - String method = StrUtil.isNotBlank(envConfig.getMethod()) - ? ClientEnvEnum.adaptMethodForRuntime(envConfig.getMethod(), rt) - : clientEnv.defaultMethodCode(rt); + String method; + if (StrUtil.isNotBlank(envConfig.getMethod())) { + method = ClientEnvEnum.adaptMethodForRuntime(envConfig.getMethod(), rt); + } + else { + // 与码牌/路由 DIRECT 一致:按能力反推,禁止静默默认 JSAPI + String inferred = payRouteService.inferMethodForCapability( + envConfig.getChannelMchNo(), envConfig.getCapability()); + if (StrUtil.isBlank(inferred)) { + PayCapabilityEnum capEnum = PayCapabilityEnum.findByCode(envConfig.getCapability()); + String capLabel = capEnum != null ? I18nUtil.getEnumName(capEnum) : envConfig.getCapability(); + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.route.error.directCapabilityChannelMchMismatch", + capLabel, envConfig.getChannelMchNo()); + } + method = ClientEnvEnum.adaptMethodForRuntime(inferred, rt); + } yield new Resolved(method, envConfig.getChannelMchNo(), envConfig.getCapability()); } }; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/CodePayResolveService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/CodePayResolveService.java index a6f738b12..f59cec222 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/CodePayResolveService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/CodePayResolveService.java @@ -7,7 +7,10 @@ import cn.daxpay.open.payment.merchant.entity.gateway.GatewayCodeConfig; import cn.daxpay.open.payment.merchant.enums.AggregateConfigLevelEnum; import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum; import cn.daxpay.open.payment.merchant.enums.CodePayFormEnum; +import cn.daxpay.open.payment.route.service.runtime.PayRouteService; +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.PayCapabilityEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; @@ -18,6 +21,7 @@ import org.springframework.stereotype.Service; /// /// 仅读 [GatewayCodeConfig] 系列表; **不回落**聚合配置。 /// METHOD/DIRECT 使用子表配置字面值, **不做** jsapi→mini 隐式升级。 +/// DIRECT: 与路由直接指定对齐——method 空时由 channelMch+capability 反推,禁止静默默认 JSAPI。 @Slf4j @Service @RequiredArgsConstructor @@ -25,6 +29,7 @@ public class CodePayResolveService { private final GatewayCodeConfigManager configManager; private final GatewayCodeClientEnvManager clientEnvManager; + private final PayRouteService payRouteService; /// 解析结果 public record Resolved(String method, String channelMchNo, String capability) {} @@ -49,18 +54,33 @@ public class CodePayResolveService { } case DIRECT -> { GatewayCodeClientEnv envConfig = requireEnvConfig(config, clientEnv, form); - if (StrUtil.isBlank(envConfig.getChannelMchNo())) { + if (StrUtil.isBlank(envConfig.getChannelMchNo()) || StrUtil.isBlank(envConfig.getCapability())) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.gateway.codeClientEnvNotConfigured"); } - String method = StrUtil.isNotBlank(envConfig.getMethod()) - ? envConfig.getMethod() - : form.defaultMethodCode(clientEnv); + String method = resolveDirectMethod( + envConfig.getMethod(), envConfig.getChannelMchNo(), envConfig.getCapability()); yield new Resolved(method, envConfig.getChannelMchNo(), envConfig.getCapability()); } }; } + /// DIRECT: 优先用已配 method;否则按通道商户+能力反推(与 [PayRouteService#inferMethodForCapability] 一致) + private String resolveDirectMethod(String configuredMethod, String channelMchNo, String capability) { + if (StrUtil.isNotBlank(configuredMethod)) { + return configuredMethod; + } + String inferred = payRouteService.inferMethodForCapability(channelMchNo, capability); + if (StrUtil.isBlank(inferred)) { + PayCapabilityEnum capEnum = PayCapabilityEnum.findByCode(capability); + String capLabel = capEnum != null ? I18nUtil.getEnumName(capEnum) : capability; + // 支付能力与通道商户不匹配(无法反推 method) + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.route.error.directCapabilityChannelMchMismatch", capLabel, channelMchNo); + } + return inferred; + } + private GatewayCodeClientEnv requireEnvConfig( GatewayCodeConfig config, ClientEnvEnum clientEnv, CodePayFormEnum payForm) { GatewayCodeClientEnv envConfig = clientEnvManager.findByConfigIdAndClientEnvAndPayForm( diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/route/service/runtime/PayRouteService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/route/service/runtime/PayRouteService.java index d513a8444..dd07fa42d 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/route/service/runtime/PayRouteService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/route/service/runtime/PayRouteService.java @@ -112,8 +112,10 @@ public class PayRouteService { } /// 直接指定: 由(通道商户, 支付能力)反推支付方式编码(策略 Map + DB 启用, 无则 null) - /// 查询次数与迁前一致:1 次通道商户 + 能力挂载/主数据检查 - private String inferMethodForCapability(String channelMchNo, String capabilityCode) { + /// + /// 供码牌/聚合 DIRECT 解析与路由直接指定共用,避免两处默认 method 语义漂移。 + /// 查询次数:1 次通道商户 + 能力挂载/主数据检查 + public String inferMethodForCapability(String channelMchNo, String capabilityCode) { String product = channelMerchantManager.findProductByChannelMchNo(channelMchNo); if (StrUtil.isBlank(product) || !PaymentStrategyFactory.existsByProduct(product, AbsProductStrategy.class)) { return null; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/AggregatePayService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/AggregatePayService.java index d5ce450f0..cf0f9d9ca 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/AggregatePayService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/AggregatePayService.java @@ -1,5 +1,6 @@ package cn.daxpay.open.payment.trade.runtime.service.pay.gateway; +import cn.daxpay.open.payment.common.util.PayMethodOpenIdSupport; import cn.daxpay.open.payment.merchant.dao.gateway.GatewayAggregateConfigManager; import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateConfig; import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum; @@ -12,7 +13,6 @@ import cn.daxpay.open.payment.unipay.result.gateway.AggregatePayMetaResult; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult; import cn.daxpay.open.platform.common.spring.util.WebServletUtil; import cn.daxpay.open.platform.core.code.CommonErrorCode; -import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; @@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Objects; -import java.util.Set; /// # 聚合扫码支付服务 /// @@ -30,15 +29,6 @@ import java.util.Set; @RequiredArgsConstructor public class AggregatePayService { - /// 需要买家标识(openId/userId)的支付方式 - private static final Set METHODS_NEED_OPEN_ID = Set.of( - PayMethodEnum.WECHAT_JSAPI.getCode(), - PayMethodEnum.WECHAT_MINI.getCode(), - PayMethodEnum.ALIPAY_JSAPI.getCode(), - PayMethodEnum.UNION_JSAPI.getCode(), - PayMethodEnum.DOUYIN_JSAPI.getCode() - ); - private final GatewayPayAssistService gatewayPayAssistService; private final ClientEnvPayResolveService clientEnvPayResolveService; private final GatewayPayHandleService gatewayPayHandleService; @@ -60,7 +50,8 @@ public class AggregatePayService { return new AggregatePayMetaResult() .setAutoLaunch(Boolean.TRUE.equals(config.getAutoLaunch())) - .setNeedOpenId(METHODS_NEED_OPEN_ID.contains(resolved.method())); + // 与码牌同源: 按 method 判定是否需 OAuth 取 openId + .setNeedOpenId(PayMethodOpenIdSupport.needsOpenId(resolved.method())); } /// 聚合扫码发起支付 diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/service/CodePayAssistService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/service/CodePayAssistService.java index c7764519d..5c6d941c3 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/service/CodePayAssistService.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/service/CodePayAssistService.java @@ -2,6 +2,7 @@ package cn.daxpay.open.payment.unipay.client.service; import cn.daxpay.open.payment.auth.ChannelAuthFacade; import cn.daxpay.open.payment.common.context.MerchantContextLoader; +import cn.daxpay.open.payment.common.util.PayMethodOpenIdSupport; import cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum; import cn.daxpay.open.payment.device.enums.QrCodeStatusEnum; import cn.daxpay.open.payment.device.qrcode.dao.DeviceQrCodeManager; @@ -22,12 +23,12 @@ import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam; import cn.daxpay.open.payment.unipay.param.device.CodePayAuthUrlParam; import cn.daxpay.open.payment.unipay.param.device.CodePayParam; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam; +import cn.daxpay.open.payment.unipay.param.trade.pay.TerminalInfo; import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult; import cn.daxpay.open.platform.common.spring.util.WebServletUtil; import cn.daxpay.open.platform.core.code.CommonCode; import cn.daxpay.open.platform.core.code.CommonErrorCode; -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.pay.trade.TradeSourceEnum; import cn.daxpay.open.platform.core.enums.unipay.ChannelAuthTypeEnum; @@ -80,11 +81,12 @@ public class CodePayAssistService { if (StrUtil.isNotBlank(clientEnv)) { try { String method = this.resolveMethod(entity, clientEnv); - result.setNeedOpenId(this.needOpenId(method)); + // 严格按 method 判定;非 JSAPI/MINI 不强制 OAuth + result.setNeedOpenId(PayMethodOpenIdSupport.needsOpenId(method)); } catch (Exception e) { - // 策略未配置等: 默认 JSAPI 路径需要 openId, 避免前端跳过授权 + // 策略未配置等: 不误强制授权(false);支付时会因策略失败再报错 log.warn("码牌 needOpenId 解析失败 code={} clientEnv={}: {}", code, clientEnv, e.getMessage()); - result.setNeedOpenId(true); + result.setNeedOpenId(false); } } return result; @@ -107,6 +109,10 @@ public class CodePayAssistService { validateRuntimeMatchesPayForm(param.getRuntime(), payForm); var resolved = codePayResolveService.resolveRequired(mchApp.getAppId(), clientEnv, payForm); + // JSAPI/MINI 必须已在授权回跳页换好 openId,支付只带 openId、禁止支付时再换 code + if (PayMethodOpenIdSupport.needsOpenId(resolved.method()) && StrUtil.isBlank(param.getOpenId())) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.openIdRequired"); + } String codeName = StrUtil.blankToDefault(entity.getName(), entity.getCode()); String amountYuan = String.format("%.2f", amount / 100.0); @@ -125,11 +131,18 @@ public class CodePayAssistService { payParam.setOpenId(param.getOpenId()); payParam.setClientIp(StrUtil.blankToDefault(param.getClientIp(), WebServletUtil.getClientIp())); payParam.setSource(TradeSourceEnum.CASHIER_CODE.getCode()); + // 门店: 码牌显式 storeNo 优先; 空则由下单侧 resolve 默认门店, 仍注入 terminal 便于统一路径 + // 此处只传码牌上的显式值(可空), NormalPayAssistService 再 resolveStoreNo + TerminalInfo terminal = new TerminalInfo(); + terminal.setStoreNo(entity.getStoreNo()); + payParam.setTerminal(terminal); return normalPayService.pay(payParam); } /// 生成码牌 OAuth 授权链接(公开, 按码牌解析商户/策略/路由) + /// + /// 仅 needOpenId 的 method 应调用;回跳落地页立即 code→openId,非支付时再换。 public AuthUrlResult generateAuthUrl(CodePayAuthUrlParam param) { DeviceQrCode entity = this.loadEnabledAssigned(param.getCode()); merchantContextLoader.initMch(entity.getMchNo()); @@ -141,6 +154,10 @@ public class CodePayAssistService { } CodePayFormEnum payForm = CodePayFormEnum.fromProgramType(entity.getProgramType()); var resolved = codePayResolveService.resolveRequired(mchApp.getAppId(), clientEnv, payForm); + if (!PayMethodOpenIdSupport.needsOpenId(resolved.method())) { + // 当前支付方式无需授权(H5/主扫等) + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.authNotRequired"); + } // 跟随支付同路径路由, 拿到 product/channelMchNo/capability 供通道认证 NormalPayParam routeParam = new NormalPayParam(); @@ -243,23 +260,6 @@ public class CodePayAssistService { return codePayResolveService.resolveRequired(mchApp.getAppId(), clientEnv, payForm).method(); } - /// method 是否需要用户标识(JSAPI/小程序类) - private boolean needOpenId(String methodCode) { - if (StrUtil.isBlank(methodCode)) { - return false; - } - try { - PayMethodEnum method = PayMethodEnum.findByCode(methodCode); - return switch (method) { - case WECHAT_JSAPI, WECHAT_MINI, ALIPAY_JSAPI, UNION_JSAPI, DOUYIN_JSAPI -> true; - default -> false; - }; - } catch (Exception e) { - // 未知 method: 含 jsapi/mini 视作需要 - return methodCode.contains("jsapi") || methodCode.contains("mini"); - } - } - /// 分端页 returnPath: /h/wechat|{alipay}/:code?authed=1 private String buildReturnPath(ClientEnvEnum clientEnv, String code) { String segment = switch (clientEnv) { diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/device/qrcode.json index cd6944c3d..4887894f6 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "Please select QR code boards", "clientEnvNotSupport": "Current client environment does not support code-plate pay. Please scan with WeChat/Alipay etc.", "amountRequired": "Please enter the payment amount", - "programTypeNotFound": "No matching QR code program type found: {0}" + "programTypeNotFound": "No matching QR code program type found: {0}", + "openIdRequired": "This payment method requires user authorization. Please complete authorization first", + "authNotRequired": "This payment method does not require authorization", + "storeBindMchInconsistent": "Selected QR code boards must belong to the same merchant when binding a store" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/device/qrcode.json index cd6944c3d..302571b15 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "Please select QR code boards", "clientEnvNotSupport": "Current client environment does not support code-plate pay. Please scan with WeChat/Alipay etc.", "amountRequired": "Please enter the payment amount", - "programTypeNotFound": "No matching QR code program type found: {0}" + "programTypeNotFound": "No matching QR code program type found: {0}", + "openIdRequired": "Metode pembayaran ini memerlukan otorisasi pengguna. Selesaikan otorisasi terlebih dahulu", + "authNotRequired": "Metode pembayaran ini tidak memerlukan otorisasi", + "storeBindMchInconsistent": "Saat mengikat toko secara batch, pilih papan kode dari merchant yang sama" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/device/qrcode.json index b440fb408..b65f9795e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "QRコードボードを選択してください", "clientEnvNotSupport": "現在の環境ではコードプレート決済に対応していません。WeChat/Alipayなどでスキャンしてください", "amountRequired": "支払金額を入力してください", - "programTypeNotFound": "対応するコード牌プログラム種別が見つかりません: {0}" + "programTypeNotFound": "対応するコード牌プログラム種別が見つかりません: {0}", + "openIdRequired": "この支払い方法ではユーザー認可が必要です。先に認可を完了してください", + "authNotRequired": "この支払い方法では認可は不要です", + "storeBindMchInconsistent": "店舗を一括バインドする場合、同一加盟店のコード札を選択してください" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/device/qrcode.json index 33f082d0f..985b5cd01 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "QR 코드 보드를 선택하세요", "clientEnvNotSupport": "현재 환경에서는 코드플레이트 결제를 지원하지 않습니다. WeChat/Alipay 등으로 스캔하세요", "amountRequired": "결제 금액을 입력하세요", - "programTypeNotFound": "일치하는 코드 프로그램 유형을 찾을 수 없습니다: {0}" + "programTypeNotFound": "일치하는 코드 프로그램 유형을 찾을 수 없습니다: {0}", + "openIdRequired": "현재 결제 방식은 사용자 승인이 필요합니다. 먼저 승인을 완료해 주세요", + "authNotRequired": "현재 결제 방식은 승인이 필요하지 않습니다", + "storeBindMchInconsistent": "매장 일괄 바인딩 시 동일 가맹점의 코드판을 선택해야 합니다" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/device/qrcode.json index cd6944c3d..9afa8fd69 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "Please select QR code boards", "clientEnvNotSupport": "Current client environment does not support code-plate pay. Please scan with WeChat/Alipay etc.", "amountRequired": "Please enter the payment amount", - "programTypeNotFound": "No matching QR code program type found: {0}" + "programTypeNotFound": "No matching QR code program type found: {0}", + "openIdRequired": "This payment method requires user authorization. Please complete authorization first", + "authNotRequired": "This payment method does not require authorization", + "storeBindMchInconsistent": "Apabila mengikat kedai secara kelompok, pilih papan kod di bawah peniaga yang sama" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/device/qrcode.json index cd6944c3d..92605c332 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "Please select QR code boards", "clientEnvNotSupport": "Current client environment does not support code-plate pay. Please scan with WeChat/Alipay etc.", "amountRequired": "Please enter the payment amount", - "programTypeNotFound": "No matching QR code program type found: {0}" + "programTypeNotFound": "No matching QR code program type found: {0}", + "openIdRequired": "This payment method requires user authorization. Please complete authorization first", + "authNotRequired": "This payment method does not require authorization", + "storeBindMchInconsistent": "เมื่อผูกสาขาแบบชุดต้องเลือกป้ายรหัสภายใต้ร้านค้าเดียวกัน" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/device/qrcode.json index cd6944c3d..6e5af045d 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "Please select QR code boards", "clientEnvNotSupport": "Current client environment does not support code-plate pay. Please scan with WeChat/Alipay etc.", "amountRequired": "Please enter the payment amount", - "programTypeNotFound": "No matching QR code program type found: {0}" + "programTypeNotFound": "No matching QR code program type found: {0}", + "openIdRequired": "This payment method requires user authorization. Please complete authorization first", + "authNotRequired": "This payment method does not require authorization", + "storeBindMchInconsistent": "Khi gán cửa hàng hàng loạt, phải chọn mã thuộc cùng một thương nhân" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/device/qrcode.json index e02fff223..e4153100d 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "请选择码牌", "clientEnvNotSupport": "当前打开环境不支持码牌支付, 请使用微信/支付宝等扫码", "amountRequired": "请输入支付金额", - "programTypeNotFound": "未找到对应的码牌程序类型: {0}" + "programTypeNotFound": "未找到对应的码牌程序类型: {0}", + "openIdRequired": "当前支付方式需要用户授权,请先完成授权后重试", + "authNotRequired": "当前支付方式无需授权", + "storeBindMchInconsistent": "批量绑定门店时须选择同一商户下的码牌" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/device/qrcode.json index a9710294f..6977e7e8c 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "請選擇碼牌", "clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼", "amountRequired": "請輸入支付金額", - "programTypeNotFound": "未找到對應的碼牌程式類型: {0}" + "openIdRequired": "目前支付方式需要使用者授權,請先完成授權後重試", + "authNotRequired": "目前支付方式無需授權", + "programTypeNotFound": "未找到對應的碼牌程式類型: {0}", + "storeBindMchInconsistent": "批量綁定門店時須選擇同一商戶下的碼牌" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/device/qrcode.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/device/qrcode.json index 3be268d84..6b6981a9b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/device/qrcode.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/device/qrcode.json @@ -10,5 +10,8 @@ "idsEmpty": "請選擇碼牌", "clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼", "amountRequired": "請輸入支付金額", - "programTypeNotFound": "未找到對應的碼牌程式類型: {0}" + "programTypeNotFound": "未找到對應的碼牌程式類型: {0}", + "openIdRequired": "目前支付方式需要使用者授權,請先完成授權後重試", + "authNotRequired": "目前支付方式無需授權", + "storeBindMchInconsistent": "批量綁定門店時須選擇同一商戶下的碼牌" }