mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 06:05:33 +08:00
feat(device): 码牌支持 H5/小程序落地类型并精简 MapStruct 映射
新增 QrCodeProgramTypeEnum,扫码链接按类型分流 /h 与 /m;批量创建写入 programType(创建后不可改),并补齐校验与多语 i18n。Convert 改为单源同名映射,资金字段由 Service 补充;清空已落地的 update-tables.sql。
This commit is contained in:
@@ -1,220 +0,0 @@
|
||||
-- 付款码列:bar_code → auth_code(与 unipay 入参 authCode 对齐)
|
||||
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;
|
||||
|
||||
-- ============================================================
|
||||
-- 易支付协议插件表
|
||||
-- ============================================================
|
||||
|
||||
-- 易支付凭证(应用级)
|
||||
CREATE TABLE IF NOT EXISTS pay_easy_pay_credential (
|
||||
id int8 NOT NULL,
|
||||
pid int4 NOT NULL,
|
||||
app_id varchar(32) NOT NULL,
|
||||
enable bool NOT NULL DEFAULT false,
|
||||
enable_v1 bool NOT NULL DEFAULT false,
|
||||
enable_v2 bool NOT NULL DEFAULT true,
|
||||
md5_key varchar(128),
|
||||
use_system_key bool NOT NULL DEFAULT true,
|
||||
public_key text,
|
||||
mch_no varchar(32) NOT NULL,
|
||||
creator int8,
|
||||
create_time timestamptz(6),
|
||||
last_modifier int8,
|
||||
last_modified_time timestamptz(6),
|
||||
version int4 NOT NULL DEFAULT 0,
|
||||
deleted bool NOT NULL DEFAULT false,
|
||||
CONSTRAINT pk_pay_easy_pay_credential PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_easy_pay_credential_app_id ON pay_easy_pay_credential (app_id) WHERE deleted = false;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_easy_pay_credential_pid ON pay_easy_pay_credential (pid) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE pay_easy_pay_credential IS '易支付凭证配置表(应用级)';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.id IS '主键';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.pid IS '易支付商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.app_id IS '应用号(支付出口)';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.enable IS '是否启用';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.enable_v1 IS '是否开启V1接口';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.enable_v2 IS '是否开启V2接口';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.md5_key IS 'V1 MD5密钥';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.use_system_key IS 'V2是否使用系统公私钥';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.public_key IS '商户RSA公钥';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.mch_no IS '商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.creator IS '创建人';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.last_modifier IS '最后修改人';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.version IS '乐观锁版本';
|
||||
COMMENT ON COLUMN pay_easy_pay_credential.deleted IS '逻辑删除';
|
||||
|
||||
-- 易支付场景配置
|
||||
CREATE TABLE IF NOT EXISTS pay_easy_pay_config (
|
||||
id int8 NOT NULL,
|
||||
pid int4 NOT NULL,
|
||||
app_id varchar(32) NOT NULL,
|
||||
limit_pay varchar(64),
|
||||
mch_no varchar(32) NOT NULL,
|
||||
creator int8,
|
||||
create_time timestamptz(6),
|
||||
last_modifier int8,
|
||||
last_modified_time timestamptz(6),
|
||||
version int4 NOT NULL DEFAULT 0,
|
||||
deleted bool NOT NULL DEFAULT false,
|
||||
CONSTRAINT pk_pay_easy_pay_config PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_easy_pay_config_app_id ON pay_easy_pay_config (app_id) WHERE deleted = false;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_easy_pay_config_pid ON pay_easy_pay_config (pid) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE pay_easy_pay_config IS '易支付场景配置表';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.id IS '主键';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.pid IS '易支付商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.app_id IS '应用号';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.limit_pay IS '限制支付方式';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.mch_no IS '商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.creator IS '创建人';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.last_modifier IS '最后修改人';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.last_modified_time IS '最后修改时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.version IS '乐观锁版本';
|
||||
COMMENT ON COLUMN pay_easy_pay_config.deleted IS '逻辑删除';
|
||||
|
||||
-- 易支付协议订单
|
||||
CREATE TABLE IF NOT EXISTS pay_easy_pay_order (
|
||||
id int8 NOT NULL,
|
||||
order_id int8,
|
||||
pid int4 NOT NULL,
|
||||
app_id varchar(32) NOT NULL,
|
||||
trade_no varchar(64),
|
||||
out_trade_no varchar(100) NOT NULL,
|
||||
api_trade_no varchar(64),
|
||||
type varchar(32),
|
||||
status int4 NOT NULL DEFAULT 0,
|
||||
add_time timestamptz(6),
|
||||
end_time timestamptz(6),
|
||||
name varchar(200),
|
||||
money numeric(16, 2) NOT NULL,
|
||||
refund_money numeric(16, 2) DEFAULT 0,
|
||||
notify_url varchar(500),
|
||||
return_url varchar(500),
|
||||
param varchar(500),
|
||||
buyer varchar(128),
|
||||
client_ip varchar(64),
|
||||
api_version varchar(8),
|
||||
pc_call_type varchar(32),
|
||||
pay_url varchar(1000),
|
||||
pay_body text,
|
||||
mch_no varchar(32) NOT NULL,
|
||||
creator int8,
|
||||
create_time timestamptz(6),
|
||||
last_modifier int8,
|
||||
last_modified_time timestamptz(6),
|
||||
version int4 NOT NULL DEFAULT 0,
|
||||
deleted bool NOT NULL DEFAULT false,
|
||||
CONSTRAINT pk_pay_easy_pay_order PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_easy_pay_order_app_out ON pay_easy_pay_order (app_id, out_trade_no) WHERE deleted = false;
|
||||
CREATE INDEX IF NOT EXISTS idx_easy_pay_order_pid_out ON pay_easy_pay_order (pid, out_trade_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_easy_pay_order_trade_no ON pay_easy_pay_order (trade_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_easy_pay_order_order_id ON pay_easy_pay_order (order_id);
|
||||
|
||||
COMMENT ON TABLE pay_easy_pay_order IS '易支付协议订单表';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.id IS '主键(收银台路径参数)';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.order_id IS '关联内核容器ID';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.pid IS '易支付商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.app_id IS '应用号';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.trade_no IS '平台业务单号(容器orderNo)';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.out_trade_no IS '商户订单号';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.api_trade_no IS '通道订单号';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.type IS '协议支付方式 alipay/wxpay/aggregate';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.status IS '协议状态 0待付 1成功';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.add_time IS '创建时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.end_time IS '完成时间';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.name IS '商品名称';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.money IS '订单金额(元)';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.refund_money IS '已退款金额(元)';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.notify_url IS '异步通知地址(本期仅落库)';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.return_url IS '同步跳转地址';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.param IS '业务扩展参数';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.buyer IS '支付用户标识';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.client_ip IS '客户端IP';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.api_version IS 'API版本 v1/v2';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.pc_call_type IS '支付调用方式';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.pay_url IS '支付链接';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.pay_body IS '支付参数体';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.mch_no IS '商户号';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.creator IS '创建人';
|
||||
COMMENT ON COLUMN pay_easy_pay_order.create_time IS '创建时间';
|
||||
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);
|
||||
|
||||
-- ============================================================
|
||||
-- pay_trade 轻量组织冗余: channel_mch_no + store_no
|
||||
-- ============================================================
|
||||
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS channel_mch_no varchar(64);
|
||||
COMMENT ON COLUMN pay_trade.channel_mch_no IS '通道商户号(冗余自业务容器, 路由确定后写入)';
|
||||
|
||||
ALTER TABLE pay_trade ADD COLUMN IF NOT EXISTS store_no varchar(32);
|
||||
COMMENT ON COLUMN pay_trade.store_no IS '门店号(冗余自业务容器, 可空; 权威在容器)';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pay_trade_mch_channel ON pay_trade (mch_no, channel_mch_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_pay_trade_mch_store ON pay_trade (mch_no, store_no);
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ public class DeviceQrCodeBatchParam {
|
||||
@Size(max = 100, message = "{validation.field.qrCodeName.size}")
|
||||
private String name;
|
||||
|
||||
/// 落地程序类型: h5 / mini_app, 整批统一, 创建后不可改
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum
|
||||
@Schema(description = "落地程序类型(h5-H5码牌/mini_app-小程序码牌)")
|
||||
@NotBlank(message = "{validation.field.programType.notBlank}")
|
||||
private String programType;
|
||||
|
||||
/// 金额类型: random-自定义金额 / fixed-固定金额
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.daxpay.open.payment.admin.service.device;
|
||||
|
||||
import cn.daxpay.open.payment.common.context.MerchantContextLoader;
|
||||
import cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum;
|
||||
import cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum;
|
||||
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;
|
||||
@@ -38,8 +39,11 @@ import java.util.stream.IntStream;
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceQrCodeAdminService {
|
||||
|
||||
/// H5 码牌支付页路径前缀(与 dax-pay-h5 RoutePath.CODE_PAY 一致)
|
||||
private static final String CODE_PAY_PATH = "/code-pay/";
|
||||
/// H5 码牌支付页路径前缀(与 dax-pay-h5 RoutePath.CODE_PAY 一致: /h/:code)
|
||||
private static final String CODE_H5_PATH = "/h/";
|
||||
|
||||
/// 小程序码牌扫码 path 前缀(映射域名落地后置, 本期仅生成链接)
|
||||
private static final String CODE_MINI_PATH = "/m/";
|
||||
|
||||
private final DeviceQrCodeManager deviceQrCodeManager;
|
||||
private final MerchantContextLoader merchantContextLoader;
|
||||
@@ -54,6 +58,8 @@ public class DeviceQrCodeAdminService {
|
||||
// 码牌: 批次号已存在
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.batchNoExists");
|
||||
}
|
||||
// 落地程序类型(创建后不可改)
|
||||
QrCodeProgramTypeEnum programType = QrCodeProgramTypeEnum.findByCode(param.getProgramType());
|
||||
// 金额类型校验
|
||||
QrCodeAmountTypeEnum amountType = QrCodeAmountTypeEnum.findByCode(param.getAmountType());
|
||||
validateFixedAmount(amountType, param.getFixedAmount());
|
||||
@@ -68,6 +74,7 @@ public class DeviceQrCodeAdminService {
|
||||
.setCode(param.getBatchNo() + String.format("%03d", i))
|
||||
.setName(param.getName())
|
||||
.setBatchNo(param.getBatchNo())
|
||||
.setProgramType(programType.getCode())
|
||||
.setAmountType(amountType.getCode())
|
||||
.setFixedAmount(fixedAmount)
|
||||
.setStatus(status)
|
||||
@@ -152,11 +159,13 @@ public class DeviceQrCodeAdminService {
|
||||
deviceQrCodeManager.updateById(entity);
|
||||
}
|
||||
|
||||
/// 获取码牌 H5 扫码链接
|
||||
/// 获取码牌扫码链接
|
||||
///
|
||||
/// 拼接规则: {paymentGatewayBaseUrl}/code-pay/{code}
|
||||
/// 同一 paymentGatewayBaseUrl 下按 programType 分流 path:
|
||||
/// - h5 → /h/{code}
|
||||
/// - mini_app → /m/{code}
|
||||
public String getCodeLink(String code) {
|
||||
deviceQrCodeManager.findByCode(code)
|
||||
DeviceQrCode qrCode = deviceQrCodeManager.findByCode(code)
|
||||
// 码牌: 码牌不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound"));
|
||||
PlatformUrlConfig urlConfig = platformUrlConfigService.getUrlConfig();
|
||||
@@ -165,7 +174,11 @@ public class DeviceQrCodeAdminService {
|
||||
// 支付网关前端地址未配置
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.common.gatewayUrlNotConfigured");
|
||||
}
|
||||
return gatewayBase + CODE_PAY_PATH + code;
|
||||
// 按落地程序类型选 path; 历史空值按 H5
|
||||
String path = QrCodeProgramTypeEnum.MINI_APP.getCode().equals(qrCode.getProgramType())
|
||||
? CODE_MINI_PATH
|
||||
: CODE_H5_PATH;
|
||||
return gatewayBase + path + code;
|
||||
}
|
||||
|
||||
/// 应用解析: 复用 [MerchantContextLoader#resolveApp], appId 空取商户默认应用, 返回解析后的 appId
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.daxpay.open.payment.device.enums;
|
||||
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.core.i18n.I18nSupport;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/// # 码牌落地程序类型
|
||||
///
|
||||
/// 扫码链接 path 按类型分流: H5 为 `/h/{code}`, 小程序为 `/m/{code}`。
|
||||
/// 字典: qrcode_program_type
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum QrCodeProgramTypeEnum implements I18nSupport {
|
||||
|
||||
/// H5 码牌(扫码进入支付网关 H5 页)
|
||||
H5("h5"),
|
||||
/// 小程序码牌(扫码进入小程序映射 path, 落地页后置)
|
||||
MINI_APP("mini_app");
|
||||
|
||||
/// 编码
|
||||
private final String code;
|
||||
|
||||
/// 翻译 key 前缀
|
||||
@Override
|
||||
public String getI18nPrefix() {
|
||||
return "enum.qrcode_program_type";
|
||||
}
|
||||
|
||||
/// 根据编码查找
|
||||
public static QrCodeProgramTypeEnum findByCode(String code) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getCode().equals(code))
|
||||
.findFirst()
|
||||
// 通用: 未找到对应的码牌程序类型: {0}
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.programTypeNotFound", code));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import cn.daxpay.open.payment.device.qrcode.convert.DeviceQrCodeConvert;
|
||||
import cn.daxpay.open.payment.device.qrcode.result.DeviceQrCodeResult;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -11,7 +13,8 @@ import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付码牌
|
||||
///
|
||||
/// 记录码牌与商户/应用的绑定关系, 扫码后进入 H5 码牌支付页(/code-pay/:code)。
|
||||
/// 记录码牌与商户/应用的绑定关系。
|
||||
/// 扫码链接按 [programType] 分流: H5 为 `/h/{code}`, 小程序为 `/m/{code}`。
|
||||
/// 支持运营端批量创建空白码牌后划拨给商户: mchNo 初始为空, 绑定后写入。
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@@ -34,6 +37,11 @@ public class DeviceQrCode extends MpBaseEntity implements ToResult<DeviceQrCodeR
|
||||
/// 关联应用号(可空, 空白码为空; 绑定后空则使用商户默认应用)
|
||||
private String appId;
|
||||
|
||||
/// 落地程序类型: h5 / mini_app, 创建写入后不可改
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String programType;
|
||||
|
||||
/// 金额类型: random-自定义金额 / fixed-固定金额
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
private String amountType;
|
||||
|
||||
@@ -31,6 +31,12 @@ public class DeviceQrCodeQuery {
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String mchNo;
|
||||
|
||||
/// 落地程序类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum
|
||||
@Schema(description = "落地程序类型(h5/mini_app)")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String programType;
|
||||
|
||||
/// 金额类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型")
|
||||
|
||||
@@ -35,6 +35,11 @@ public class DeviceQrCodeResult extends BaseResult {
|
||||
@Schema(description = "关联应用号(空表示使用商户默认应用)")
|
||||
private String appId;
|
||||
|
||||
/// 落地程序类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum
|
||||
@Schema(description = "落地程序类型(h5/mini_app)")
|
||||
private String programType;
|
||||
|
||||
/// 金额类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
|
||||
@@ -3,17 +3,15 @@ package cn.daxpay.open.payment.route.convert.basic;
|
||||
import cn.daxpay.open.payment.route.entity.basic.PayRouteBasicConfig;
|
||||
import cn.daxpay.open.payment.route.result.basic.PayRouteBasicConfigResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 支付通道路由基础模式配置转换
|
||||
///
|
||||
/// 同名映射;channelMchants(可选通道商户列表)由 service 按商户开通情况装配
|
||||
@Mapper
|
||||
public interface PayRouteBasicConfigConvert {
|
||||
|
||||
PayRouteBasicConfigConvert CONVERT = Mappers.getMapper(PayRouteBasicConfigConvert.class);
|
||||
|
||||
/// channelMchants(可选通道商户列表)由 service 按商户开通情况装配,转换时忽略
|
||||
@Mapping(target = "channelMchants", ignore = true)
|
||||
PayRouteBasicConfigResult toResult(PayRouteBasicConfig entity);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 支付交易转换器
|
||||
///
|
||||
/// 仅从 PayTrade 映射部分字段; orderNo/payBody 属容器, 由 [NormalPayAssistService#buildResult] 组装
|
||||
/// 仅改名映射 orderId ← id;其余同名字段自动映射;orderNo/payBody 等无源字段保持 null,
|
||||
/// 由 [cn.daxpay.open.payment.trade.runtime.service.pay.normal.NormalPayAssistService#buildResult] 组装
|
||||
@Mapper
|
||||
public interface PayTradeConvert {
|
||||
|
||||
@@ -16,10 +17,5 @@ public interface PayTradeConvert {
|
||||
|
||||
/// PayTrade → NormalPayResult(不含 orderNo/payBody, 需配合容器)
|
||||
@Mapping(target = "orderId", source = "id")
|
||||
@Mapping(target = "tradeNo", source = "tradeNo")
|
||||
@Mapping(target = "orderNo", ignore = true)
|
||||
@Mapping(target = "bizOrderNo", ignore = true)
|
||||
@Mapping(target = "payBody", ignore = true)
|
||||
@Mapping(target = "payBodyType", ignore = true)
|
||||
NormalPayResult toResult(PayTrade trade);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/// # 码牌支付(公开/H5/小程序侧)
|
||||
///
|
||||
/// 供 H5 码牌支付页(/code-pay/:code)扫码后调用, 无需登录态。
|
||||
/// 供 H5 码牌支付页(/h/:code)扫码后调用, 无需登录态。
|
||||
@IgnoreAuth
|
||||
@Validated
|
||||
@Tag(name = "码牌支付")
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.experimental.Accessors;
|
||||
|
||||
/// # 码牌支付信息(公开接口返回, 脱敏)
|
||||
///
|
||||
/// 供 H5 码牌支付页(/code-pay/:code)扫码后查询展示, 不含 appId/mchNo 等内部字段。
|
||||
/// 供 H5 码牌支付页(/h/:code)扫码后查询展示, 不含 appId/mchNo 等内部字段。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "码牌支付信息")
|
||||
|
||||
@@ -1,40 +1,19 @@
|
||||
package cn.daxpay.open.payment.unipay.trade.convert;
|
||||
|
||||
import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.payment.trade.order.entity.PayTrade;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayOrderResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 普通支付订单转换器(对外查询)
|
||||
///
|
||||
/// 将 core 双表(NormalPayOrder 业务容器 + PayTrade 资金凭证)映射为对外契约 NormalPayOrderResult
|
||||
/// 资金态字段(status / outOrderNo / refundableBalance 等)取自 PayTrade, 业务字段取自 NormalPayOrder
|
||||
///
|
||||
/// 仅声明:改名、多源同名消歧、以及目标独有字段的 ignore(由服务层后填或暂不输出)
|
||||
/// 仅同名映射业务容器(NormalPayOrder);资金凭证字段(tradeNo / outOrderNo / status / refundableBalance)
|
||||
/// 由 [cn.daxpay.open.payment.unipay.trade.service.NormalPayOrderQueryService] 手写补充
|
||||
@Mapper
|
||||
public interface UnipayNormalPayOrderConvert {
|
||||
|
||||
UnipayNormalPayOrderConvert CONVERT = Mappers.getMapper(UnipayNormalPayOrderConvert.class);
|
||||
|
||||
/// NormalPayOrder + PayTrade → 对外 NormalPayOrderResult
|
||||
// 业务单号取容器, 资金交易号取 trade
|
||||
@Mapping(target = "orderNo", source = "order.orderNo")
|
||||
@Mapping(target = "tradeNo", source = "trade.tradeNo")
|
||||
// 多源同名消歧:资金态取 trade,业务容器取 order
|
||||
@Mapping(target = "status", source = "trade.status")
|
||||
@Mapping(target = "outOrderNo", source = "trade.outOrderNo")
|
||||
@Mapping(target = "refundableBalance", source = "trade.refundableBalance")
|
||||
@Mapping(target = "bizOrderNo", source = "order.bizOrderNo")
|
||||
@Mapping(target = "channel", source = "order.channel")
|
||||
@Mapping(target = "method", source = "order.method")
|
||||
@Mapping(target = "amount", source = "order.amount")
|
||||
@Mapping(target = "payTime", source = "order.payTime")
|
||||
@Mapping(target = "closeTime", source = "order.closeTime")
|
||||
@Mapping(target = "expiredTime", source = "order.expiredTime")
|
||||
// 目标独有字段:源无对应属性,显式 ignore 标明不在此转换
|
||||
@Mapping(target = "realAmount", ignore = true)
|
||||
@Mapping(target = "refundStatus", ignore = true)
|
||||
NormalPayOrderResult toResult(NormalPayOrder order, PayTrade trade);
|
||||
/// 仅同名映射业务容器;资金字段由 Service 补
|
||||
NormalPayOrderResult toResult(NormalPayOrder order);
|
||||
}
|
||||
|
||||
@@ -47,9 +47,17 @@ public class NormalPayOrderQueryService {
|
||||
order = normalPayOrderManager.findByBizOrderNo(param.getBizOrderNo(), param.getAppId())
|
||||
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
|
||||
}
|
||||
// 联表资金凭证补充交易字段
|
||||
// 同名映射业务容器,再联表资金凭证补充/覆盖交易字段
|
||||
NormalPayOrderResult result = UnipayNormalPayOrderConvert.CONVERT.toResult(order);
|
||||
PayTrade trade = payTradeManager.findByContainerId(order.getId(),
|
||||
cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum.NORMAL.getCode()).orElse(null);
|
||||
return UnipayNormalPayOrderConvert.CONVERT.toResult(order, trade);
|
||||
if (Objects.nonNull(trade)) {
|
||||
result.setTradeNo(trade.getTradeNo());
|
||||
result.setOutOrderNo(trade.getOutOrderNo());
|
||||
// 对外契约 status = 资金态,覆盖容器业务 status
|
||||
result.setStatus(trade.getStatus());
|
||||
result.setRefundableBalance(trade.getRefundableBalance());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5 QR code",
|
||||
"mini_app": "Mini program QR code"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"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"
|
||||
"amountRequired": "Please enter the payment amount",
|
||||
"programTypeNotFound": "No matching QR code program type found: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "Authorization code cannot be blank",
|
||||
"size": "Authorization code cannot exceed 128 characters"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "Program type cannot be blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "Kode QR H5",
|
||||
"mini_app": "Kode QR mini program"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"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"
|
||||
"amountRequired": "Please enter the payment amount",
|
||||
"programTypeNotFound": "No matching QR code program type found: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "Kode otorisasi tidak boleh kosong",
|
||||
"size": "Kode otorisasi tidak boleh melebihi 128 karakter"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "Program type cannot be blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5コード",
|
||||
"mini_app": "ミニプログラムコード"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"notAssigned": "QRコードボードが加盟店に割り当てられていません",
|
||||
"idsEmpty": "QRコードボードを選択してください",
|
||||
"clientEnvNotSupport": "現在の環境ではコードプレート決済に対応していません。WeChat/Alipayなどでスキャンしてください",
|
||||
"amountRequired": "支払金額を入力してください"
|
||||
"amountRequired": "支払金額を入力してください",
|
||||
"programTypeNotFound": "対応するコード牌プログラム種別が見つかりません: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "認証コードは必須です",
|
||||
"size": "認証コードは128文字以内で入力してください"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "プログラム種別を入力してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5 코드",
|
||||
"mini_app": "미니프로그램 코드"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"notAssigned": "QR 코드 보드가 가맹점에 할당되지 않았습니다",
|
||||
"idsEmpty": "QR 코드 보드를 선택하세요",
|
||||
"clientEnvNotSupport": "현재 환경에서는 코드플레이트 결제를 지원하지 않습니다. WeChat/Alipay 등으로 스캔하세요",
|
||||
"amountRequired": "결제 금액을 입력하세요"
|
||||
"amountRequired": "결제 금액을 입력하세요",
|
||||
"programTypeNotFound": "일치하는 코드 프로그램 유형을 찾을 수 없습니다: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "인증 코드는 필수입니다",
|
||||
"size": "인증 코드는 128자를 초과할 수 없습니다"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "프로그램 유형은 비워 둘 수 없습니다"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "Kod QR H5",
|
||||
"mini_app": "Kod QR program mini"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"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"
|
||||
"amountRequired": "Please enter the payment amount",
|
||||
"programTypeNotFound": "No matching QR code program type found: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "Kod kebenaran tidak boleh kosong",
|
||||
"size": "Kod kebenaran tidak boleh melebihi 128 aksara"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "Program type cannot be blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "คิวอาร์โค้ด H5",
|
||||
"mini_app": "คิวอาร์โค้ดมินิโปรแกรม"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"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"
|
||||
"amountRequired": "Please enter the payment amount",
|
||||
"programTypeNotFound": "No matching QR code program type found: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "รหัสอนุญาตต้องไม่เว้นว่าง",
|
||||
"size": "รหัสอนุญาตต้องไม่เกิน 128 ตัวอักษร"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "Program type cannot be blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "Mã QR H5",
|
||||
"mini_app": "Mã QR mini program"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"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"
|
||||
"amountRequired": "Please enter the payment amount",
|
||||
"programTypeNotFound": "No matching QR code program type found: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "Mã ủy quyền không được để trống",
|
||||
"size": "Mã ủy quyền không được vượt quá 128 ký tự"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "Program type cannot be blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5码牌",
|
||||
"mini_app": "小程序码牌"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"notAssigned": "码牌未分配商户",
|
||||
"idsEmpty": "请选择码牌",
|
||||
"clientEnvNotSupport": "当前打开环境不支持码牌支付, 请使用微信/支付宝等扫码",
|
||||
"amountRequired": "请输入支付金额"
|
||||
"amountRequired": "请输入支付金额",
|
||||
"programTypeNotFound": "未找到对应的码牌程序类型: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "授权码不可为空",
|
||||
"size": "授权码不可超过128位"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "落地程序类型不能为空"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5碼牌",
|
||||
"mini_app": "小程序碼牌"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"notAssigned": "碼牌未分配商戶",
|
||||
"idsEmpty": "請選擇碼牌",
|
||||
"clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼",
|
||||
"amountRequired": "請輸入支付金額"
|
||||
"amountRequired": "請輸入支付金額",
|
||||
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "授權碼不可為空",
|
||||
"size": "授權碼不可超過128位"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "落地程式類型不能為空"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"h5": "H5碼牌",
|
||||
"mini_app": "小程序碼牌"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"notAssigned": "碼牌未分配商戶",
|
||||
"idsEmpty": "請選擇碼牌",
|
||||
"clientEnvNotSupport": "目前開啟環境不支援碼牌支付,請使用微信/支付寶等掃碼",
|
||||
"amountRequired": "請輸入支付金額"
|
||||
"amountRequired": "請輸入支付金額",
|
||||
"programTypeNotFound": "未找到對應的碼牌程式類型: {0}"
|
||||
}
|
||||
|
||||
@@ -743,6 +743,9 @@
|
||||
"oauthCode": {
|
||||
"notBlank": "授權碼不可為空",
|
||||
"size": "授權碼不可超過128位"
|
||||
},
|
||||
"programType": {
|
||||
"notBlank": "落地程式類型不能為空"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,10 @@ public interface NotifyNoticeConvert {
|
||||
/// 公告转铃铛摘要(类型固定为公告, 内容摘要来自正文, 已读状态由服务层填充)
|
||||
@Mapping(target = "type", constant = "notice")
|
||||
@Mapping(target = "message", source = "content")
|
||||
@Mapping(target = "isRead", ignore = true)
|
||||
@Mapping(target = "link", ignore = true)
|
||||
@Mapping(target = "htmlContent", ignore = true)
|
||||
NotifyNoticeBriefResult toBrief(NotifyNotice in);
|
||||
|
||||
/// 个人消息转铃铛摘要(类型固定为个人消息, 内容摘要来自正文)
|
||||
@Mapping(target = "type", constant = "message")
|
||||
@Mapping(target = "message", source = "content")
|
||||
@Mapping(target = "severity", ignore = true)
|
||||
@Mapping(target = "isTop", ignore = true)
|
||||
@Mapping(target = "htmlContent", ignore = true)
|
||||
NotifyNoticeBriefResult convert(NotifyMessage in);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import cn.daxpay.open.platform.system.param.mobile.MobileAppParam;
|
||||
import cn.daxpay.open.platform.system.result.mobile.MobileAppResult;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
@@ -13,24 +12,19 @@ import org.mapstruct.factory.Mappers;
|
||||
/// # 移动端应用配置外壳转换
|
||||
///
|
||||
/// 嵌套平台配置与 appConfig 密文由 [cn.daxpay.open.platform.system.service.mobile.MobileAppService] 处理,
|
||||
/// 此处仅映射外壳字段。
|
||||
/// 此处仅同名映射外壳字段。
|
||||
@Mapper
|
||||
public interface MobileAppConvert {
|
||||
|
||||
MobileAppConvert CONVERT = Mappers.getMapper(MobileAppConvert.class);
|
||||
|
||||
/// 实体转结果(嵌套配置由 Service 填充)
|
||||
@Mapping(target = "wxMini", ignore = true)
|
||||
@Mapping(target = "alipayMini", ignore = true)
|
||||
@Mapping(target = "dyMini", ignore = true)
|
||||
MobileAppResult toResult(MobileApp entity);
|
||||
|
||||
/// 参数转实体(新增, appConfig 由 Service 序列化后写入)
|
||||
@Mapping(target = "appConfig", ignore = true)
|
||||
MobileApp toEntity(MobileAppParam param);
|
||||
|
||||
/// 参数拷贝到实体(更新)
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
@Mapping(target = "appConfig", ignore = true)
|
||||
void copy(MobileAppParam param, @MappingTarget MobileApp entity);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user