diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index f5743ae7d..44eb5924d 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -2,6 +2,41 @@ ALTER TABLE pay_normal_order RENAME COLUMN bar_code TO auth_code; COMMENT ON COLUMN pay_normal_order.auth_code IS '付款码(被扫支付,审计保留)'; +-- 订单来源: 容器权威 + pay_trade 冗余(方案 C) +ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS source varchar(32); +COMMENT ON COLUMN pay_normal_order.source IS '订单来源(业务入口权威, TradeSourceEnum)'; + +ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS source varchar(32); +COMMENT ON COLUMN pay_gateway_order.source IS '订单来源(业务入口权威, TradeSourceEnum; 预下单写入)'; + +-- 历史数据: 从 pay_trade 回填容器(仅空值) +UPDATE pay_normal_order o +SET source = t.source +FROM pay_trade t +WHERE t.container_id = o.id + AND t.trade_type = 'normal' + AND t.deleted = false + AND o.source IS NULL + AND t.source IS NOT NULL; + +UPDATE pay_gateway_order o +SET source = t.source +FROM pay_trade t +WHERE t.container_id = o.id + AND t.trade_type = 'gateway' + AND t.deleted = false + AND o.source IS NULL + AND t.source IS NOT NULL; + +-- 网关预下单尚未建 trade 的: 按 gateway_type 派生 +UPDATE pay_gateway_order +SET source = CASE gateway_type + WHEN 'cashier' THEN 'cashier' + ELSE 'aggress_pay' +END +WHERE source IS NULL + AND gateway_type IS NOT NULL; + -- ============================================================ -- 易支付协议插件表 -- ============================================================ @@ -151,3 +186,23 @@ COMMENT ON COLUMN pay_easy_pay_order.last_modifier IS '最后修改人'; COMMENT ON COLUMN pay_easy_pay_order.last_modified_time IS '最后修改时间'; COMMENT ON COLUMN pay_easy_pay_order.version IS '乐观锁版本'; COMMENT ON COLUMN pay_easy_pay_order.deleted IS '逻辑删除'; + +-- ============================================================ +-- 订单门店维度: store_no(线下经营归属,可空) + +-- ============================================================ +-- 订单门店维度: store_no(线下经营归属,可空) +-- ============================================================ +ALTER TABLE pay_normal_order ADD COLUMN IF NOT EXISTS store_no varchar(32); +COMMENT ON COLUMN pay_normal_order.store_no IS '门店号(线下经营归属, 可空; 对应 mch_store_info.store_no)'; + +ALTER TABLE pay_gateway_order ADD COLUMN IF NOT EXISTS store_no varchar(32); +COMMENT ON COLUMN pay_gateway_order.store_no IS '门店号(线下经营归属, 可空; 对应 mch_store_info.store_no)'; + +ALTER TABLE pay_refund_order ADD COLUMN IF NOT EXISTS store_no varchar(32); +COMMENT ON COLUMN pay_refund_order.store_no IS '门店号(继承自原支付容器, 可空)'; + +CREATE INDEX IF NOT EXISTS idx_normal_order_mch_store ON pay_normal_order (mch_no, store_no); +CREATE INDEX IF NOT EXISTS idx_gateway_order_mch_store ON pay_gateway_order (mch_no, store_no); +CREATE INDEX IF NOT EXISTS idx_refund_order_mch_store ON pay_refund_order (mch_no, store_no); + diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientEnvEnum.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientEnvEnum.java index 14bd8bf6d..145a4dccf 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientEnvEnum.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientEnvEnum.java @@ -5,6 +5,7 @@ import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.i18n.I18nSupport; +import cn.hutool.core.util.StrUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -47,19 +48,50 @@ public enum ClientEnvEnum implements I18nSupport { .orElseThrow(() -> new DataNotExistException("error.common.clientEnvNotExist", code)); } - /// 聚合扫码 L1 AUTO:按客户端环境推导默认支付方式编码(原生环境一律 JSAPI) + /// 聚合扫码 L1 AUTO:按客户端环境推导默认支付方式(H5 默认 JSAPI) public String defaultMethodCode() { - PayMethodEnum method = switch (this) { - case WECHAT_PAY -> PayMethodEnum.WECHAT_JSAPI; - case ALIPAY -> PayMethodEnum.ALIPAY_JSAPI; - case UNION_PAY -> PayMethodEnum.UNION_JSAPI; - case DOUYIN -> PayMethodEnum.DOUYIN_JSAPI; - // browser 等非聚合扫码环境不支持 - default -> null; - }; + return defaultMethodCode(ClientRuntimeEnum.H5); + } + + /// 按运行形态推导默认支付方式: H5→JSAPI, 小程序→MINI(无独立 mini 时回退 JSAPI) + public String defaultMethodCode(ClientRuntimeEnum runtime) { + ClientRuntimeEnum rt = runtime == null ? ClientRuntimeEnum.H5 : runtime; + PayMethodEnum method; + if (rt == ClientRuntimeEnum.MINI) { + method = switch (this) { + case WECHAT_PAY -> PayMethodEnum.WECHAT_MINI; + case ALIPAY -> PayMethodEnum.ALIPAY_MINI; + case UNION_PAY -> PayMethodEnum.UNION_JSAPI; + case DOUYIN -> PayMethodEnum.DOUYIN_JSAPI; + default -> null; + }; + } else { + method = switch (this) { + case WECHAT_PAY -> PayMethodEnum.WECHAT_JSAPI; + case ALIPAY -> PayMethodEnum.ALIPAY_JSAPI; + case UNION_PAY -> PayMethodEnum.UNION_JSAPI; + case DOUYIN -> PayMethodEnum.DOUYIN_JSAPI; + // browser 等非聚合扫码环境不支持 + default -> null; + }; + } if (method == null) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.gateway.clientEnvNotSupport"); } return method.getCode(); } + + /// METHOD 配置的 method 按 runtime 升级(jsapi→mini), 商户心智保持「配微信/支付宝」即可 + public static String adaptMethodForRuntime(String method, ClientRuntimeEnum runtime) { + if (StrUtil.isBlank(method) || runtime != ClientRuntimeEnum.MINI) { + return method; + } + if (PayMethodEnum.WECHAT_JSAPI.getCode().equals(method)) { + return PayMethodEnum.WECHAT_MINI.getCode(); + } + if (PayMethodEnum.ALIPAY_JSAPI.getCode().equals(method)) { + return PayMethodEnum.ALIPAY_MINI.getCode(); + } + return method; + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientRuntimeEnum.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientRuntimeEnum.java new file mode 100644 index 000000000..6924cfa16 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/enums/ClientRuntimeEnum.java @@ -0,0 +1,49 @@ +package cn.daxpay.open.payment.merchant.enums; + +import cn.daxpay.open.platform.core.exception.DataNotExistException; +import cn.daxpay.open.platform.core.i18n.I18nSupport; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/// # 客户端运行形态 +/// +/// 与 [ClientEnvEnum] 正交: env 表示钱包宿主(微信/支付宝…), runtime 表示页面容器(H5/小程序)。 +/// 字典: client_runtime +@Getter +@RequiredArgsConstructor +public enum ClientRuntimeEnum implements I18nSupport { + + /// H5 / WebView 内页面 + H5("h5"), + /// 微信/支付宝/抖音等小程序 + MINI("mini"), + ; + + private final String code; + + @Override + public String getI18nPrefix() { + return "enum.client_runtime"; + } + + public static ClientRuntimeEnum findByCode(String code) { + return Arrays.stream(values()) + .filter(e -> e.getCode().equals(code)) + .findFirst() + .orElseThrow(() -> new DataNotExistException("error.common.clientRuntimeNotExist", code)); + } + + /// 空或非法时默认 H5(一期主路径) + public static ClientRuntimeEnum ofOrDefault(String code) { + if (StrUtil.isBlank(code)) { + return H5; + } + return Arrays.stream(values()) + .filter(e -> e.getCode().equals(code)) + .findFirst() + .orElse(H5); + } +} 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 new file mode 100644 index 000000000..4eb3c89b6 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/gateway/ClientEnvPayResolveService.java @@ -0,0 +1,92 @@ +package cn.daxpay.open.payment.merchant.service.gateway; + +import cn.daxpay.open.payment.merchant.dao.gateway.GatewayAggregateClientEnvManager; +import cn.daxpay.open.payment.merchant.dao.gateway.GatewayAggregateConfigManager; +import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateClientEnv; +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.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 客户端环境支付方式解析(共享) +/// +/// 读应用级聚合扫码配置(AUTO/METHOD/DIRECT), 输出 method / channelMchNo / capability。 +/// 供 [cn.daxpay.open.payment.trade.runtime.service.pay.gateway.AggregatePayService] 与码牌支付共用。 +/// 无配置记录时按 AUTO 降级(仅码牌友好; 聚合仍可走 getRequired)。 +@Slf4j +@Service +@RequiredArgsConstructor +public class ClientEnvPayResolveService { + + private final GatewayAggregateConfigManager configManager; + private final GatewayAggregateClientEnvManager clientEnvManager; + + /// 解析结果 + public record Resolved(String method, String channelMchNo, String capability) {} + + /// 按应用聚合配置解析(无配置 → AUTO) + /// + /// @param appId 应用号 + /// @param clientEnv 客户端环境 + /// @param runtime 运行形态(默认 H5); mini 时 method 自动升级 + public Resolved resolve(String appId, ClientEnvEnum clientEnv, ClientRuntimeEnum runtime) { + ClientRuntimeEnum rt = runtime == null ? ClientRuntimeEnum.H5 : runtime; + GatewayAggregateConfig config = configManager.findByAppId(appId).orElse(null); + AggregateConfigLevelEnum level = config == null + ? AggregateConfigLevelEnum.AUTO + : AggregateConfigLevelEnum.findByCode(config.getLevel()); + + return switch (level) { + case AUTO -> new Resolved(clientEnv.defaultMethodCode(rt), null, null); + case METHOD -> { + GatewayAggregateClientEnv envConfig = requireEnvConfig(config, clientEnv); + if (StrUtil.isBlank(envConfig.getMethod())) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.error.gateway.clientEnvNotConfigured"); + } + String method = ClientEnvEnum.adaptMethodForRuntime(envConfig.getMethod(), rt); + yield new Resolved(method, null, null); + } + case DIRECT -> { + GatewayAggregateClientEnv envConfig = requireEnvConfig(config, clientEnv); + if (StrUtil.isBlank(envConfig.getChannelMchNo())) { + 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); + yield new Resolved(method, envConfig.getChannelMchNo(), envConfig.getCapability()); + } + }; + } + + /// 聚合支付: 必须已有配置(与历史 getRequiredByAppId 行为一致) + public Resolved resolveRequired(String appId, ClientEnvEnum clientEnv, ClientRuntimeEnum runtime) { + if (configManager.findByAppId(appId).isEmpty()) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.error.gateway.aggregateConfigMissing"); + } + return resolve(appId, clientEnv, runtime); + } + + private GatewayAggregateClientEnv requireEnvConfig(GatewayAggregateConfig config, ClientEnvEnum clientEnv) { + if (config == null) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.error.gateway.aggregateConfigMissing"); + } + GatewayAggregateClientEnv envConfig = clientEnvManager + .findByConfigIdAndClientEnv(config.getId(), clientEnv.getCode()); + if (envConfig == null) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.error.gateway.clientEnvNotConfigured"); + } + return envConfig; + } +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/store/MchStoreInfoService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/store/MchStoreInfoService.java index 5ba8de7a1..4b73c7afc 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/store/MchStoreInfoService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/merchant/service/store/MchStoreInfoService.java @@ -11,7 +11,9 @@ import cn.daxpay.open.payment.merchant.param.store.MchStoreInfoQuery; import cn.daxpay.open.payment.merchant.result.store.MchStoreInfoResult; import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil; import cn.daxpay.open.platform.core.code.CommonCode; +import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.enums.client.ClientEnum; +import cn.daxpay.open.platform.core.enums.merchant.StoreStatusEnum; import cn.daxpay.open.platform.core.exception.BizException; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.DataNotExistException; @@ -20,12 +22,14 @@ import cn.daxpay.open.platform.core.rest.param.PageParam; import cn.daxpay.open.platform.core.rest.result.PageResult; import cn.daxpay.open.platform.iam.service.client.ClientCodeService; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; /// # 门店信息管理 @@ -133,4 +137,25 @@ public class MchStoreInfoService { } } } + + /// 下单校验门店: 空则跳过; 非空须存在、归属商户、启用 + /// + /// @param storeNo 门店号(可空) + /// @param mchNo 当前下单商户号 + public void validateStoreForPay(String storeNo, String mchNo) { + if (StrUtil.isBlank(storeNo)) { + return; + } + MchStoreInfo store = mchStoreInfoManager.findByStoreNo(storeNo) + // 商户: 门店不存在 + .orElseThrow(() -> new DataNotExistException("error.payment.merchant.storeNotFound")); + if (StrUtil.isNotBlank(mchNo) && !Objects.equals(store.getMchNo(), mchNo)) { + // 商户: 门店不属于当前商户 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.payment.merchant.storeNoMatch"); + } + if (!Objects.equals(StoreStatusEnum.ENABLE.getCode(), store.getStatus())) { + // 商户: 门店已停用 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.payment.merchant.storeDisabled"); + } + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java index dc568dee1..8b1399a95 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java @@ -44,6 +44,10 @@ public class GatewayPayOrder extends MchBaseEntity { /// @see GatewayPayTypeEnum private String gatewayType; + /// 订单来源(业务入口权威;预下单即写入,与 gatewayType 对应 cashier/aggress_pay) + /// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum + private String source; + /// 标题 private String title; @@ -177,6 +181,9 @@ public class GatewayPayOrder extends MchBaseEntity { /// 下单客户端 IP;关单/同步/退款透传通道的单一事实源,兼审计排查 private String clientIp; + /// 门店号(线下经营归属,可空;预下单写入;对应 [MchStoreInfo#storeNo]) + private String storeNo; + /// 错误信息 @TableField(updateStrategy = FieldStrategy.ALWAYS) private String errorMsg; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/NormalPayOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/NormalPayOrder.java index 6423a288d..823128dba 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/NormalPayOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/NormalPayOrder.java @@ -37,6 +37,10 @@ public class NormalPayOrder extends MchBaseEntity { /// 商户业务单号 private String bizOrderNo; + /// 订单来源(业务入口权威) + /// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum + private String source; + /// 标题 private String title; @@ -169,6 +173,9 @@ public class NormalPayOrder extends MchBaseEntity { /// 终端设备编码 private String terminalNo; + /// 门店号(线下经营归属,可空;对应 [MchStoreInfo#storeNo]) + private String storeNo; + /// 错误信息 @TableField(updateStrategy = FieldStrategy.ALWAYS) private String errorMsg; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayRefundOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayRefundOrder.java index 85c4b6192..67ff4cc89 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayRefundOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayRefundOrder.java @@ -93,6 +93,9 @@ public class PayRefundOrder extends MchBaseEntity { /// 客户端 IP private String clientIp; + /// 门店号(继承自原支付容器,可空;对应 [MchStoreInfo#storeNo]) + private String storeNo; + /// 错误信息 @TableField(updateStrategy = FieldStrategy.ALWAYS) private String errorMsg; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayTrade.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayTrade.java index 5d2175886..939881cc0 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayTrade.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/PayTrade.java @@ -60,7 +60,8 @@ public class PayTrade extends MchBaseEntity { /// 关闭时间 private OffsetDateTime closeTime; - /// 订单来源 + /// 订单来源(冗余自容器, 插件/资金列表免回表; 权威在容器 source) + /// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum private String source; /// 通道订单号(三方通道返回的订单号,通道 close/sync 调用参数 + 回调反查索引) diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java index 195edce54..878f3eac1 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java @@ -59,6 +59,11 @@ public class GatewayPayOrderQuery { @Schema(description = "支付产品") private String product; + /// 门店号 + @QueryParam(type = QueryParam.CompareTypeEnum.EQ) + @Schema(description = "门店号") + private String storeNo; + @QueryParam(type = QueryParam.CompareTypeEnum.GE, fieldName = "create_time") @Schema(description = "创建时间-开始") private OffsetDateTime createTimeStart; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/NormalPayOrderQuery.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/NormalPayOrderQuery.java index e3cffd208..09c82b96e 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/NormalPayOrderQuery.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/NormalPayOrderQuery.java @@ -57,6 +57,11 @@ public class NormalPayOrderQuery { @Schema(description = "支付产品") private String product; + /// 门店号 + @QueryParam(type = QueryParam.CompareTypeEnum.EQ) + @Schema(description = "门店号") + private String storeNo; + /// 创建时间-开始 @QueryParam(type = QueryParam.CompareTypeEnum.GE, fieldName = "create_time") @Schema(description = "创建时间-开始") diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/PayRefundOrderQuery.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/PayRefundOrderQuery.java index 063a5168f..96af4009a 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/PayRefundOrderQuery.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/PayRefundOrderQuery.java @@ -55,6 +55,11 @@ public class PayRefundOrderQuery { @Schema(description = "支付通道") private String channel; + /// 门店号 + @QueryParam(type = QueryParam.CompareTypeEnum.EQ) + @Schema(description = "门店号") + private String storeNo; + /// 创建时间-开始 @QueryParam(type = QueryParam.CompareTypeEnum.GE, fieldName = "create_time") @Schema(description = "创建时间-开始") diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java index 03e7ad414..d7d75aa5d 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java @@ -37,6 +37,10 @@ public class GatewayPayOrderResult extends BaseResult { @Schema(description = "网关类型") private String gatewayType; + /// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum + @Schema(description = "订单来源") + private String source; + @Schema(description = "标题") private String title; @@ -102,6 +106,10 @@ public class GatewayPayOrderResult extends BaseResult { @Schema(description = "客户端IP") private String clientIp; + /// 门店号(线下经营归属,可空) + @Schema(description = "门店号") + private String storeNo; + @Schema(description = "通道附加参数") private String extraParam; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/NormalPayOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/NormalPayOrderResult.java index c6538bb05..c50e7d64b 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/NormalPayOrderResult.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/NormalPayOrderResult.java @@ -36,6 +36,11 @@ public class NormalPayOrderResult extends BaseResult { @Schema(description = "商户业务单号") private String bizOrderNo; + /// 订单来源 + /// @see cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum + @Schema(description = "订单来源") + private String source; + /// 标题 @Schema(description = "标题") private String title; @@ -113,6 +118,10 @@ public class NormalPayOrderResult extends BaseResult { @Schema(description = "终端设备编码") private String terminalNo; + /// 门店号(线下经营归属,可空) + @Schema(description = "门店号") + private String storeNo; + // ===== 资金凭证(PayTrade)联表字段, 仅详情时填充 ===== /// 资金交易号 diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/PayRefundOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/PayRefundOrderResult.java index ad05e1b21..b1ed8eee1 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/PayRefundOrderResult.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/PayRefundOrderResult.java @@ -85,6 +85,10 @@ public class PayRefundOrderResult extends BaseResult { @Schema(description = "支付方式") private String method; + /// 门店号(继承自原支付容器,可空) + @Schema(description = "门店号") + private String storeNo; + /// 错误信息 @Schema(description = "错误信息") private String errorMsg; 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 9bbbc9c6f..6ce52ca3a 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,13 +1,10 @@ package cn.daxpay.open.payment.trade.runtime.service.pay.gateway; -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.merchant.service.gateway.ClientEnvPayResolveService; import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum; import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder; -import cn.daxpay.open.payment.merchant.dao.gateway.GatewayAggregateClientEnvManager; -import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateConfig; -import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateClientEnv; -import cn.daxpay.open.payment.merchant.service.gateway.GatewayAggregateConfigService; import cn.daxpay.open.payment.unipay.param.gateway.AggregateQrPayParam; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult; import cn.daxpay.open.platform.common.spring.util.WebServletUtil; @@ -22,18 +19,14 @@ import java.util.Objects; /// # 聚合扫码支付服务 /// -/// 按应用聚合配置的深度(level)解析支付方式: -/// - AUTO: 系统按打开环境推导 method, 跟随通道路由 -/// - METHOD: 每打开环境配置 method, 跟随通道路由 -/// - DIRECT: 每打开环境配置 channelMchNo+capability, 直接指定 +/// 按应用聚合配置的深度(level)解析支付方式, 委托 [ClientEnvPayResolveService] 与码牌共用解析。 @Slf4j @Service @RequiredArgsConstructor public class AggregatePayService { private final GatewayPayAssistService gatewayPayAssistService; - private final GatewayAggregateConfigService gatewayAggregateConfigService; - private final GatewayAggregateClientEnvManager gatewayAggregateClientEnvManager; + private final ClientEnvPayResolveService clientEnvPayResolveService; private final GatewayPayHandleService gatewayPayHandleService; /// 聚合扫码发起支付 @@ -43,51 +36,14 @@ public class AggregatePayService { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.gateway.typeMismatch"); } ClientEnvEnum clientEnv = ClientEnvEnum.findByCode(param.getClientEnv()); - GatewayAggregateConfig config = gatewayAggregateConfigService.getRequiredByAppId(order.getAppId()); - AggregateConfigLevelEnum level = AggregateConfigLevelEnum.findByCode(config.getLevel()); - - // 按配置深度解析支付方式 - String method; - String channelMchNo = null; - String capability = null; - - switch (level) { - case AUTO -> { - // L1: 系统按客户端环境推导支付方式, channelMchNo 为空走路由 - method = clientEnv.defaultMethodCode(); - } - case METHOD -> { - // L2: 每客户端环境配置支付方式, channelMchNo 为空走路由 - GatewayAggregateClientEnv envConfig = gatewayAggregateClientEnvManager - .findByConfigIdAndClientEnv(config.getId(), clientEnv.getCode()); - if (envConfig == null || StrUtil.isBlank(envConfig.getMethod())) { - throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, - "pay.error.gateway.clientEnvNotConfigured"); - } - method = envConfig.getMethod(); - } - case DIRECT -> { - // L3: 直接指定通道商户号+能力, 跳过应用路由 - GatewayAggregateClientEnv envConfig = gatewayAggregateClientEnvManager - .findByConfigIdAndClientEnv(config.getId(), clientEnv.getCode()); - if (envConfig == null || StrUtil.isBlank(envConfig.getChannelMchNo())) { - throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, - "pay.error.gateway.clientEnvNotConfigured"); - } - channelMchNo = envConfig.getChannelMchNo(); - capability = envConfig.getCapability(); - // method: 优先用配置值, 未配则按打开环境推导(直接指定可由通道商户+能力反推) - method = StrUtil.isNotBlank(envConfig.getMethod()) - ? envConfig.getMethod() - : clientEnv.defaultMethodCode(); - } - default -> throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, - "pay.error.gateway.clientEnvNotSupport"); - } + // 一期 H5; 小程序接入口时传 runtime=mini + ClientRuntimeEnum runtime = ClientRuntimeEnum.ofOrDefault(param.getRuntime()); + var resolved = clientEnvPayResolveService.resolveRequired(order.getAppId(), clientEnv, runtime); String clientIp = StrUtil.blankToDefault(param.getClientIp(), WebServletUtil.getClientIp()); // product 传 null: AUTO/METHOD 由路由解析, DIRECT 由 channelMchNo 派生 - return gatewayPayHandleService.handle(order, null, method, channelMchNo, capability, + return gatewayPayHandleService.handle(order, null, resolved.method(), + resolved.channelMchNo(), resolved.capability(), param.getOpenId(), clientEnv.getCode(), param.getDevice(), clientIp); } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/GatewayPayAssistService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/GatewayPayAssistService.java index 7896930a5..4ababdd7e 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/GatewayPayAssistService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/pay/gateway/GatewayPayAssistService.java @@ -3,6 +3,7 @@ package cn.daxpay.open.payment.trade.runtime.service.pay.gateway; import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum; import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum; import cn.daxpay.open.payment.common.context.MerchantContextLoader; +import cn.daxpay.open.payment.merchant.service.store.MchStoreInfoService; import cn.daxpay.open.payment.trade.order.dao.GatewayPayOrderManager; import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder; import cn.daxpay.open.payment.trade.runtime.service.pay.normal.NormalPayAssistService; @@ -16,6 +17,7 @@ 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.CurrencyEnum; +import cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.operation.OperationFailException; import cn.daxpay.open.platform.core.util.DateTimeUtil; @@ -48,6 +50,7 @@ public class GatewayPayAssistService { private final ArtemisTemplateService artemisTemplateService; private final LockExecutor lockExecutor; private final NormalPayAssistService payAssistService; + private final MchStoreInfoService mchStoreInfoService; /// 预下单: 仅创建容器, 返回落地页 URL public GatewayPrePayResult prePay(GatewayPrePayParam param) { @@ -87,6 +90,8 @@ public class GatewayPayAssistService { } OffsetDateTime expiredTime = payAssistService.getExpiredTime(param.getExpiredTime()); + // 门店号(线下归属, 可空; 有值则校验存在/归属/启用) + mchStoreInfoService.validateStoreForPay(param.getStoreNo(), param.getMchNo()); GatewayPayOrder order = new GatewayPayOrder(); order.setAppId(param.getAppId()); @@ -94,6 +99,8 @@ public class GatewayPayAssistService { order.setOrderNo(TradeNoGenerateUtil.order()); order.setBizOrderNo(param.getBizOrderNo()); order.setGatewayType(typeEnum.getCode()); + // 来源: 容器权威, 预下单即写入(trade 创建时再冗余拷贝) + order.setSource(resolveSourceByGatewayType(typeEnum)); order.setTitle(param.getTitle()); order.setDescription(param.getDescription()); order.setStatus(GatewayOrderStatusEnum.WAIT_PAY.getCode()); @@ -106,6 +113,8 @@ public class GatewayPayAssistService { order.setCurrency(CurrencyEnum.CNY.getCode()); order.setClientIp(param.getClientIp()); order.setGoodsDetail(param.getGoodsDetail()); + // 门店号: 预下单写入, 支付回填路由时不改 + order.setStoreNo(param.getStoreNo()); gatewayPayOrderManager.save(order); this.registerTimeout(order.getOrderNo(), order.getBizOrderNo(), expiredTime); @@ -176,4 +185,20 @@ public class GatewayPayAssistService { log.warn("注册网关超时关单延时消息失败, 由定时任务兜底, orderNo={}", orderNo, e); } } + + /// 网关产品类型 → 交易来源 + public static String resolveSourceByGatewayType(GatewayPayTypeEnum typeEnum) { + if (typeEnum == GatewayPayTypeEnum.CASHIER) { + return TradeSourceEnum.CASHIER.getCode(); + } + return TradeSourceEnum.AGGRESS_PAY.getCode(); + } + + /// 网关产品类型编码 → 交易来源(缺省聚合) + public static String resolveSourceByGatewayTypeCode(String gatewayType) { + if (Objects.equals(gatewayType, GatewayPayTypeEnum.CASHIER.getCode())) { + return TradeSourceEnum.CASHIER.getCode(); + } + return TradeSourceEnum.AGGRESS_PAY.getCode(); + } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/PayRefundService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/PayRefundService.java index e2e4c8877..7787f79e5 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/PayRefundService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/PayRefundService.java @@ -161,7 +161,9 @@ public class PayRefundService { .setBizOrderNo(order.getBizOrderNo()) .setNotifyUrl(order.getNotifyUrl()) .setClientIp(order.getClientIp()) - .setAttach(order.getAttach()); + .setAttach(order.getAttach()) + // 门店号: 继承原支付容器 + .setStoreNo(order.getStoreNo()); } // 默认普通支付(含 future 扩展未知类型时尽量按 normal 查, 查不到再失败) NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()) @@ -180,7 +182,9 @@ public class PayRefundService { .setBizOrderNo(order.getBizOrderNo()) .setNotifyUrl(order.getNotifyUrl()) .setClientIp(order.getClientIp()) - .setAttach(order.getAttach()); + .setAttach(order.getAttach()) + // 门店号: 继承原支付容器 + .setStoreNo(order.getStoreNo()); } /// 校验交易可退: 状态须为 SUCCESS, 退款金额不能超过可退余额 @@ -218,7 +222,9 @@ public class PayRefundService { .setChannelAppId(snapshot.getChannelAppId()) // notifyUrl 语义: 商户出站通知地址, 通道回调 URL 由各通道 buildRefundNotifyUrl 生成 .setNotifyUrl(snapshot.getNotifyUrl()) - .setAttach(snapshot.getAttach()); + .setAttach(snapshot.getAttach()) + // 门店号: 继承原支付容器, 禁止退款入参改写 + .setStoreNo(snapshot.getStoreNo()); // 客户端IP: 优先取下单时留存的原订单IP, 为空则从当前HTTP请求兜底 String refundClientIp = snapshot.getClientIp(); if (StrUtil.isBlank(refundClientIp)) { diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/RefundOrderSnapshot.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/RefundOrderSnapshot.java index 67541189d..cb67e1785 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/RefundOrderSnapshot.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/refund/RefundOrderSnapshot.java @@ -46,4 +46,7 @@ public class RefundOrderSnapshot { /// 商户附加参数(继承自原支付单) private String attach; + + /// 门店号(继承自原支付容器,可空) + private String storeNo; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/device/CodePayParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/device/CodePayParam.java new file mode 100644 index 000000000..6dd50ed17 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/device/CodePayParam.java @@ -0,0 +1,56 @@ +package cn.daxpay.open.payment.unipay.param.device; + +import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum; +import cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/// # 码牌发起支付参数(H5/小程序) +@Data +@Schema(title = "码牌支付参数") +public class CodePayParam { + + /// 码牌编码 + @Schema(description = "码牌编码") + @NotBlank(message = "{validation.field.code.notBlank}") + @Size(max = 64, message = "{validation.field.code.size}") + private String code; + + /// 支付金额(分); 固定金额码牌可空(服务端取码牌配置) + @Schema(description = "支付金额(分, 自定义金额时必填)") + @Min(value = 1, message = "{validation.field.amount.min}") + private Long amount; + + /// 备注/描述(与普通支付 description 长度对齐) + @Schema(description = "备注") + @Size(max = 50, message = "{validation.field.description.size}") + private String description; + + /// 客户端环境 + /// @see ClientEnvEnum + @Schema(description = "客户端环境(wechat_pay/alipay/union_pay/douyin)") + @NotBlank(message = "{validation.field.clientEnv.notBlank}") + @Size(max = 32, message = "{validation.field.clientEnv.size}") + private String clientEnv; + + /// 运行形态, 默认 h5 + /// @see ClientRuntimeEnum + @Schema(description = "运行形态(h5/mini, 默认 h5)") + @Size(max = 16, message = "{validation.field.runtime.size}") + private String runtime; + + @Schema(description = "OpenId(微信/支付宝 JSAPI 等)") + @Size(max = 128, message = "{validation.field.openId.size}") + private String openId; + + @Schema(description = "设备(mobile/pc)") + @Size(max = 16, message = "{validation.field.device.size}") + private String device; + + @Schema(description = "客户端IP") + @Size(max = 64, message = "{validation.field.clientIp.size}") + private String clientIp; +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/AggregateQrPayParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/AggregateQrPayParam.java index 6e17be757..828d63453 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/AggregateQrPayParam.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/AggregateQrPayParam.java @@ -31,6 +31,12 @@ public class AggregateQrPayParam { @Size(max = 16, message = "{validation.field.device.size}") private String device; + /// 运行形态 h5/mini, 默认 h5; 小程序接入口时传 mini + /// @see cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum + @Schema(description = "运行形态(h5/mini, 默认 h5)") + @Size(max = 16, message = "{validation.field.runtime.size}") + private String runtime; + @Schema(description = "客户端IP") @Size(max = 64, message = "{validation.field.clientIp.size}") private String clientIp; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/GatewayPrePayParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/GatewayPrePayParam.java index b6b266b26..32169ba14 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/GatewayPrePayParam.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/gateway/GatewayPrePayParam.java @@ -63,6 +63,11 @@ public class GatewayPrePayParam extends MerchantPaymentCommonParam { @Schema(description = "过期时间") private OffsetDateTime expiredTime; + /// 门店号(线下经营归属,可空;对应 mch_store_info.store_no) + @Schema(description = "门店号") + @Size(max = 64, message = "{validation.field.storeNo.size}") + private String storeNo; + @Schema(description = "商品明细") private List goodsDetail; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/pay/NormalPayOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/pay/NormalPayOrderResult.java index 3c505d71b..d44e47f14 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/pay/NormalPayOrderResult.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/pay/NormalPayOrderResult.java @@ -99,6 +99,10 @@ public class NormalPayOrderResult { @Schema(description = "终端设备编码") private String terminalNo; + /// 门店号(线下经营归属,可空) + @Schema(description = "门店号") + private String storeNo; + /// 付款用户ID @Schema(description = "付款用户ID") private String buyerId; diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/controller/DeviceQrCodeClientController.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/controller/DeviceQrCodeClientController.java index 6e61699a3..5756dc60e 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/controller/DeviceQrCodeClientController.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/client/controller/DeviceQrCodeClientController.java @@ -2,6 +2,8 @@ package cn.daxpay.open.payment.unipay.client.controller; import cn.daxpay.open.payment.unipay.client.result.CodePayInfoResult; import cn.daxpay.open.payment.unipay.client.service.CodePayAssistService; +import cn.daxpay.open.payment.unipay.param.device.CodePayParam; +import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult; import cn.daxpay.open.platform.core.annotation.IgnoreAuth; import cn.daxpay.open.platform.core.rest.Res; import cn.daxpay.open.platform.core.rest.result.Result; @@ -11,10 +13,12 @@ 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; -/// # 码牌支付(公开/H5 侧) +/// # 码牌支付(公开/H5/小程序侧) /// /// 供 H5 码牌支付页(/code-pay/:code)扫码后调用, 无需登录态。 @IgnoreAuth @@ -32,4 +36,10 @@ public class DeviceQrCodeClientController { public Result getByCode(@NotBlank(message = "{validation.field.code.notBlank}") String code) { return Res.ok(codePayAssistService.getByCode(code)); } + + @Operation(summary = "码牌发起支付") + @PostMapping("/pay") + public Result pay(@RequestBody @Validated CodePayParam param) { + return Res.ok(codePayAssistService.pay(param)); + } } 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 a66b83ed8..236ea7415 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 @@ -1,23 +1,35 @@ package cn.daxpay.open.payment.unipay.client.service; +import cn.daxpay.open.payment.common.access.MerchantAccessInfo; +import cn.daxpay.open.payment.common.context.MerchantContextLoader; +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; import cn.daxpay.open.payment.device.qrcode.entity.DeviceQrCode; -import cn.daxpay.open.payment.unipay.client.result.CodePayInfoResult; -import cn.daxpay.open.payment.common.access.MerchantAccessInfo; +import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum; +import cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum; import cn.daxpay.open.payment.merchant.service.access.MerchantAccessQueryService; +import cn.daxpay.open.payment.merchant.service.gateway.ClientEnvPayResolveService; +import cn.daxpay.open.payment.trade.runtime.service.pay.normal.NormalPayService; +import cn.daxpay.open.payment.unipay.client.result.CodePayInfoResult; +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.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.enums.pay.trade.TradeSourceEnum; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.exception.operation.OperationFailException; -import cn.daxpay.open.platform.core.code.CommonCode; +import cn.daxpay.open.platform.core.util.TradeNoGenerateUtil; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -/// # 码牌支付编排(公开/H5 侧) +/// # 码牌支付编排(公开/H5/小程序侧) /// -/// 一期: 仅提供按编码查询码牌信息(供 H5 扫码展示)。 -/// 二期: 扩展 OAuth 重定向获取 openId 与发起 JSAPI 支付(复用 ChannelAuthService/NormalPayService)。 +/// - 查询: 按编码返回脱敏码牌信息 +/// - 支付: 读应用聚合配置解析 method → 建普通订单(source=cashier_code) → [NormalPayService] @Slf4j @Service @RequiredArgsConstructor @@ -25,23 +37,16 @@ public class CodePayAssistService { private final DeviceQrCodeManager deviceQrCodeManager; private final MerchantAccessQueryService merchantAccessQueryService; + private final MerchantContextLoader merchantContextLoader; + private final ClientEnvPayResolveService clientEnvPayResolveService; + private final NormalPayService normalPayService; /// 根据码牌编码查询支付信息(公开接口, 脱敏返回) /// /// 校验码牌存在、启用、已分配商户, 且商户存在。 public CodePayInfoResult getByCode(String code) { - DeviceQrCode entity = deviceQrCodeManager.findByCode(code) - // 码牌: 码牌不存在 - .orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound")); - // 码牌停用 - if (!QrCodeStatusEnum.ENABLED.getCode().equals(entity.getStatus())) { - throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.disabled"); - } - // 空白码未划拨商户 - if (StrUtil.isBlank(entity.getMchNo())) { - throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.notAssigned"); - } - // 查商户并校验存在(启用校验留待二期支付链路 NormalPayService 统一处理) + DeviceQrCode entity = this.loadEnabledAssigned(code); + // 查商户并校验存在 MerchantAccessInfo merchant = merchantAccessQueryService.getMerchantByMchNo(entity.getMchNo()); if (merchant == null) { // 码牌: 商户不存在 @@ -53,4 +58,77 @@ public class CodePayAssistService { .setAmountType(entity.getAmountType()) .setFixedAmount(entity.getFixedAmount()); } + + /// 码牌发起支付: 普通订单 + source=cashier_code, 通道路由复用聚合配置 + public NormalPayResult pay(CodePayParam param) { + DeviceQrCode entity = this.loadEnabledAssigned(param.getCode()); + merchantContextLoader.initMch(entity.getMchNo()); + // 应用: 码牌 appId 空则商户默认应用 + var mchApp = merchantContextLoader.resolveApp(entity.getMchNo(), entity.getAppId()); + + long amount = this.resolveAmount(entity, param.getAmount()); + ClientEnvEnum clientEnv = ClientEnvEnum.findByCode(param.getClientEnv()); + // browser 不支持一码通扫 + if (clientEnv == ClientEnvEnum.BROWSER) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.clientEnvNotSupport"); + } + ClientRuntimeEnum runtime = ClientRuntimeEnum.ofOrDefault(param.getRuntime()); + // 无聚合配置时 AUTO 降级 + var resolved = clientEnvPayResolveService.resolve(mchApp.getAppId(), clientEnv, runtime); + + String codeName = StrUtil.blankToDefault(entity.getName(), entity.getCode()); + // 元展示: amount 为分 + String amountYuan = String.format("%.2f", amount / 100.0); + String title = StrUtil.format("{} 码牌收款: {}元", codeName, amountYuan); + + NormalPayParam payParam = new NormalPayParam(); + payParam.setMchNo(entity.getMchNo()); + payParam.setAppId(mchApp.getAppId()); + // 平台生成商户业务单号, 每笔扫码收款独立 + payParam.setBizOrderNo(TradeNoGenerateUtil.order()); + payParam.setTitle(title); + payParam.setDescription(param.getDescription()); + payParam.setAmount(amount); + payParam.setMethod(resolved.method()); + payParam.setChannelMchNo(resolved.channelMchNo()); + payParam.setCapability(resolved.capability()); + payParam.setOpenId(param.getOpenId()); + payParam.setClientIp(StrUtil.blankToDefault(param.getClientIp(), WebServletUtil.getClientIp())); + // 码牌来源 + payParam.setSource(TradeSourceEnum.CASHIER_CODE.getCode()); + + return normalPayService.pay(payParam); + } + + /// 加载启用且已划拨的码牌 + private DeviceQrCode loadEnabledAssigned(String code) { + DeviceQrCode entity = deviceQrCodeManager.findByCode(code) + // 码牌: 码牌不存在 + .orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound")); + // 码牌停用 + if (!QrCodeStatusEnum.ENABLED.getCode().equals(entity.getStatus())) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.disabled"); + } + // 空白码未划拨商户 + if (StrUtil.isBlank(entity.getMchNo())) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.notAssigned"); + } + return entity; + } + + /// 固定金额取码牌配置; 自定义金额取入参并校验 + private long resolveAmount(DeviceQrCode entity, Long requestAmount) { + QrCodeAmountTypeEnum amountType = QrCodeAmountTypeEnum.findByCode(entity.getAmountType()); + if (amountType == QrCodeAmountTypeEnum.FIXED) { + if (entity.getFixedAmount() == null || entity.getFixedAmount() <= 0) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.fixedAmountInvalid"); + } + return entity.getFixedAmount(); + } + // random + if (requestAmount == null || requestAmount <= 0) { + throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.amountRequired"); + } + return requestAmount; + } } 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 387a5eb21..520f0c682 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 @@ -7,5 +7,7 @@ "fixedAmountInvalid": "Fixed amount must be greater than 0", "batchNoExists": "Batch number already exists", "notAssigned": "QR code board has not been assigned to a merchant", - "idsEmpty": "Please select QR code boards" + "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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/payment/merchant.json index f979b07dc..cb6741e3e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "Store not found", "storeNoGenFailed": "Store number generation failed", "storeNoMatch": "Store does not belong to the current merchant", + "storeDisabled": "Store is disabled", "wxVerifyNotFound": "WeChat domain verification file not found", "wxVerifyFileNameEmpty": "WeChat domain verification file name is empty", "wxVerifyFileNameInvalid": "WeChat domain verification file name format is invalid, expected MP_verify_.txt", 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 a4581a2c6..520f0c682 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 @@ -1,11 +1,13 @@ { - "notFound": "Papan kode QR tidak ada", - "disabled": "Papan kode QR dinonaktifkan", - "mchNotFound": "Pedagang papan kode QR tidak ada", - "amountTypeNotFound": "Tidak ditemukan jenis jumlah kode QR yang cocok: {0}", - "statusNotFound": "Tidak ditemukan status kode QR yang cocok: {0}", - "fixedAmountInvalid": "Jumlah tetap harus lebih besar dari 0", - "batchNoExists": "Nomor batch sudah ada", - "notAssigned": "Papan kode QR belum ditetapkan ke pedagang", - "idsEmpty": "Silakan pilih papan kode QR" + "notFound": "QR code board does not exist", + "disabled": "QR code board is disabled", + "mchNotFound": "The merchant of the QR code board does not exist", + "amountTypeNotFound": "No matching QR code amount type found: {0}", + "statusNotFound": "No matching QR code status found: {0}", + "fixedAmountInvalid": "Fixed amount must be greater than 0", + "batchNoExists": "Batch number already exists", + "notAssigned": "QR code board has not been assigned to a merchant", + "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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/payment/merchant.json index 5dc098927..4a6ab12a6 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "Toko tidak ditemukan", "storeNoGenFailed": "Pembuatan nomor toko gagal", "storeNoMatch": "Toko bukan milik pedagang saat ini", + "storeDisabled": "Toko dinonaktifkan", "wxVerifyNotFound": "File verifikasi domain WeChat tidak ditemukan", "wxVerifyFileNameEmpty": "Nama file verifikasi domain WeChat kosong", "wxVerifyFileNameInvalid": "Format nama file verifikasi domain WeChat tidak valid, diharapkan MP_verify_.txt", 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 f03b8a635..7458846a8 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 @@ -7,5 +7,7 @@ "fixedAmountInvalid": "固定量は 0 より大きくなければなりません", "batchNoExists": "バッチ番号はすでに存在します", "notAssigned": "QRコードボードが加盟店に割り当てられていません", - "idsEmpty": "QRコードボードを選択してください" + "idsEmpty": "QRコードボードを選択してください", + "clientEnvNotSupport": "現在の環境ではコードプレート決済に対応していません。WeChat/Alipayなどでスキャンしてください", + "amountRequired": "支払金額を入力してください" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/payment/merchant.json index 8b4eaeccc..13974b5ba 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "ストアが見つかりません", "storeNoGenFailed": "店舗番号の生成に失敗しました", "storeNoMatch": "ストアは現在の販売者に属していません", + "storeDisabled": "ストアは無効です", "wxVerifyNotFound": "WeChat ドメイン検証ファイルが見つかりません", "wxVerifyFileNameEmpty": "WeChat ドメイン検証ファイル名が空です", "wxVerifyFileNameInvalid": "WeChat ドメイン検証ファイル名の形式が無効です。MP_verify_<ランダム>.txt が必要です", 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 856e14022..251343b12 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 @@ -1,11 +1,13 @@ { "notFound": "QR 코드 보드가 존재하지 않습니다", "disabled": "QR 코드 보드가 비활성화되었습니다", - "mchNotFound": "QR코드판의 판매자가 존재하지 않습니다.", - "amountTypeNotFound": "일치하는 QR 코드 금액 유형이 없습니다: {0}", - "statusNotFound": "일치하는 QR 코드 상태가 없습니다: {0}", - "fixedAmountInvalid": "고정 금액은 0보다 커야 합니다.", - "batchNoExists": "배치 번호가 이미 존재합니다.", - "notAssigned": "QR코드 보드가 가맹점에 할당되지 않았습니다.", - "idsEmpty": "QR코드 보드를 선택해주세요." + "mchNotFound": "QR 코드 보드 소속 가맹점이 존재하지 않습니다", + "amountTypeNotFound": "일치하는 QR 코드 금액 유형을 찾을 수 없습니다: {0}", + "statusNotFound": "일치하는 QR 코드 상태를 찾을 수 없습니다: {0}", + "fixedAmountInvalid": "고정 금액은 0보다 커야 합니다", + "batchNoExists": "배치 번호가 이미 존재합니다", + "notAssigned": "QR 코드 보드가 가맹점에 할당되지 않았습니다", + "idsEmpty": "QR 코드 보드를 선택하세요", + "clientEnvNotSupport": "현재 환경에서는 코드플레이트 결제를 지원하지 않습니다. WeChat/Alipay 등으로 스캔하세요", + "amountRequired": "결제 금액을 입력하세요" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/payment/merchant.json index 6408b62ba..7f50d9009 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "매장을 찾을 수 없습니다", "storeNoGenFailed": "매장번호 생성 실패", "storeNoMatch": "매장이 현재 판매자의 소유가 아닙니다.", + "storeDisabled": "매장이 비활성화되었습니다", "wxVerifyNotFound": "WeChat 도메인 확인 파일을 찾을 수 없습니다", "wxVerifyFileNameEmpty": "WeChat 도메인 확인 파일 이름이 비어 있습니다.", "wxVerifyFileNameInvalid": "WeChat 도메인 확인 파일 이름 형식이 잘못되었습니다. MP_verify_.txt가 필요합니다.", 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 994a28c48..520f0c682 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 @@ -1,11 +1,13 @@ { - "notFound": "Papan kod QR tidak wujud", - "disabled": "Papan kod QR dilumpuhkan", - "mchNotFound": "Pedagang papan kod QR tidak wujud", - "amountTypeNotFound": "Tiada jenis jumlah kod QR yang sepadan ditemui: {0}", - "statusNotFound": "Tiada status kod QR yang sepadan ditemui: {0}", - "fixedAmountInvalid": "Jumlah tetap mestilah lebih besar daripada 0", - "batchNoExists": "Nombor kelompok sudah wujud", - "notAssigned": "Papan kod QR belum diberikan kepada pedagang", - "idsEmpty": "Sila pilih papan kod QR" + "notFound": "QR code board does not exist", + "disabled": "QR code board is disabled", + "mchNotFound": "The merchant of the QR code board does not exist", + "amountTypeNotFound": "No matching QR code amount type found: {0}", + "statusNotFound": "No matching QR code status found: {0}", + "fixedAmountInvalid": "Fixed amount must be greater than 0", + "batchNoExists": "Batch number already exists", + "notAssigned": "QR code board has not been assigned to a merchant", + "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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/payment/merchant.json index 9dcfc2922..9d71caf8d 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "Kedai tidak ditemui", "storeNoGenFailed": "Penjanaan nombor kedai gagal", "storeNoMatch": "Kedai bukan milik peniaga semasa", + "storeDisabled": "Kedai telah dilumpuhkan", "wxVerifyNotFound": "Fail pengesahan domain WeChat tidak ditemui", "wxVerifyFileNameEmpty": "Nama fail pengesahan domain WeChat kosong", "wxVerifyFileNameInvalid": "Format nama fail pengesahan domain WeChat tidak sah, dijangka MP_verify_.txt", 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 e494c8bee..520f0c682 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 @@ -1,11 +1,13 @@ { - "notFound": "ไม่มีบอร์ดรหัส QR", - "disabled": "บอร์ดรหัส QR ถูกปิดใช้งาน", - "mchNotFound": "ไม่มีผู้ขายบอร์ดรหัส QR", - "amountTypeNotFound": "ไม่พบประเภทจำนวนรหัส QR ที่ตรงกัน: {0}", - "statusNotFound": "ไม่พบสถานะรหัส QR ที่ตรงกัน: {0}", - "fixedAmountInvalid": "จำนวนเงินคงที่ต้องมากกว่า 0", - "batchNoExists": "มีหมายเลขแบทช์อยู่แล้ว", - "notAssigned": "ยังไม่ได้กำหนดบอร์ดรหัส QR ให้กับร้านค้า", - "idsEmpty": "กรุณาเลือกบอร์ดรหัส QR" + "notFound": "QR code board does not exist", + "disabled": "QR code board is disabled", + "mchNotFound": "The merchant of the QR code board does not exist", + "amountTypeNotFound": "No matching QR code amount type found: {0}", + "statusNotFound": "No matching QR code status found: {0}", + "fixedAmountInvalid": "Fixed amount must be greater than 0", + "batchNoExists": "Batch number already exists", + "notAssigned": "QR code board has not been assigned to a merchant", + "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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/payment/merchant.json index 57c19afda..c76015f49 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "ไม่พบร้านค้า", "storeNoGenFailed": "การสร้างหมายเลขร้านค้าล้มเหลว", "storeNoMatch": "ร้านค้าไม่ได้เป็นของผู้ค้าปัจจุบัน", + "storeDisabled": "ร้านค้าถูกปิดใช้งาน", "wxVerifyNotFound": "ไม่พบไฟล์การยืนยันโดเมน WeChat", "wxVerifyFileNameEmpty": "ชื่อไฟล์การยืนยันโดเมน WeChat ว่างเปล่า", "wxVerifyFileNameInvalid": "รูปแบบชื่อไฟล์การยืนยันโดเมน WeChat ไม่ถูกต้อง ต้องใช้ MP_verify_.txt", 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 93105d734..520f0c682 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 @@ -1,11 +1,13 @@ { - "notFound": "Bảng mã QR không tồn tại", - "disabled": "Bảng mã QR bị vô hiệu hóa", - "mchNotFound": "Người bán bảng mã QR không tồn tại", - "amountTypeNotFound": "Không tìm thấy loại số tiền mã QR phù hợp: {0}", - "statusNotFound": "Không tìm thấy trạng thái mã QR phù hợp: {0}", - "fixedAmountInvalid": "Số tiền cố định phải lớn hơn 0", - "batchNoExists": "Số lô đã tồn tại", - "notAssigned": "Bảng mã QR chưa được gán cho người bán", - "idsEmpty": "Vui lòng chọn bảng mã QR" + "notFound": "QR code board does not exist", + "disabled": "QR code board is disabled", + "mchNotFound": "The merchant of the QR code board does not exist", + "amountTypeNotFound": "No matching QR code amount type found: {0}", + "statusNotFound": "No matching QR code status found: {0}", + "fixedAmountInvalid": "Fixed amount must be greater than 0", + "batchNoExists": "Batch number already exists", + "notAssigned": "QR code board has not been assigned to a merchant", + "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" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/payment/merchant.json index 316616773..44a911d99 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "Không tìm thấy cửa hàng", "storeNoGenFailed": "Tạo số cửa hàng không thành công", "storeNoMatch": "Cửa hàng không thuộc về người bán hiện tại", + "storeDisabled": "Cửa hàng đã bị vô hiệu hóa", "wxVerifyNotFound": "Không tìm thấy tệp xác minh tên miền WeChat", "wxVerifyFileNameEmpty": "Tên tệp xác minh miền WeChat trống", "wxVerifyFileNameInvalid": "Định dạng tên tệp xác minh miền WeChat không hợp lệ, dự kiến ​​là MP_verify_.txt", 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 ccb577803..5cf3d33c7 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 @@ -7,5 +7,7 @@ "fixedAmountInvalid": "固定金额必须大于0", "batchNoExists": "批次号已存在", "notAssigned": "码牌未分配商户", - "idsEmpty": "请选择码牌" + "idsEmpty": "请选择码牌", + "clientEnvNotSupport": "当前打开环境不支持码牌支付, 请使用微信/支付宝等扫码", + "amountRequired": "请输入支付金额" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/payment/merchant.json index 4c58a5563..9157c31c2 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "门店信息不存在", "storeNoGenFailed": "门店号生成失败", "storeNoMatch": "门店不属于当前商户", + "storeDisabled": "门店已停用", "wxVerifyNotFound": "微信域名验证文件不存在", "wxVerifyFileNameEmpty": "微信域名验证文件名为空", "wxVerifyFileNameInvalid": "微信域名验证文件名格式不正确,应为 MP_verify_随机字符串.txt", 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 9850db413..c6014966c 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 @@ -1,11 +1,13 @@ { "notFound": "碼牌不存在", "disabled": "碼牌已停用", - "mchNotFound": "碼牌所屬商户不存在", + "mchNotFound": "碼牌所屬商戶不存在", "amountTypeNotFound": "未找到對應的碼牌金額類型: {0}", "statusNotFound": "未找到對應的碼牌狀態: {0}", "fixedAmountInvalid": "固定金額必須大於0", "batchNoExists": "批次號已存在", - "notAssigned": "碼牌未分配商户", - "idsEmpty": "請選擇碼牌" + "notAssigned": "碼牌未分配商戶", + "idsEmpty": "請選擇碼牌", + "clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼", + "amountRequired": "請輸入支付金額" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/payment/merchant.json index bc4ef6c7d..973c13879 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "門店信息不存在", "storeNoGenFailed": "門店號生成失敗", "storeNoMatch": "門店不屬於當前商户", + "storeDisabled": "門店已停用", "wxVerifyNotFound": "微信域名驗證文件不存在", "wxVerifyFileNameEmpty": "微信域名驗證文件名為空", "wxVerifyFileNameInvalid": "微信域名驗證文件名格式不正確,應為 MP_verify_隨機字符串.txt", 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 56096c866..867adc762 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 @@ -7,5 +7,7 @@ "fixedAmountInvalid": "固定金額必須大於0", "batchNoExists": "批次號已存在", "notAssigned": "碼牌未分配商戶", - "idsEmpty": "請選擇碼牌" + "idsEmpty": "請選擇碼牌", + "clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼", + "amountRequired": "請輸入支付金額" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/payment/merchant.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/payment/merchant.json index 17971db1d..b0c2a6111 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/payment/merchant.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/payment/merchant.json @@ -21,6 +21,7 @@ "storeNotFound": "門店資訊不存在", "storeNoGenFailed": "門店號生成失敗", "storeNoMatch": "門店不屬於當前商戶", + "storeDisabled": "門店已停用", "wxVerifyNotFound": "微信域名驗證檔案不存在", "wxVerifyFileNameEmpty": "微信域名驗證檔名為空", "wxVerifyFileNameInvalid": "微信域名驗證檔名格式不正確,應為 MP_verify_隨機字串.txt",