mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 06:05:33 +08:00
feat(device): 运营端支付码牌管理(批量生成/绑定商户/商户名翻译)
支持空白码库存与划拨绑定,分页翻译商户名称;绑定默认走商户默认应用。 补齐码牌菜单落地、batch_no 升级 SQL 与中英校验/错误文案。
This commit is contained in:
@@ -134,7 +134,7 @@ INSERT INTO "public"."iam_perm_menu" VALUES (90201, 9, 'device:vendor_config', '
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (40503, 40105, 'payment:wechat:isv', 'admin', 'WechatIsvAppManage', '微信服务商应用', 'WeChat ISV Application', 'menu.payment.config.wechatApp', NULL, 't', 'f', '/payment/channel/wechat/manage/app/WechatIsvAppManage', '/payment/config/product/wechat-app-manage', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (90202, 9, 'device:speaker', 'admin', 'DeviceSpeakerList', '云音箱管理', 'Speaker Management', 'menu.device.speaker', 'lucide:speaker', 'f', 'f', '/payment/device/speaker/DeviceSpeakerList', '/device/speaker', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.647632+00', '2026-06-25 12:51:25.245363+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (8, NULL, 'develop', 'admin', 'Develop', '开发调试', 'Develop', 'menu.develop', 'lucide:wrench', 'f', 'f', NULL, '/develop', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-23 16:00:00+00', '2026-06-23 16:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (901, 9, 'device:qrcode', 'admin', 'DeviceQrCode', '码牌管理', 'QR Code Management', 'menu.device.qrcode', 'lucide:qr-code', 't', 'f', '/_core/fallback/coming-soon', '/device/qrcode', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.634639+00', '2026-06-25 12:51:25.24304+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (901, 9, 'device:qrcode', 'admin', 'DeviceQrCode', '码牌管理', 'QR Code Management', 'menu.device.qrcode', 'lucide:qr-code', 'f', 'f', '/payment/device/qrcode/DeviceQrCode', '/device/qrcode', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.634639+00', '2026-06-25 12:51:25.24304+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (204, 2, 'demos:artemis', 'admin', 'ArtemisDemo', '消息队列演示', 'Message Queue Demo', 'menu.demos.artemis', 'lucide:send', 'f', 'f', '/demos/artemis/ArtemisDemo', '/demos/artemis', NULL, 4, 'f', 't', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-18 00:00:00+00', '2026-06-18 00:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (900001, 304, 'iam:social:login-config', 'admin', 'ThirdPlatform', '三方平台管理', 'Third-party Platform Management', 'menu.system.config.thirdPlatform', 'lucide:share-2', 'f', 'f', 'views/system/config/third-platform/ThirdPlatform', '/system/config/third-platform', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (30403, 304, 'system:protocol', 'admin', 'UserProtocol', '用户协议', 'User Protocol', 'menu.system.config.protocol', 'lucide:file-text', 'f', 'f', '/system/protocol/UserProtocolList', '/system/config/protocol', NULL, 10, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-24 10:32:13.183371+00', '2026-06-24 10:32:13.183371+00');
|
||||
|
||||
@@ -201,3 +201,13 @@ COMMENT ON COLUMN pay_platform_mobile_app.deleted IS '逻辑删除标记';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_pay_platform_mobile_app_type_platform
|
||||
ON pay_platform_mobile_app (app_type, platform) WHERE deleted = false;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 码牌: 支持空白库存与划拨(批次号 + 商户号可空)
|
||||
-- ------------------------------------------------------------
|
||||
ALTER TABLE device_qr_code ADD COLUMN IF NOT EXISTS batch_no varchar(64);
|
||||
ALTER TABLE device_qr_code ALTER COLUMN mch_no DROP NOT NULL;
|
||||
|
||||
COMMENT ON COLUMN device_qr_code.batch_no IS '批次号(批量创建空白码时写入)';
|
||||
COMMENT ON COLUMN device_qr_code.mch_no IS '所属商户号(空白码为空, 划拨后写入)';
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
|
||||
/// # 码牌金额类型
|
||||
///
|
||||
/// 字典: qrcode_amount_type
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum QrCodeAmountTypeEnum implements I18nSupport {
|
||||
|
||||
/// 自定义金额(扫码后由用户输入)
|
||||
RANDOM("random"),
|
||||
/// 固定金额(创建时设定, 扫码后直接支付)
|
||||
FIXED("fixed");
|
||||
|
||||
/// 编码
|
||||
private final String code;
|
||||
|
||||
/// 翻译 key 前缀
|
||||
@Override
|
||||
public String getI18nPrefix() {
|
||||
return "enum.qrcode_amount_type";
|
||||
}
|
||||
|
||||
/// 根据编码查找
|
||||
public static QrCodeAmountTypeEnum findByCode(String code) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getCode().equals(code))
|
||||
.findFirst()
|
||||
// 通用: 未找到对应的码牌金额类型: {0}
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.amountTypeNotFound", code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
|
||||
/// # 码牌状态
|
||||
///
|
||||
/// 字典: qrcode_status
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum QrCodeStatusEnum implements I18nSupport {
|
||||
|
||||
/// 启用
|
||||
ENABLED("enabled"),
|
||||
/// 停用
|
||||
DISABLED("disabled");
|
||||
|
||||
/// 编码
|
||||
private final String code;
|
||||
|
||||
/// 翻译 key 前缀
|
||||
@Override
|
||||
public String getI18nPrefix() {
|
||||
return "enum.qrcode_status";
|
||||
}
|
||||
|
||||
/// 根据编码查找
|
||||
public static QrCodeStatusEnum findByCode(String code) {
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> e.getCode().equals(code))
|
||||
.findFirst()
|
||||
// 通用: 未找到对应的码牌状态: {0}
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.statusNotFound", code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.controller.admin;
|
||||
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeBatchParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeBindMerchantParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeQuery;
|
||||
import cn.daxpay.open.payment.device.qrcode.result.DeviceQrCodeResult;
|
||||
import cn.daxpay.open.payment.device.qrcode.service.DeviceQrCodeAdminService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
import cn.daxpay.open.platform.core.rest.result.PageResult;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import cn.daxpay.open.platform.core.validation.ValidationGroup;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付码牌管理(运营端)
|
||||
///
|
||||
@PermCode(menuCode = "device:qrcode")
|
||||
@Validated
|
||||
@Tag(name = "支付码牌管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/device/qrcode")
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceQrCodeAdminController {
|
||||
|
||||
private final DeviceQrCodeAdminService deviceQrCodeAdminService;
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "新增码牌(快捷绑定商户)")
|
||||
@PostMapping("/add")
|
||||
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) DeviceQrCodeParam param) {
|
||||
deviceQrCodeAdminService.add(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "批量创建空白码牌")
|
||||
@PostMapping("/create-batch")
|
||||
public Result<Void> createBatch(@RequestBody @Validated DeviceQrCodeBatchParam param) {
|
||||
deviceQrCodeAdminService.createBatch(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "码牌查看", nameEn = "QrCode View")
|
||||
@Operation(summary = "判断批次号是否已存在")
|
||||
@GetMapping("/exists-by-batch-no")
|
||||
public Result<Boolean> existsByBatchNo(@NotBlank(message = "{validation.field.batchNo.notBlank}") String batchNo) {
|
||||
return Res.ok(deviceQrCodeAdminService.existsByBatchNo(batchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "批量绑定商户")
|
||||
@PostMapping("/bind-merchant")
|
||||
public Result<Void> bindMerchant(@RequestBody @Validated DeviceQrCodeBindMerchantParam param) {
|
||||
deviceQrCodeAdminService.bindMerchant(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "批量解绑商户")
|
||||
@PostMapping("/unbind-merchant")
|
||||
public Result<Void> unbindMerchant(@RequestBody @NotEmpty(message = "{validation.field.ids.notEmpty}") List<Long> ids) {
|
||||
deviceQrCodeAdminService.unbindMerchant(ids);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "修改码牌")
|
||||
@PostMapping("/update")
|
||||
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) DeviceQrCodeParam param) {
|
||||
deviceQrCodeAdminService.update(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "码牌查看", nameEn = "QrCode View")
|
||||
@Operation(summary = "码牌分页")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<DeviceQrCodeResult>> page(PageParam pageParam, DeviceQrCodeQuery query) {
|
||||
return Res.ok(deviceQrCodeAdminService.page(pageParam, query));
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "码牌查看", nameEn = "QrCode View")
|
||||
@Operation(summary = "根据id查询码牌")
|
||||
@GetMapping("/get")
|
||||
public Result<DeviceQrCodeResult> findById(@NotNull(message = "{validation.field.id.notNull}") Long id) {
|
||||
return Res.ok(deviceQrCodeAdminService.findById(id));
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "删除码牌")
|
||||
@PostMapping("/delete")
|
||||
public Result<Void> delete(@NotNull(message = "{validation.field.id.notNull}") Long id) {
|
||||
deviceQrCodeAdminService.delete(id);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = "manage", nameCn = "码牌管理", nameEn = "QrCode Manage")
|
||||
@Operation(summary = "修改码牌状态")
|
||||
@PostMapping("/change-status")
|
||||
public Result<Void> changeStatus(@NotNull(message = "{validation.field.id.notNull}") Long id,
|
||||
@NotBlank(message = "{validation.field.status.notBlank}") String status) {
|
||||
deviceQrCodeAdminService.changeStatus(id, status);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.controller.client;
|
||||
|
||||
import cn.daxpay.open.payment.device.qrcode.result.CodePayInfoResult;
|
||||
import cn.daxpay.open.payment.device.qrcode.service.CodePayAssistService;
|
||||
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;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/// # 码牌支付(公开/H5 侧)
|
||||
///
|
||||
/// 供 H5 码牌支付页(/code-pay/:code)扫码后调用, 无需登录态。
|
||||
@IgnoreAuth
|
||||
@Validated
|
||||
@Tag(name = "码牌支付")
|
||||
@RestController
|
||||
@RequestMapping("/client/device/qrcode")
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceQrCodeClientController {
|
||||
|
||||
private final CodePayAssistService codePayAssistService;
|
||||
|
||||
@Operation(summary = "根据码牌编码查询支付信息")
|
||||
@GetMapping("/get-by-code")
|
||||
public Result<CodePayInfoResult> getByCode(@NotBlank(message = "{validation.field.code.notBlank}") String code) {
|
||||
return Res.ok(codePayAssistService.getByCode(code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.convert;
|
||||
|
||||
import cn.daxpay.open.payment.device.qrcode.entity.DeviceQrCode;
|
||||
import cn.daxpay.open.payment.device.qrcode.result.DeviceQrCodeResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 支付码牌转换
|
||||
///
|
||||
@Mapper
|
||||
public interface DeviceQrCodeConvert {
|
||||
DeviceQrCodeConvert CONVERT = Mappers.getMapper(DeviceQrCodeConvert.class);
|
||||
|
||||
DeviceQrCodeResult toResult(DeviceQrCode in);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.dao;
|
||||
|
||||
import cn.daxpay.open.payment.device.qrcode.entity.DeviceQrCode;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeQuery;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.query.generator.QueryGenerator;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 支付码牌管理
|
||||
///
|
||||
@Slf4j
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceQrCodeManager extends BaseManager<DeviceQrCodeMapper, DeviceQrCode> {
|
||||
|
||||
/// 根据码牌编码查询
|
||||
public Optional<DeviceQrCode> findByCode(String code) {
|
||||
return findByField(DeviceQrCode::getCode, code);
|
||||
}
|
||||
|
||||
/// 判断码牌编码是否存在(排除指定id, excludeId 为 null 时不排除)
|
||||
public boolean existsByCode(String code, Long excludeId) {
|
||||
if (excludeId == null) {
|
||||
return existedByField(DeviceQrCode::getCode, code);
|
||||
}
|
||||
return existedByField(DeviceQrCode::getCode, code, excludeId);
|
||||
}
|
||||
|
||||
/// 判断批次号是否已存在
|
||||
public boolean existedByBatchNo(String batchNo) {
|
||||
return existedByField(DeviceQrCode::getBatchNo, batchNo);
|
||||
}
|
||||
|
||||
/// 批量绑定商户与应用
|
||||
public void bindMerchant(Collection<Long> ids, String mchNo, String appId) {
|
||||
lambdaUpdate()
|
||||
.set(DeviceQrCode::getMchNo, mchNo)
|
||||
.set(DeviceQrCode::getAppId, appId)
|
||||
.in(DeviceQrCode::getId, ids)
|
||||
.update();
|
||||
}
|
||||
|
||||
/// 批量解绑商户与应用(回空白库存)
|
||||
public void unbindMerchant(Collection<Long> ids) {
|
||||
lambdaUpdate()
|
||||
.set(DeviceQrCode::getMchNo, null)
|
||||
.set(DeviceQrCode::getAppId, null)
|
||||
.in(DeviceQrCode::getId, ids)
|
||||
.update();
|
||||
}
|
||||
|
||||
/// 分页
|
||||
public Page<DeviceQrCode> page(PageParam pageParam, DeviceQrCodeQuery query) {
|
||||
Page<DeviceQrCode> mpPage = MpUtil.getMpPage(pageParam);
|
||||
QueryWrapper<DeviceQrCode> wrapper = QueryGenerator.generator(query);
|
||||
return this.page(mpPage, wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.dao;
|
||||
|
||||
import cn.daxpay.open.payment.device.qrcode.entity.DeviceQrCode;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/// # 支付码牌
|
||||
@Mapper
|
||||
public interface DeviceQrCodeMapper extends MPJBaseMapper<DeviceQrCode> {
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.entity;
|
||||
|
||||
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.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付码牌
|
||||
///
|
||||
/// 记录码牌与商户/应用的绑定关系, 扫码后进入 H5 码牌支付页(/code-pay/:code)。
|
||||
/// 支持运营端批量创建空白码牌后划拨给商户: mchNo 初始为空, 绑定后写入。
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("device_qr_code")
|
||||
public class DeviceQrCode extends MpBaseEntity implements ToResult<DeviceQrCodeResult> {
|
||||
|
||||
/// 码牌编码(系统自动生成, 唯一, 二维码 :code 参数)
|
||||
private String code;
|
||||
|
||||
/// 码牌名称
|
||||
private String name;
|
||||
|
||||
/// 批次号(批量创建空白码时写入, 单条新增可空)
|
||||
private String batchNo;
|
||||
|
||||
/// 所属商户号(划拨后设置, 初始为空表示空白码)
|
||||
private String mchNo;
|
||||
|
||||
/// 关联应用号(可空, 空白码为空; 绑定后空则使用商户默认应用)
|
||||
private String appId;
|
||||
|
||||
/// 金额类型: random-自定义金额 / fixed-固定金额
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
private String amountType;
|
||||
|
||||
/// 固定金额(分, amount_type=fixed 时必填)
|
||||
private Long fixedAmount;
|
||||
|
||||
/// 状态: enabled-启用 / disabled-停用
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeStatusEnum
|
||||
private String status;
|
||||
|
||||
/// 备注
|
||||
private String remark;
|
||||
|
||||
/// 转换为返回对象
|
||||
@Override
|
||||
public DeviceQrCodeResult toResult() {
|
||||
return DeviceQrCodeConvert.CONVERT.toResult(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 批量创建空白码牌参数
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "批量创建空白码牌参数")
|
||||
public class DeviceQrCodeBatchParam {
|
||||
|
||||
/// 批次号(全局唯一, 同时作为码牌编码前缀)
|
||||
@Schema(description = "批次号")
|
||||
@NotBlank(message = "{validation.field.batchNo.notBlank}")
|
||||
@Size(max = 64, message = "{validation.field.batchNo.size}")
|
||||
private String batchNo;
|
||||
|
||||
/// 创建数量(1-999)
|
||||
@Schema(description = "创建数量")
|
||||
@NotNull(message = "{validation.field.count.notNull}")
|
||||
@Min(value = 1, message = "{validation.field.count.min}")
|
||||
@Max(value = 999, message = "{validation.field.count.max}")
|
||||
private Integer count;
|
||||
|
||||
/// 码牌名称(可选, 整批统一名称)
|
||||
@Schema(description = "码牌名称")
|
||||
@Size(max = 100, message = "{validation.field.qrCodeName.size}")
|
||||
private String name;
|
||||
|
||||
/// 金额类型: random-自定义金额 / fixed-固定金额
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
@NotBlank(message = "{validation.field.amountType.notBlank}")
|
||||
private String amountType;
|
||||
|
||||
/// 固定金额(分, amount_type=fixed 时必填, 由 Service 层校验)
|
||||
@Schema(description = "固定金额(分)")
|
||||
private Long fixedAmount;
|
||||
|
||||
/// 状态(enabled/disabled), 空则默认启用
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeStatusEnum
|
||||
@Schema(description = "状态(enabled-启用/disabled-停用), 空则默认启用")
|
||||
private String status;
|
||||
|
||||
/// 备注
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 500, message = "{validation.field.remark.size}")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 码牌绑定商户参数
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "码牌绑定商户参数")
|
||||
public class DeviceQrCodeBindMerchantParam {
|
||||
|
||||
/// 码牌主键列表
|
||||
@Schema(description = "码牌主键列表")
|
||||
@NotEmpty(message = "{validation.field.ids.notEmpty}")
|
||||
private List<Long> ids;
|
||||
|
||||
/// 商户号
|
||||
@Schema(description = "商户号")
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}")
|
||||
private String mchNo;
|
||||
|
||||
/// 关联应用号(可空, 空则使用商户默认应用)
|
||||
@Schema(description = "关联应用号")
|
||||
@Size(max = 50, message = "{validation.field.appId.size}")
|
||||
private String appId;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.param;
|
||||
|
||||
import cn.daxpay.open.platform.core.validation.ValidationGroup;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付码牌
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付码牌")
|
||||
public class DeviceQrCodeParam {
|
||||
|
||||
/// 主键
|
||||
@Schema(description = "主键")
|
||||
@NotNull(message = "{validation.field.id.notNull}", groups = ValidationGroup.edit.class)
|
||||
private Long id;
|
||||
|
||||
/// 码牌名称
|
||||
@Schema(description = "码牌名称")
|
||||
@NotBlank(message = "{validation.field.qrCodeName.notBlank}")
|
||||
@Size(max = 100, message = "{validation.field.qrCodeName.size}")
|
||||
private String name;
|
||||
|
||||
/// 商户号(新增快捷绑定时必填; 编辑不改归属, 仅 add 校验)
|
||||
@Schema(description = "商户号")
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}", groups = ValidationGroup.add.class)
|
||||
private String mchNo;
|
||||
|
||||
/// 关联应用号(可空, 空则使用商户默认应用)
|
||||
@Schema(description = "关联应用号")
|
||||
@Size(max = 50, message = "{validation.field.appId.size}")
|
||||
private String appId;
|
||||
|
||||
/// 金额类型: random-自定义金额 / fixed-固定金额
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
@NotBlank(message = "{validation.field.amountType.notBlank}")
|
||||
private String amountType;
|
||||
|
||||
/// 固定金额(分, amount_type=fixed 时必填, 由 Service 层校验)
|
||||
@Schema(description = "固定金额(分)")
|
||||
private Long fixedAmount;
|
||||
|
||||
/// 状态(enabled/disabled)
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeStatusEnum
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/// 备注
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 500, message = "{validation.field.remark.size}")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.param;
|
||||
|
||||
import cn.daxpay.open.platform.core.annotation.QueryParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付码牌查询参数
|
||||
@Data
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付码牌查询参数")
|
||||
public class DeviceQrCodeQuery {
|
||||
|
||||
/// 码牌编码
|
||||
@Schema(description = "码牌编码")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
|
||||
private String code;
|
||||
|
||||
/// 码牌名称
|
||||
@Schema(description = "码牌名称")
|
||||
private String name;
|
||||
|
||||
/// 批次号
|
||||
@Schema(description = "批次号")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String batchNo;
|
||||
|
||||
/// 商户号
|
||||
@Schema(description = "商户号")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String mchNo;
|
||||
|
||||
/// 金额类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String amountType;
|
||||
|
||||
/// 状态
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeStatusEnum
|
||||
@Schema(description = "状态")
|
||||
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 码牌支付信息(公开接口返回, 脱敏)
|
||||
///
|
||||
/// 供 H5 码牌支付页(/code-pay/:code)扫码后查询展示, 不含 appId/mchNo 等内部字段。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "码牌支付信息")
|
||||
public class CodePayInfoResult {
|
||||
|
||||
@Schema(description = "码牌编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "码牌名称")
|
||||
private String name;
|
||||
|
||||
/// 金额类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
private String amountType;
|
||||
|
||||
@Schema(description = "固定金额(分)")
|
||||
private Long fixedAmount;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.result;
|
||||
|
||||
import cn.daxpay.open.payment.merchant.entity.info.MerchantInfo;
|
||||
import cn.daxpay.open.platform.core.annotation.Trans;
|
||||
import cn.daxpay.open.platform.core.result.BaseResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付码牌
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付码牌")
|
||||
public class DeviceQrCodeResult extends BaseResult {
|
||||
|
||||
@Schema(description = "码牌编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "码牌名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@Schema(description = "所属商户号(空表示未绑定)")
|
||||
private String mchNo;
|
||||
|
||||
/// 商户名称(由 mchNo 翻译, 未绑定为空)
|
||||
@Trans(entity = MerchantInfo.class, source = "mchNo", result = "mchName")
|
||||
@Schema(description = "商户名称")
|
||||
private String mchName;
|
||||
|
||||
@Schema(description = "关联应用号(空表示使用商户默认应用)")
|
||||
private String appId;
|
||||
|
||||
/// 金额类型
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum
|
||||
@Schema(description = "金额类型(random-自定义/fixed-固定)")
|
||||
private String amountType;
|
||||
|
||||
@Schema(description = "固定金额(分)")
|
||||
private Long fixedAmount;
|
||||
|
||||
/// 状态
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeStatusEnum
|
||||
@Schema(description = "状态(enabled-启用/disabled-停用)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.service;
|
||||
|
||||
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.device.qrcode.result.CodePayInfoResult;
|
||||
import cn.daxpay.open.payment.merchant.dto.MerchantAccessInfo;
|
||||
import cn.daxpay.open.payment.merchant.service.query.MerchantAccessQueryService;
|
||||
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.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 码牌支付编排(公开/H5 侧)
|
||||
///
|
||||
/// 一期: 仅提供按编码查询码牌信息(供 H5 扫码展示)。
|
||||
/// 二期: 扩展 OAuth 重定向获取 openId 与发起 JSAPI 支付(复用 ChannelAuthService/NormalPayService)。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CodePayAssistService {
|
||||
|
||||
private final DeviceQrCodeManager deviceQrCodeManager;
|
||||
private final MerchantAccessQueryService merchantAccessQueryService;
|
||||
|
||||
/// 根据码牌编码查询支付信息(公开接口, 脱敏返回)
|
||||
///
|
||||
/// 校验码牌存在、启用、已分配商户, 且商户存在。
|
||||
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 统一处理)
|
||||
MerchantAccessInfo merchant = merchantAccessQueryService.getMerchantByMchNo(entity.getMchNo());
|
||||
if (merchant == null) {
|
||||
// 码牌: 商户不存在
|
||||
throw new DataNotExistException("error.device.qrcode.mchNotFound");
|
||||
}
|
||||
return new CodePayInfoResult()
|
||||
.setCode(entity.getCode())
|
||||
.setName(entity.getName())
|
||||
.setAmountType(entity.getAmountType())
|
||||
.setFixedAmount(entity.getFixedAmount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package cn.daxpay.open.payment.device.qrcode.service;
|
||||
|
||||
import cn.daxpay.open.payment.common.service.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.device.qrcode.param.DeviceQrCodeBatchParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeBindMerchantParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeParam;
|
||||
import cn.daxpay.open.payment.device.qrcode.param.DeviceQrCodeQuery;
|
||||
import cn.daxpay.open.payment.device.qrcode.result.DeviceQrCodeResult;
|
||||
import cn.daxpay.open.payment.merchant.dto.MchAppInfoAccessInfo;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.daxpay.open.platform.common.translate.service.TransService;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.core.exception.operation.OperationFailException;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
import cn.daxpay.open.platform.core.rest.result.PageResult;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
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.stream.IntStream;
|
||||
|
||||
/// # 支付码牌管理(运营端)
|
||||
///
|
||||
/// 维护码牌台账, 支持批量创建空白码与划拨绑定商户。
|
||||
/// 扫码后的支付编排由 [CodePayAssistService] 承担。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceQrCodeAdminService {
|
||||
|
||||
/// 码牌编码前缀(单条快捷新增)
|
||||
private static final String CODE_PREFIX = "QR";
|
||||
|
||||
private final DeviceQrCodeManager deviceQrCodeManager;
|
||||
private final MerchantContextLoader merchantContextLoader;
|
||||
private final TransService transService;
|
||||
|
||||
/// 新增码牌(快捷路径: 直接绑定商户, 自动生成编码)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void add(DeviceQrCodeParam param) {
|
||||
// 金额类型校验
|
||||
QrCodeAmountTypeEnum amountType = QrCodeAmountTypeEnum.findByCode(param.getAmountType());
|
||||
// 固定金额: fixed 类型必填且大于 0
|
||||
validateFixedAmount(amountType, param.getFixedAmount());
|
||||
// 应用解析: appId 空则取商户默认应用, 否则校验启用与归属, 回填 appId
|
||||
String appId = resolveAppId(param.getMchNo(), param.getAppId());
|
||||
DeviceQrCode entity = new DeviceQrCode()
|
||||
.setCode(generateCode())
|
||||
.setName(param.getName())
|
||||
.setMchNo(param.getMchNo())
|
||||
.setAppId(appId)
|
||||
.setAmountType(amountType.getCode())
|
||||
.setFixedAmount(amountType == QrCodeAmountTypeEnum.FIXED ? param.getFixedAmount() : null)
|
||||
.setStatus(QrCodeStatusEnum.ENABLED.getCode())
|
||||
.setRemark(param.getRemark());
|
||||
deviceQrCodeManager.save(entity);
|
||||
}
|
||||
|
||||
/// 批量创建空白码牌(不绑商户, 进入平台库存)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createBatch(DeviceQrCodeBatchParam param) {
|
||||
// 批次号唯一
|
||||
if (deviceQrCodeManager.existedByBatchNo(param.getBatchNo())) {
|
||||
// 码牌: 批次号已存在
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.batchNoExists");
|
||||
}
|
||||
// 金额类型校验
|
||||
QrCodeAmountTypeEnum amountType = QrCodeAmountTypeEnum.findByCode(param.getAmountType());
|
||||
validateFixedAmount(amountType, param.getFixedAmount());
|
||||
Long fixedAmount = amountType == QrCodeAmountTypeEnum.FIXED ? param.getFixedAmount() : null;
|
||||
// 状态: 空则默认启用
|
||||
String status = StrUtil.isBlank(param.getStatus())
|
||||
? QrCodeStatusEnum.ENABLED.getCode()
|
||||
: QrCodeStatusEnum.findByCode(param.getStatus()).getCode();
|
||||
// 编码: 批次号 + 三位序号(001-999)
|
||||
List<DeviceQrCode> list = IntStream.rangeClosed(1, param.getCount())
|
||||
.mapToObj(i -> new DeviceQrCode()
|
||||
.setCode(param.getBatchNo() + String.format("%03d", i))
|
||||
.setName(param.getName())
|
||||
.setBatchNo(param.getBatchNo())
|
||||
.setAmountType(amountType.getCode())
|
||||
.setFixedAmount(fixedAmount)
|
||||
.setStatus(status)
|
||||
.setRemark(param.getRemark()))
|
||||
.toList();
|
||||
deviceQrCodeManager.saveAll(list);
|
||||
}
|
||||
|
||||
/// 判断批次号是否已存在
|
||||
public boolean existsByBatchNo(String batchNo) {
|
||||
return deviceQrCodeManager.existedByBatchNo(batchNo);
|
||||
}
|
||||
|
||||
/// 批量绑定商户(可覆盖已绑定归属)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void bindMerchant(DeviceQrCodeBindMerchantParam param) {
|
||||
// 应用解析: appId 空则取商户默认应用
|
||||
String appId = resolveAppId(param.getMchNo(), param.getAppId());
|
||||
deviceQrCodeManager.bindMerchant(param.getIds(), param.getMchNo(), appId);
|
||||
}
|
||||
|
||||
/// 批量解绑商户(回空白库存, 保留编码/批次/金额配置)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void unbindMerchant(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
// 码牌: 请选择码牌
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.idsEmpty");
|
||||
}
|
||||
deviceQrCodeManager.unbindMerchant(ids);
|
||||
}
|
||||
|
||||
/// 修改码牌(编码/归属不可改; 归属走 bind/unbind)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(DeviceQrCodeParam param) {
|
||||
DeviceQrCode entity = deviceQrCodeManager.findById(param.getId())
|
||||
// 码牌: 码牌不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound"));
|
||||
// 金额类型校验
|
||||
QrCodeAmountTypeEnum amountType = QrCodeAmountTypeEnum.findByCode(param.getAmountType());
|
||||
validateFixedAmount(amountType, param.getFixedAmount());
|
||||
// 仅更新业务配置, 不改 mchNo/appId
|
||||
entity.setName(param.getName())
|
||||
.setAmountType(amountType.getCode())
|
||||
.setFixedAmount(amountType == QrCodeAmountTypeEnum.FIXED ? param.getFixedAmount() : null)
|
||||
.setRemark(param.getRemark());
|
||||
deviceQrCodeManager.updateById(entity);
|
||||
}
|
||||
|
||||
/// 分页
|
||||
public PageResult<DeviceQrCodeResult> page(PageParam pageParam, DeviceQrCodeQuery query) {
|
||||
PageResult<DeviceQrCodeResult> pageResult = MpUtil.toPageResult(deviceQrCodeManager.page(pageParam, query));
|
||||
// 翻译商户名称(mchNo -> mchName)
|
||||
transService.translate(pageResult);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
/// 根据id查询
|
||||
public DeviceQrCodeResult findById(Long id) {
|
||||
DeviceQrCodeResult result = deviceQrCodeManager.findById(id)
|
||||
// 码牌: 码牌不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound"))
|
||||
.toResult();
|
||||
// 翻译商户名称
|
||||
transService.translate(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 删除
|
||||
public void delete(Long id) {
|
||||
deviceQrCodeManager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound"));
|
||||
deviceQrCodeManager.deleteById(id);
|
||||
}
|
||||
|
||||
/// 修改状态(启用/停用)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void changeStatus(Long id, String status) {
|
||||
QrCodeStatusEnum statusEnum = QrCodeStatusEnum.findByCode(status);
|
||||
DeviceQrCode entity = deviceQrCodeManager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("error.device.qrcode.notFound"));
|
||||
entity.setStatus(statusEnum.getCode());
|
||||
deviceQrCodeManager.updateById(entity);
|
||||
}
|
||||
|
||||
/// 生成码牌编码: 前缀 + 雪花ID
|
||||
private String generateCode() {
|
||||
return CODE_PREFIX + IdUtil.getSnowflakeNextId();
|
||||
}
|
||||
|
||||
/// 应用解析: 复用 [MerchantContextLoader.resolveApp], appId 空取商户默认应用, 返回解析后的 appId
|
||||
private String resolveAppId(String mchNo, String appId) {
|
||||
MchAppInfoAccessInfo mchApp = merchantContextLoader.resolveApp(mchNo, appId);
|
||||
return mchApp.getAppId();
|
||||
}
|
||||
|
||||
/// 固定金额校验: fixed 类型必填且大于 0
|
||||
private void validateFixedAmount(QrCodeAmountTypeEnum amountType, Long fixedAmount) {
|
||||
if (amountType == QrCodeAmountTypeEnum.FIXED) {
|
||||
if (fixedAmount == null || fixedAmount <= 0) {
|
||||
// 码牌: 固定金额必须大于 0
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.fixedAmountInvalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"random": "Custom Amount",
|
||||
"fixed": "Fixed Amount"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
"clientSecret": {
|
||||
"notBlank": "Client secret cannot be blank"
|
||||
},
|
||||
"clientKey": {
|
||||
"notBlank": "Client Key cannot be blank"
|
||||
},
|
||||
"clientCode": {
|
||||
"notBlank": "Client code cannot be blank"
|
||||
},
|
||||
@@ -552,6 +555,25 @@
|
||||
"vendorConfigId": {
|
||||
"notNull": "Vendor config cannot be null"
|
||||
},
|
||||
"qrCodeName": {
|
||||
"notBlank": "QR code name cannot be blank",
|
||||
"size": "QR code name cannot exceed 100 characters"
|
||||
},
|
||||
"amountType": {
|
||||
"notBlank": "Amount type cannot be blank"
|
||||
},
|
||||
"batchNo": {
|
||||
"notBlank": "Batch number cannot be blank",
|
||||
"size": "Batch number cannot exceed 64 characters"
|
||||
},
|
||||
"count": {
|
||||
"notNull": "Count cannot be null",
|
||||
"min": "Count cannot be less than 1",
|
||||
"max": "Count cannot be greater than 999"
|
||||
},
|
||||
"ids": {
|
||||
"notEmpty": "Please select QR code boards"
|
||||
},
|
||||
"configName": {
|
||||
"notBlank": "Config name cannot be blank"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"random": "自定义金额",
|
||||
"fixed": "固定金额"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"enabled": "启用",
|
||||
"disabled": "停用"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"notFound": "码牌不存在",
|
||||
"disabled": "码牌已停用",
|
||||
"mchNotFound": "码牌所属商户不存在",
|
||||
"amountTypeNotFound": "未找到对应的码牌金额类型: {0}",
|
||||
"statusNotFound": "未找到对应的码牌状态: {0}",
|
||||
"fixedAmountInvalid": "固定金额必须大于0",
|
||||
"batchNoExists": "批次号已存在",
|
||||
"notAssigned": "码牌未分配商户",
|
||||
"idsEmpty": "请选择码牌"
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
"clientSecret": {
|
||||
"notBlank": "客户端密钥不能为空"
|
||||
},
|
||||
"clientKey": {
|
||||
"notBlank": "Client Key不能为空"
|
||||
},
|
||||
"clientCode": {
|
||||
"notBlank": "终端不能为空"
|
||||
},
|
||||
@@ -555,6 +558,25 @@
|
||||
"vendorConfigId": {
|
||||
"notNull": "厂商配置不能为空"
|
||||
},
|
||||
"qrCodeName": {
|
||||
"notBlank": "码牌名称不能为空",
|
||||
"size": "码牌名称不可超过100位"
|
||||
},
|
||||
"amountType": {
|
||||
"notBlank": "金额类型不能为空"
|
||||
},
|
||||
"batchNo": {
|
||||
"notBlank": "批次号不能为空",
|
||||
"size": "批次号不可超过64位"
|
||||
},
|
||||
"count": {
|
||||
"notNull": "创建数量不能为空",
|
||||
"min": "创建数量不能小于1",
|
||||
"max": "创建数量不能大于999"
|
||||
},
|
||||
"ids": {
|
||||
"notEmpty": "请选择码牌"
|
||||
},
|
||||
"configName": {
|
||||
"notBlank": "配置名称不能为空"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user