feat(code-pay): method 判定 needOpenId,DIRECT 按能力反推 method

This commit is contained in:
DaxPay Dev
2026-07-17 22:48:25 +08:00
parent 624a44711b
commit d53b310df2
17 changed files with 190 additions and 53 deletions

View File

@@ -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<List<LabelValue>> 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<List<LabelValue>> listDirectCapabilityCandidates(
@NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo) {
return Res.ok(payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo));
}
}

View File

@@ -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<String> 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");
}
}
}

View File

@@ -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());
}
};

View File

@@ -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(

View File

@@ -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;

View File

@@ -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<String> 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()));
}
/// 聚合扫码发起支付

View File

@@ -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) {

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -10,5 +10,8 @@
"idsEmpty": "QRコードボードを選択してください",
"clientEnvNotSupport": "現在の環境ではコードプレート決済に対応していません。WeChat/Alipayなどでスキャンしてください",
"amountRequired": "支払金額を入力してください",
"programTypeNotFound": "対応するコード牌プログラム種別が見つかりません: {0}"
"programTypeNotFound": "対応するコード牌プログラム種別が見つかりません: {0}",
"openIdRequired": "この支払い方法ではユーザー認可が必要です。先に認可を完了してください",
"authNotRequired": "この支払い方法では認可は不要です",
"storeBindMchInconsistent": "店舗を一括バインドする場合、同一加盟店のコード札を選択してください"
}

View File

@@ -10,5 +10,8 @@
"idsEmpty": "QR 코드 보드를 선택하세요",
"clientEnvNotSupport": "현재 환경에서는 코드플레이트 결제를 지원하지 않습니다. WeChat/Alipay 등으로 스캔하세요",
"amountRequired": "결제 금액을 입력하세요",
"programTypeNotFound": "일치하는 코드 프로그램 유형을 찾을 수 없습니다: {0}"
"programTypeNotFound": "일치하는 코드 프로그램 유형을 찾을 수 없습니다: {0}",
"openIdRequired": "현재 결제 방식은 사용자 승인이 필요합니다. 먼저 승인을 완료해 주세요",
"authNotRequired": "현재 결제 방식은 승인이 필요하지 않습니다",
"storeBindMchInconsistent": "매장 일괄 바인딩 시 동일 가맹점의 코드판을 선택해야 합니다"
}

View File

@@ -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"
}

View File

@@ -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": "เมื่อผูกสาขาแบบชุดต้องเลือกป้ายรหัสภายใต้ร้านค้าเดียวกัน"
}

View File

@@ -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"
}

View File

@@ -10,5 +10,8 @@
"idsEmpty": "请选择码牌",
"clientEnvNotSupport": "当前打开环境不支持码牌支付, 请使用微信/支付宝等扫码",
"amountRequired": "请输入支付金额",
"programTypeNotFound": "未找到对应的码牌程序类型: {0}"
"programTypeNotFound": "未找到对应的码牌程序类型: {0}",
"openIdRequired": "当前支付方式需要用户授权,请先完成授权后重试",
"authNotRequired": "当前支付方式无需授权",
"storeBindMchInconsistent": "批量绑定门店时须选择同一商户下的码牌"
}

View File

@@ -10,5 +10,8 @@
"idsEmpty": "請選擇碼牌",
"clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼",
"amountRequired": "請輸入支付金額",
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}"
"openIdRequired": "目前支付方式需要使用者授權,請先完成授權後重試",
"authNotRequired": "目前支付方式無需授權",
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}",
"storeBindMchInconsistent": "批量綁定門店時須選擇同一商戶下的碼牌"
}

View File

@@ -10,5 +10,8 @@
"idsEmpty": "請選擇碼牌",
"clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼",
"amountRequired": "請輸入支付金額",
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}"
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}",
"openIdRequired": "目前支付方式需要使用者授權,請先完成授權後重試",
"authNotRequired": "目前支付方式無需授權",
"storeBindMchInconsistent": "批量綁定門店時須選擇同一商戶下的碼牌"
}