feat(masterdata): 迁移主数据模块到 payment.masterdata

- 新建 masterdata/constants/{channel/capability/method/product/provider} 子域
- 迁移 masterdata/config (ChannelConfig)
- 包结构: business-domain-first (masterdata.{subdomain}.{layer})
- 继承 MpBaseEntity (平台层)
- 删除 old/pay/service/route/PayRouteFacade.java (路由代码在 merchant 模块独立存在)
This commit is contained in:
DaxPay Dev
2026-06-16 16:25:33 +08:00
parent 0e75f4505a
commit 3e25ace24a
52 changed files with 2303 additions and 11 deletions

View File

@@ -0,0 +1,15 @@
package org.dromara.daxpay.payment.masterdata.config.convert;
import org.dromara.daxpay.payment.masterdata.config.entity.ChannelConfig;
import org.dromara.daxpay.payment.masterdata.config.result.ChannelConfigResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 通道配置
///
@Mapper
public interface ChannelConfigConvert {
ChannelConfigConvert INSTANCE = Mappers.getMapper(ChannelConfigConvert.class);
ChannelConfigResult toResult(ChannelConfig in);
}

View File

@@ -0,0 +1,33 @@
package org.dromara.daxpay.payment.masterdata.config.entity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.payment.common.entity.merchant.MchAppBaseEntity;
import org.dromara.daxpay.payment.masterdata.config.convert.ChannelConfigConvert;
import org.dromara.daxpay.payment.masterdata.config.result.ChannelConfigResult;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 通道支付配置
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_channel_config" )
public class ChannelConfig extends MchAppBaseEntity implements ToResult<ChannelConfigResult> {
/// 支付通道
/// @see ChannelEnum
private String channel;
/// 是否启用
private boolean enable;
@Override
public ChannelConfigResult toResult() {
return ChannelConfigConvert.INSTANCE.toResult(this);
}
}

View File

@@ -0,0 +1,39 @@
package org.dromara.daxpay.payment.masterdata.config.result;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 通道配置
///
@Data
@Accessors(chain = true)
@Schema(title = "通道配置")
public class ChannelConfigResult {
@Schema(description = "主键ID")
private Long id;
/// 支付通道
/// @see ChannelEnum
@Schema(description = "支付通道")
private String channel;
/// 通道名称
@Schema(description = "通道名称")
private String name;
/// 是否启用
@Schema(description = "是否启用")
private Boolean enable;
/// 商户号
@Schema(description = "商户号")
private String mchNo;
/// 应用号
@Schema(description = "应用号")
private String appId;
}

View File

@@ -0,0 +1,14 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.convert;
import org.dromara.daxpay.payment.masterdata.constants.capability.entity.PayCapability;
import org.dromara.daxpay.payment.masterdata.constants.capability.result.PayCapabilityResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 支付能力转换
@Mapper
public interface PayCapabilityConvert {
PayCapabilityConvert CONVERT = Mappers.getMapper(PayCapabilityConvert.class);
PayCapabilityResult toResult(PayCapability entity);
}

View File

@@ -0,0 +1,70 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.dao;
import org.dromara.daxpay.payment.masterdata.constants.capability.entity.PayCapability;
import org.dromara.daxpay.payment.masterdata.constants.capability.param.PayCapabilityQuery;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.platform.common.mybatisplus.query.generator.QueryGenerator;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/// # 支付能力
@Repository
@RequiredArgsConstructor
public class PayCapabilityManager extends BaseManager<PayCapabilityMapper, PayCapability> {
/// 按编码查询
public Optional<PayCapability> findByCode(String code) {
return lambdaQuery().eq(PayCapability::getCode, code).oneOpt();
}
/// 是否存在任意主数据行(预留:后续若能力参与运行时收窄可据此分支)
public boolean hasAny() {
return lambdaQuery().count() > 0;
}
/// 分页(默认 sort_no、id 升序)
public Page<PayCapability> page(PageParam pageParam, PayCapabilityQuery query) {
Page<PayCapability> mpPage = MpUtil.getMpPage(pageParam);
QueryWrapper<PayCapability> wrapper = QueryGenerator.generator(query);
if (StrUtil.isBlank(query.getSortField())) {
wrapper.orderByAsc(MpUtil.getColumnName(PayCapability::getSortNo))
.orderByAsc(MpUtil.getColumnName(PayCapability::getId));
}
return page(mpPage, wrapper);
}
/// 全部未删除,有序
public List<PayCapability> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayCapability::getSortNo)
.orderByAsc(PayCapability::getId)
.list();
}
/// code -> 实体
public Map<String, PayCapability> mapByCode() {
return listAllOrdered().stream()
.collect(Collectors.toMap(PayCapability::getCode, e -> e, (a, b) -> a));
}
/// 按编码批量查询
public List<PayCapability> listByCodes(Collection<String> codes) {
if (CollUtil.isEmpty(codes)) {
return Collections.emptyList();
}
return lambdaQuery().in(PayCapability::getCode, codes).list();
}
}

View File

@@ -0,0 +1,10 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.dao;
import org.dromara.daxpay.payment.masterdata.constants.capability.entity.PayCapability;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付能力 Mapper
@Mapper
public interface PayCapabilityMapper extends MPJBaseMapper<PayCapability> {
}

View File

@@ -0,0 +1,65 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.dao;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductCapability;
import cn.hutool.core.collection.CollUtil;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/// # 支付产品能力关联
@Repository
@RequiredArgsConstructor
public class PayProductCapabilityManager extends BaseManager<PayProductCapabilityMapper, PayProductCapability> {
/// 某能力下的全部关联
public List<PayProductCapability> listByCapability(String capabilityCode) {
return lambdaQuery()
.eq(PayProductCapability::getCapabilityCode, capabilityCode)
.orderByAsc(PayProductCapability::getSortNo)
.orderByAsc(PayProductCapability::getId)
.list();
}
/// 某产品下的全部关联
public List<PayProductCapability> listByProduct(String productCode) {
return lambdaQuery()
.eq(PayProductCapability::getProductCode, productCode)
.orderByAsc(PayProductCapability::getSortNo)
.orderByAsc(PayProductCapability::getId)
.list();
}
/// 多个产品下的全部关联
public List<PayProductCapability> listByProductCodes(Collection<String> productCodes) {
if (CollUtil.isEmpty(productCodes)) {
return Collections.emptyList();
}
return lambdaQuery()
.in(PayProductCapability::getProductCode, productCodes)
.orderByAsc(PayProductCapability::getProductCode)
.orderByAsc(PayProductCapability::getSortNo)
.orderByAsc(PayProductCapability::getId)
.list();
}
/// 全部未删除关联
public List<PayProductCapability> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayProductCapability::getProductCode)
.orderByAsc(PayProductCapability::getSortNo)
.orderByAsc(PayProductCapability::getId)
.list();
}
/// 产品是否关联该能力
public boolean exists(String productCode, String capabilityCode) {
return lambdaQuery()
.eq(PayProductCapability::getProductCode, productCode)
.eq(PayProductCapability::getCapabilityCode, capabilityCode)
.exists();
}
}

View File

@@ -0,0 +1,10 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.dao;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductCapability;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付产品能力关联 Mapper
@Mapper
public interface PayProductCapabilityMapper extends MPJBaseMapper<PayProductCapability> {
}

View File

@@ -0,0 +1,37 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.entity;
import org.dromara.daxpay.payment.masterdata.constants.capability.convert.PayCapabilityConvert;
import org.dromara.daxpay.payment.masterdata.constants.capability.result.PayCapabilityResult;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayCapabilityEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付能力主数据
///
/// `code` 对齐 `PayCapabilityEnum`(独立发版字典,与支付方式无表字段关联);展示名走 enum i18n。
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_capability")
public class PayCapability extends MpBaseEntity implements ToResult<PayCapabilityResult> {
/// 支付能力编码
/// @see PayCapabilityEnum
private String code;
/// 全局排序
private Integer sortNo;
/// 说明
private String description;
/// 转换
@Override
public PayCapabilityResult toResult() {
return PayCapabilityConvert.CONVERT.toResult(this);
}
}

View File

@@ -0,0 +1,20 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.param;
import org.dromara.daxpay.platform.common.mybatisplus.query.entity.SortParam;
import org.dromara.daxpay.platform.core.annotation.QueryParam;
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 PayCapabilityQuery extends SortParam {
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
@Schema(description = "支付能力编码")
private String code;
}

View File

@@ -0,0 +1,33 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.result;
import org.dromara.daxpay.platform.core.rest.dto.LabelValue;
import org.dromara.daxpay.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
/// # 支付能力
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付能力")
public class PayCapabilityResult extends BaseResult {
@Schema(description = "支付能力编码")
private String code;
@Schema(description = "支付能力名称i18n")
private String name;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "说明")
private String description;
@Schema(description = "已关联支付产品")
private List<LabelValue> products;
}

View File

@@ -0,0 +1,83 @@
package org.dromara.daxpay.payment.masterdata.constants.capability.service;
import org.dromara.daxpay.payment.masterdata.constants.capability.dao.PayCapabilityManager;
import org.dromara.daxpay.payment.masterdata.constants.capability.dao.PayProductCapabilityManager;
import org.dromara.daxpay.payment.masterdata.constants.capability.entity.PayCapability;
import org.dromara.daxpay.payment.masterdata.constants.capability.param.PayCapabilityQuery;
import org.dromara.daxpay.payment.masterdata.constants.capability.result.PayCapabilityResult;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayCapabilityEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import org.dromara.daxpay.platform.core.rest.dto.LabelValue;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import org.dromara.daxpay.platform.core.rest.result.PageResult;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/// # 支付能力主数据
///
/// 供管理端维护与展示支付能力字典。
@Service
@RequiredArgsConstructor
public class PayCapabilityService {
private final PayCapabilityManager payCapabilityManager;
private final PayProductCapabilityManager payProductCapabilityManager;
/// 分页查询
public PageResult<PayCapabilityResult> page(PageParam pageParam, PayCapabilityQuery query, String nameKeyword) {
PageResult<PayCapabilityResult> pageResult = MpUtil.toPageResult(payCapabilityManager.page(pageParam, query));
List<PayCapabilityResult> records = pageResult.getRecords().stream()
.map(this::fillDisplay)
.filter(row -> matchNameKeyword(row, nameKeyword))
.toList();
pageResult.setRecords(records);
return pageResult;
}
/// 按能力编码查详情,含已关联的支付产品
public PayCapabilityResult findByCode(String code) {
PayCapability row = payCapabilityManager.findByCode(code)
.orElseThrow(() -> new DataNotExistException("error.payment.capability.notExist"));
return fillDisplayWithProducts(row.toResult());
}
/// 补全支付能力 i18n 展示名
private PayCapabilityResult fillDisplay(PayCapabilityResult result) {
PayCapabilityEnum capabilityEnum = PayCapabilityEnum.findByCode(result.getCode());
if (capabilityEnum != null) {
result.setName(I18nUtil.getEnumName(capabilityEnum));
}
return result;
}
/// 补全展示名与已关联支付产品
private PayCapabilityResult fillDisplayWithProducts(PayCapabilityResult result) {
fillDisplay(result);
List<LabelValue> products = payProductCapabilityManager.listByCapability(result.getCode()).stream()
.map(rel -> {
ProductEnum productEnum = ProductEnum.findByCode(rel.getProductCode());
String label = productEnum != null
? I18nUtil.getEnumName(productEnum)
: rel.getProductCode();
return new LabelValue(label, rel.getProductCode());
})
.toList();
result.setProducts(products);
return result;
}
/// 分页后在内存中按展示名过滤
private boolean matchNameKeyword(PayCapabilityResult row, String nameKeyword) {
if (StrUtil.isBlank(nameKeyword)) {
return true;
}
String name = row.getName();
return name != null && name.contains(nameKeyword);
}
}

View File

@@ -0,0 +1,15 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.convert;
import org.dromara.daxpay.payment.masterdata.constants.channel.entity.PayChannel;
import org.dromara.daxpay.payment.masterdata.constants.channel.result.PayChannelResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 支付通道转换
///
@Mapper
public interface PayChannelConvert {
PayChannelConvert CONVERT = Mappers.getMapper(PayChannelConvert.class);
PayChannelResult toResult(PayChannel entity);
}

View File

@@ -0,0 +1,45 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.dao;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import org.dromara.daxpay.payment.masterdata.constants.channel.entity.PayChannel;
import org.dromara.daxpay.payment.masterdata.constants.channel.param.PayChannelQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.daxpay.platform.common.mybatisplus.query.generator.QueryGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/// # 支付通道
///
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayChannelManager extends BaseManager<PayChannelMapper, PayChannel> {
/// 根据通道编码查询
public Optional<PayChannel> findByCode(String code) {
return lambdaQuery()
.eq(PayChannel::getCode, code)
.oneOpt();
}
/// 分页
public Page<PayChannel> page(PageParam pageParam, PayChannelQuery query) {
Page<PayChannel> mpPage = MpUtil.getMpPage(pageParam);
QueryWrapper<PayChannel> generator = QueryGenerator.generator(query);
return page(mpPage, generator);
}
/// 查询所有(按排序号升序)
public List<PayChannel> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayChannel::getSortNo)
.list();
}
}

View File

@@ -0,0 +1,11 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.dao;
import org.dromara.daxpay.payment.masterdata.constants.channel.entity.PayChannel;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付通道
///
@Mapper
public interface PayChannelMapper extends MPJBaseMapper<PayChannel> {
}

View File

@@ -0,0 +1,39 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.entity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.payment.masterdata.constants.channel.convert.PayChannelConvert;
import org.dromara.daxpay.payment.masterdata.constants.channel.result.PayChannelResult;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付通道
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_channel")
public class PayChannel extends MpBaseEntity implements ToResult<PayChannelResult> {
/// 通道编码
/// @see ChannelEnum
private String code;
/// 排序
private Integer sortNo;
/// 通道介绍
private String description;
/// 图标
private String icon;
/// 转换
@Override
public PayChannelResult toResult() {
return PayChannelConvert.CONVERT.toResult(this);
}
}

View File

@@ -0,0 +1,21 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.param;
import org.dromara.daxpay.platform.common.mybatisplus.query.entity.SortParam;
import org.dromara.daxpay.platform.core.annotation.QueryParam;
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 PayChannelQuery extends SortParam {
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
@Schema(description = "通道编码")
private String code;
}

View File

@@ -0,0 +1,31 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.result;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
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 PayChannelResult extends MpBaseEntity {
@Schema(description = "通道编码")
private String code;
@Schema(description = "通道名称")
private String name;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "通道介绍")
private String description;
@Schema(description = "图标")
private String icon;
}

View File

@@ -0,0 +1,135 @@
package org.dromara.daxpay.payment.masterdata.constants.channel.service;
import org.dromara.daxpay.payment.masterdata.constants.channel.dao.PayChannelManager;
import org.dromara.daxpay.payment.masterdata.constants.channel.entity.PayChannel;
import org.dromara.daxpay.payment.masterdata.constants.channel.param.PayChannelQuery;
import org.dromara.daxpay.payment.masterdata.constants.channel.result.PayChannelResult;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import org.dromara.daxpay.platform.core.rest.dto.LabelValue;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import org.dromara.daxpay.platform.core.rest.result.PageResult;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// # 支付通道主数据
///
/// 通道指接入层分类(如微信通道、支付宝通道),与「支付渠道」不同。数据来自 `ChannelEnum` 与 `pay_channel` 表。
@Slf4j
@Service
@RequiredArgsConstructor
public class PayChannelService {
private final PayChannelManager payChannelManager;
/// 分页查询支付通道
public PageResult<PayChannelResult> page(PageParam pageParam, PayChannelQuery query, String nameKeyword) {
PageResult<PayChannelResult> pageResult = MpUtil.toPageResult(payChannelManager.page(pageParam, query));
pageResult.setRecords(pageResult.getRecords().stream()
.map(this::fillChannelName)
.filter(row -> matchNameKeyword(row, nameKeyword))
.toList());
return pageResult;
}
/// 库表无记录时回退到通道枚举定义
public PayChannelResult findByCode(String code) {
return payChannelManager.findByCode(code)
.map(PayChannel::toResult)
.map(this::fillChannelName)
.orElseGet(() -> resolveFromEnum(code));
}
/// 库表无记录时从通道枚举构造结果
private PayChannelResult resolveFromEnum(String code) {
ChannelEnum enumVal = ChannelEnum.findByCode(code);
if (enumVal == null) {
throw new DataNotExistException("error.payment.channel.notExist");
}
return fillChannelName(toChannelResult(enumVal, Map.of()));
}
/// 支付通道下拉选项
public List<LabelValue> dropdown() {
return payChannelManager.listAllOrdered().stream()
.map(PayChannel::toResult)
.map(this::fillChannelName)
.map(e -> new LabelValue(e.getName(), e.getCode()))
.toList();
}
/// 返回全部支付通道
public List<PayChannelResult> listAll() {
return buildChannels();
}
/// 按通道编码取展示名称
public String findNameByCode(String code) {
return resolveChannelName(code);
}
/// 枚举与库表合并为通道列表
private List<PayChannelResult> buildChannels() {
Map<String, PayChannel> dbMap = payChannelManager.listAllOrdered().stream()
.collect(Collectors.toMap(PayChannel::getCode, c -> c, (a, b) -> a));
return Arrays.stream(ChannelEnum.values())
.map(enumVal -> toChannelResult(enumVal, dbMap))
.sorted(Comparator.comparing(PayChannelResult::getSortNo,
Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
}
/// 枚举与库表合并为单条通道结果
private PayChannelResult toChannelResult(ChannelEnum enumVal, Map<String, PayChannel> dbMap) {
PayChannelResult result = new PayChannelResult()
.setCode(enumVal.getCode())
.setName(resolveChannelName(enumVal.getCode()));
PayChannel dbRow = dbMap.get(enumVal.getCode());
if (dbRow != null) {
result.setId(dbRow.getId());
result.setSortNo(dbRow.getSortNo());
result.setDescription(dbRow.getDescription());
result.setIcon(dbRow.getIcon());
} else {
result.setId(null);
result.setSortNo(0);
}
return result;
}
/// 补全通道 i18n 展示名
private PayChannelResult fillChannelName(PayChannelResult result) {
if (result == null || result.getCode() == null) {
return result;
}
return result.setName(resolveChannelName(result.getCode()));
}
/// 按编码取通道展示名
private String resolveChannelName(String code) {
ChannelEnum channelEnum = ChannelEnum.findByCode(code);
return channelEnum != null ? I18nUtil.getEnumName(channelEnum) : code;
}
/// 分页后在内存中按展示名过滤
private boolean matchNameKeyword(PayChannelResult row, String nameKeyword) {
if (StrUtil.isBlank(nameKeyword)) {
return true;
}
String name = row.getName();
return name != null && name.contains(nameKeyword);
}
}

View File

@@ -0,0 +1,36 @@
package org.dromara.daxpay.payment.masterdata.constants.method.dao;
import org.dromara.daxpay.payment.masterdata.constants.method.entity.PayMethod;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/// # 支付方式
@Repository
@RequiredArgsConstructor
public class PayMethodManager extends BaseManager<PayMethodMapper, PayMethod> {
/// 按编码查询
public Optional<PayMethod> findByCode(String code) {
return lambdaQuery().eq(PayMethod::getCode, code).oneOpt();
}
/// 全部未删除,按 sortNo、id 排序
public List<PayMethod> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayMethod::getSortNo)
.orderByAsc(PayMethod::getId)
.list();
}
/// code -> 实体
public Map<String, PayMethod> mapByCode() {
return listAllOrdered().stream()
.collect(Collectors.toMap(PayMethod::getCode, e -> e, (a, b) -> a));
}
}

View File

@@ -0,0 +1,10 @@
package org.dromara.daxpay.payment.masterdata.constants.method.dao;
import org.dromara.daxpay.payment.masterdata.constants.method.entity.PayMethod;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付方式
@Mapper
public interface PayMethodMapper extends MPJBaseMapper<PayMethod> {
}

View File

@@ -0,0 +1,28 @@
package org.dromara.daxpay.payment.masterdata.constants.method.entity;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayMethodEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付方式主数据
///
/// code 对齐 `PayMethodEnum`;展示名走 enum i18n。
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_method")
public class PayMethod extends MpBaseEntity {
/// 支付方式编码
/// @see PayMethodEnum
private String code;
/// 全局排序
private Integer sortNo;
/// 说明
private String description;
}

View File

@@ -0,0 +1,16 @@
package org.dromara.daxpay.payment.masterdata.constants.product.convert;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductConfig;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductConfigResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 支付产品配置转换
///
@Mapper
public interface PayProductConfigConvert {
PayProductConfigConvert CONVERT = Mappers.getMapper(PayProductConfigConvert.class);
PayProductConfigResult toResult(PayProductConfig entity);
}

View File

@@ -0,0 +1,15 @@
package org.dromara.daxpay.payment.masterdata.constants.product.convert;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProduct;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 支付产品转换
///
@Mapper
public interface PayProductConvert {
PayProductConvert CONVERT = Mappers.getMapper(PayProductConvert.class);
PayProductResult toResult(PayProduct entity);
}

View File

@@ -0,0 +1,24 @@
package org.dromara.daxpay.payment.masterdata.constants.product.dao;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/// # 支付产品配置
///
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayProductConfigManager extends BaseManager<PayProductConfigMapper, PayProductConfig> {
/// 根据产品编码查询
public Optional<PayProductConfig> findByProduct(String product) {
return lambdaQuery()
.eq(PayProductConfig::getProduct, product)
.oneOpt();
}
}

View File

@@ -0,0 +1,11 @@
package org.dromara.daxpay.payment.masterdata.constants.product.dao;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductConfig;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付产品配置
///
@Mapper
public interface PayProductConfigMapper extends MPJBaseMapper<PayProductConfig> {
}

View File

@@ -0,0 +1,48 @@
package org.dromara.daxpay.payment.masterdata.constants.product.dao;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProduct;
import org.dromara.daxpay.payment.masterdata.constants.product.param.PayProductQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.daxpay.platform.common.mybatisplus.query.generator.QueryGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/// # 支付产品
///
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayProductManager extends BaseManager<PayProductMapper, PayProduct> {
/// 根据产品编码查询
public Optional<PayProduct> findByCode(String code) {
return lambdaQuery()
.eq(PayProduct::getCode, code)
.oneOpt();
}
/// 按通道编码查询
public List<PayProduct> listByChannel(String channel) {
return lambdaQuery()
.eq(PayProduct::getChannel, channel)
.orderByAsc(PayProduct::getSortNo)
.orderByAsc(PayProduct::getId)
.list();
}
/// 分页
public Page<PayProduct> page(PageParam pageParam, PayProductQuery query) {
Page<PayProduct> mpPage = MpUtil.getMpPage(pageParam);
QueryWrapper<PayProduct> generator = QueryGenerator.generator(query);
generator.lambda().orderByAsc(PayProduct::getSortNo).orderByAsc(PayProduct::getId);
return page(mpPage, generator);
}
}

View File

@@ -0,0 +1,11 @@
package org.dromara.daxpay.payment.masterdata.constants.product.dao;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProduct;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付产品
///
@Mapper
public interface PayProductMapper extends MPJBaseMapper<PayProduct> {
}

View File

@@ -0,0 +1,53 @@
package org.dromara.daxpay.payment.masterdata.constants.product.entity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.common.mybatisplus.handler.type.StringListTypeHandler;
import org.dromara.daxpay.payment.masterdata.constants.product.convert.PayProductConvert;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductResult;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
/// # 支付产品
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName(value = "pay_product", autoResultMap = true)
public class PayProduct extends MpBaseEntity implements ToResult<PayProductResult> {
/// 产品编码
/// @see ProductEnum
private String code;
/// 关联通道编码
private String channel;
/// 产品介绍
private String description;
/// 图标
private String icon;
/// 支持的结算周期列表
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> settlePeriods;
/// 排序
private Integer sortNo;
/// 是否支持沙箱环境
private Boolean sandbox;
/// 转换
@Override
public PayProductResult toResult() {
return PayProductConvert.CONVERT.toResult(this);
}
}

View File

@@ -0,0 +1,26 @@
package org.dromara.daxpay.payment.masterdata.constants.product.entity;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付产品与支付能力关联
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_product_capability")
public class PayProductCapability extends MpBaseEntity {
/// 支付产品编码
private String productCode;
/// 支付能力编码
private String capabilityCode;
/// 排序
private Integer sortNo;
}

View File

@@ -0,0 +1,40 @@
package org.dromara.daxpay.payment.masterdata.constants.product.entity;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.payment.masterdata.constants.product.convert.PayProductConfigConvert;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductConfigResult;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付产品配置
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName(value = "pay_product_config", autoResultMap = true)
public class PayProductConfig extends MpBaseEntity implements ToResult<PayProductConfigResult> {
/// 产品编码
private String product;
/// 通道编码
private String channel;
/// 生效环境: prod/sandbox
private String activeEnv;
/// 是否已配置参数
private boolean configured;
/// 备注
private String remark;
/// 转换
@Override
public PayProductConfigResult toResult() {
return PayProductConfigConvert.CONVERT.toResult(this);
}
}

View File

@@ -0,0 +1,31 @@
package org.dromara.daxpay.payment.masterdata.constants.product.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 支付产品配置保存参数
///
@Data
@Accessors(chain = true)
@Schema(title = "支付产品配置保存参数")
public class PayProductConfigParam {
@NotBlank(message = "{validation.field.product.notBlank}")
@Schema(description = "产品编码")
private String product;
@NotBlank(message = "{validation.field.channel.notBlank}")
@Schema(description = "通道编码")
private String channel;
@Schema(description = "生效环境: prod/sandbox")
private String activeEnv;
@Schema(description = "是否已配置参数")
private boolean configured;
@Schema(description = "备注")
private String remark;
}

View File

@@ -0,0 +1,29 @@
package org.dromara.daxpay.payment.masterdata.constants.product.param;
import org.dromara.daxpay.platform.common.mybatisplus.query.entity.SortParam;
import org.dromara.daxpay.platform.core.annotation.QueryParam;
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 PayProductQuery extends SortParam {
@QueryParam(type = QueryParam.CompareTypeEnum.LIKE)
@Schema(description = "产品编码")
private String code;
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
@Schema(description = "关联通道编码")
private String channel;
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
@Schema(description = "是否启用")
private Boolean enable;
}

View File

@@ -0,0 +1,21 @@
package org.dromara.daxpay.payment.masterdata.constants.product.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 支付产品关联的支付能力项
@Data
@Accessors(chain = true)
@Schema(title = "支付产品支付能力项")
public class PayProductCapabilityResult {
@Schema(description = "支付能力编码")
private String code;
@Schema(description = "支付能力名称i18n")
private String name;
@Schema(description = "关联表排序")
private Integer sortNo;
}

View File

@@ -0,0 +1,40 @@
package org.dromara.daxpay.payment.masterdata.constants.product.result;
import org.dromara.daxpay.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 PayProductConfigResult extends BaseResult {
@Schema(description = "产品编码")
private String product;
@Schema(description = "产品名称")
private String name;
@Schema(description = "通道编码")
private String channel;
@Schema(description = "通道名称")
private String channelName;
@Schema(description = "是否支持沙箱环境")
private boolean sandboxSupport;
@Schema(description = "生效环境: prod/sandbox")
private String activeEnv;
@Schema(description = "是否已配置参数")
private boolean configured;
/// 是否为服务商模式true=服务商, false=直连)
private boolean isv;
}

View File

@@ -0,0 +1,66 @@
package org.dromara.daxpay.payment.masterdata.constants.product.result;
import org.dromara.daxpay.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
/// # 支付产品
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付产品")
public class PayProductResult extends BaseResult {
@Schema(description = "产品编码")
private String code;
@Schema(description = "产品名称")
private String name;
@Schema(description = "关联通道编码")
private String channel;
@Schema(description = "通道名称")
private String channelName;
@Schema(description = "产品介绍")
private String description;
@Schema(description = "图标")
private String icon;
@Schema(description = "支持的结算周期列表")
private List<String> settlePeriods;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "备注")
private String remark;
@Schema(description = "是否支持服务商")
private boolean isv;
@Schema(description = "是否支持分账")
private boolean allocatable;
@Schema(description = "是否支持终端报备")
private boolean terminal;
@Schema(description = "是否支持沙箱环境")
private boolean sandbox;
@Schema(description = "API调用模式")
private String apiCallMode;
@Schema(description = "支付标识类型")
private String payIdType;
@Schema(description = "已挂载的支付能力")
private List<PayProductCapabilityResult> capabilities;
}

View File

@@ -0,0 +1,159 @@
package org.dromara.daxpay.payment.masterdata.constants.product.service;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.masterdata.constants.capability.dao.PayCapabilityManager;
import org.dromara.daxpay.payment.masterdata.constants.capability.dao.PayProductCapabilityManager;
import org.dromara.daxpay.payment.masterdata.constants.capability.entity.PayCapability;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductCapability;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductCapabilityResult;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductResult;
import org.dromara.daxpay.payment.old.pay.strategy.AbsProductStrategy;
import org.dromara.daxpay.payment.old.pay.support.ProductStrategySupport;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayCapabilityEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayMethodEnum;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/// # 支付产品与支付能力关联
///
/// 查询产品挂载了哪些支付能力,并判断某支付产品是否支持某种支付方式。
/// 方式→能力以各产品策略 {@link AbsProductStrategy#methodCapabilityMapping()} 为准,再与 `pay_product_capability` 求交。
/// 内调约定:`productCode` / `methodCode` 均为已解析的合法编码,不在本类重复判空。
@Service
@RequiredArgsConstructor
public class PayProductCapabilityService {
private final PayProductCapabilityManager payProductCapabilityManager;
private final PayCapabilityManager payCapabilityManager;
/// 预加载「支付产品 → 能力编码」,供批量判断使用
public Map<String, Set<String>> loadCapabilityCodesByProduct() {
Map<String, PayCapability> capabilityMap = payCapabilityManager.mapByCode();
Map<String, Set<String>> capabilityCodesByProduct = new HashMap<>();
for (PayProductCapability rel : payProductCapabilityManager.listAllOrdered()) {
PayCapability cap = capabilityMap.get(rel.getCapabilityCode());
if (cap == null) {
continue;
}
capabilityCodesByProduct
.computeIfAbsent(rel.getProductCode(), key -> new HashSet<>())
.add(rel.getCapabilityCode());
}
return capabilityCodesByProduct;
}
/// 用预加载结果判断产品是否支持该支付方式(策略声明能力 ∩ 已挂载能力,不再查库)
public boolean supportsMethod(Map<String, Set<String>> capabilityCodesByProduct, String productCode, String methodCode) {
Set<String> mountedCodes = capabilityCodesByProduct.getOrDefault(productCode, Set.of());
return strategyCapabilitiesForMethod(productCode, methodCode).stream()
.map(PayCapabilityEnum::getCode)
.anyMatch(mountedCodes::contains);
}
/// 判断产品是否支持该支付方式(策略声明能力 ∩ 已挂载能力,按产品查库)
public boolean productSupportsMethod(String productCode, String methodCode) {
Set<String> mountedCodes = payProductCapabilityManager.listByProduct(productCode).stream()
.map(PayProductCapability::getCapabilityCode)
.filter(code -> payCapabilityManager.findByCode(code).isPresent())
.collect(Collectors.toSet());
return strategyCapabilitiesForMethod(productCode, methodCode).stream()
.map(PayCapabilityEnum::getCode)
.anyMatch(mountedCodes::contains);
}
/// 填充单个产品的支付能力列表
public void fillCapabilities(PayProductResult result) {
if (StrUtil.isBlank(result.getCode())) {
return;
}
fillCapabilitiesBatch(List.of(result));
}
/// 批量填充产品的支付能力列表
public void fillCapabilitiesBatch(List<PayProductResult> products) {
if (CollUtil.isEmpty(products)) {
return;
}
Set<String> productCodes = products.stream()
.map(PayProductResult::getCode)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
if (productCodes.isEmpty()) {
return;
}
List<PayProductCapability> allRels = payProductCapabilityManager.listByProductCodes(productCodes);
Map<String, List<PayProductCapability>> relsByProduct = allRels.stream()
.collect(Collectors.groupingBy(PayProductCapability::getProductCode));
Set<String> capabilityCodes = allRels.stream()
.map(PayProductCapability::getCapabilityCode)
.collect(Collectors.toSet());
Map<String, PayCapability> capabilityMap = payCapabilityManager.listByCodes(capabilityCodes).stream()
.collect(Collectors.toMap(PayCapability::getCode, e -> e, (a, b) -> a));
for (PayProductResult product : products) {
if (StrUtil.isBlank(product.getCode())) {
product.setCapabilities(List.of());
continue;
}
List<PayProductCapability> rels = relsByProduct.getOrDefault(product.getCode(), List.of());
List<PayProductCapabilityResult> items = new ArrayList<>();
for (PayProductCapability rel : rels) {
PayCapability cap = capabilityMap.get(rel.getCapabilityCode());
if (cap == null) {
continue;
}
items.add(toItem(rel, cap));
}
product.setCapabilities(items);
}
}
/// 关联行转为能力展示项
private PayProductCapabilityResult toItem(PayProductCapability rel, PayCapability cap) {
PayProductCapabilityResult item = new PayProductCapabilityResult()
.setCode(cap.getCode())
.setSortNo(rel.getSortNo());
PayCapabilityEnum capabilityEnum = PayCapabilityEnum.findByCode(cap.getCode());
if (capabilityEnum != null) {
item.setName(I18nUtil.getEnumName(capabilityEnum));
} else {
item.setName(cap.getCode());
}
return item;
}
/// 产品策略在该支付方式下声明的支付能力列表
private List<PayCapabilityEnum> strategyCapabilitiesForMethod(String productCode, String methodCode) {
if (!PaymentStrategyFactory.existsByProduct(productCode, AbsProductStrategy.class)) {
return List.of();
}
PayMethodEnum method = findMethodByCode(methodCode);
if (method == null) {
return List.of();
}
AbsProductStrategy strategy = PaymentStrategyFactory.createByProduct(productCode, AbsProductStrategy.class);
return ProductStrategySupport.capabilitiesForMethod(strategy, method);
}
/// 按编码解析支付方式枚举(未知编码返回 null不抛异常
private static PayMethodEnum findMethodByCode(String code) {
return Arrays.stream(PayMethodEnum.values())
.filter(method -> Objects.equals(method.getCode(), code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,135 @@
package org.dromara.daxpay.payment.masterdata.constants.product.service;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.masterdata.constants.product.dao.PayProductConfigManager;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProduct;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProductConfig;
import org.dromara.daxpay.payment.masterdata.constants.product.param.PayProductConfigParam;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductConfigResult;
import org.dromara.daxpay.payment.old.pay.strategy.AbsProductStrategy;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import org.dromara.daxpay.platform.core.enums.pay.config.PayEnvEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
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.Map;
import java.util.stream.Collectors;
/// # 支付产品配置服务
///
@Slf4j
@Service
@RequiredArgsConstructor
public class PayProductConfigService {
private final PayProductConfigManager payProductConfigManager;
private final PayProductService payProductService;
/// 查询全部产品配置列表(卡片页使用)
/// 融合 PayProduct + pay_product_config 表 + 策略信息
public List<PayProductConfigResult> listAll() {
Map<String, PayProductConfig> configMap = payProductConfigManager.lambdaQuery().list().stream()
.collect(Collectors.toMap(PayProductConfig::getProduct, c -> c, (a, b) -> a));
return payProductService.listSortedProducts()
.stream()
.map(payProduct -> toConfigResult(payProduct, configMap))
.toList();
}
/// 切换产品的生效环境
@Transactional(rollbackFor = Exception.class)
public void switchEnv(String product, boolean sandbox) {
PayProductConfig config = payProductConfigManager.findByProduct(product)
.orElseGet(() -> createDefaultConfig(product));
if (sandbox) {
config.setActiveEnv(PayEnvEnum.SANDBOX.getCode());
} else {
config.setActiveEnv(PayEnvEnum.PROD.getCode());
}
payProductConfigManager.saveOrUpdate(config);
}
/// 保存或更新配置
@Transactional(rollbackFor = Exception.class)
public void saveOrUpdate(PayProductConfigParam param) {
PayProductConfig config = payProductConfigManager.findByProduct(param.getProduct())
.orElseGet(PayProductConfig::new);
config.setProduct(param.getProduct());
config.setChannel(param.getChannel());
config.setActiveEnv(param.getActiveEnv() != null ? param.getActiveEnv() : PayEnvEnum.PROD.getCode());
config.setConfigured(param.isConfigured());
config.setRemark(param.getRemark());
payProductConfigManager.saveOrUpdate(config);
}
/// 更新配置状态
@Transactional(rollbackFor = Exception.class)
public void markConfigured(String product, boolean configured) {
PayProductConfig config = payProductConfigManager.findByProduct(product)
.orElseGet(() -> createDefaultConfig(product));
config.setConfigured(configured);
payProductConfigManager.saveOrUpdate(config);
}
/// PayProduct + 库表 + 策略合并为配置结果
private PayProductConfigResult toConfigResult(PayProduct payProduct, Map<String, PayProductConfig> configMap) {
ProductEnum productEnum = ProductEnum.findByCode(payProduct.getCode());
PayProductConfigResult result = new PayProductConfigResult()
.setProduct(payProduct.getCode())
.setName(productEnum != null ? I18nUtil.getEnumName(productEnum) : payProduct.getCode())
.setChannel(payProduct.getChannel())
.setChannelName(I18nUtil.getEnumName(ChannelEnum.findByCode(payProduct.getChannel())));
AbsProductStrategy strategy = resolveStrategy(payProduct.getCode());
if (strategy != null) {
result.setSandboxSupport(strategy.isSandbox());
result.setIsv(strategy.isIsv());
}
PayProductConfig config = configMap.get(payProduct.getCode());
if (config != null) {
result.setId(config.getId());
result.setActiveEnv(config.getActiveEnv());
result.setConfigured(config.isConfigured());
} else {
result.setActiveEnv(PayEnvEnum.PROD.getCode());
result.setConfigured(false);
}
return result;
}
/// 创建默认配置记录
private PayProductConfig createDefaultConfig(String product) {
ProductEnum productEnum = ProductEnum.findByCode(product);
if (productEnum == null) {
throw new DataNotExistException("error.payment.product.notExist");
}
PayProductConfig config = new PayProductConfig();
config.setProduct(product);
config.setChannel(productEnum.getChannel());
config.setActiveEnv(PayEnvEnum.PROD.getCode());
config.setConfigured(false);
payProductConfigManager.save(config);
return config;
}
private AbsProductStrategy resolveStrategy(String productCode) {
if (!PaymentStrategyFactory.existsByProduct(productCode, AbsProductStrategy.class)) {
return null;
}
return PaymentStrategyFactory.createByProduct(productCode, AbsProductStrategy.class);
}
}

View File

@@ -0,0 +1,167 @@
package org.dromara.daxpay.payment.masterdata.constants.product.service;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.masterdata.constants.product.dao.PayProductManager;
import org.dromara.daxpay.payment.masterdata.constants.product.entity.PayProduct;
import org.dromara.daxpay.payment.masterdata.constants.product.param.PayProductQuery;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductResult;
import org.dromara.daxpay.payment.old.pay.strategy.AbsProductStrategy;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.common.mybatisplus.util.MpUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import org.dromara.daxpay.platform.core.rest.dto.LabelValue;
import org.dromara.daxpay.platform.core.rest.param.PageParam;
import org.dromara.daxpay.platform.core.rest.result.PageResult;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// # 支付产品主数据
///
/// 名称、支付通道、是否进件/终端/沙箱等。数据来自产品枚举、产品策略与 `pay_product` 表。
@Slf4j
@Service
@RequiredArgsConstructor
public class PayProductService {
private final PayProductManager payProductManager;
private final PayProductCapabilityService payProductCapabilityService;
/// 分页查询支付产品
public PageResult<PayProductResult> page(PageParam pageParam, PayProductQuery query, String nameKeyword) {
PageResult<PayProductResult> pageResult = MpUtil.toPageResult(payProductManager.page(pageParam, query));
pageResult.setRecords(pageResult.getRecords().stream()
.map(this::fillProductFeatures)
.filter(row -> matchNameKeyword(row, nameKeyword))
.toList());
return pageResult;
}
/// 根据产品编码查询
public PayProductResult findByCode(String code) {
PayProduct payProduct = payProductManager.findByCode(code)
.orElseThrow(() -> new DataNotExistException("error.payment.product.notExist"));
return enrich(payProduct.toResult());
}
/// 查询全部支付产品,按 sortNo 升序排列
public List<PayProduct> listSortedProducts() {
return payProductManager.lambdaQuery()
.orderByAsc(PayProduct::getSortNo)
.list();
}
/// 按支付通道编码查询所属支付产品
public List<PayProductResult> listByChannel(String channelCode) {
return payProductManager.listByChannel(channelCode).stream()
.map(PayProduct::toResult)
.map(this::fillProductFeatures)
.toList();
}
/// 返回全部支付产品,按 sortNo、id 排序
/// 策略存在时补全特性字段,不存在时仅返回枚举与数据库基础信息
public List<PayProductResult> listAll() {
Map<String, PayProduct> dbMap = payProductManager.lambdaQuery().list().stream()
.collect(Collectors.toMap(PayProduct::getCode, p -> p, (a, b) -> a));
return Arrays.stream(ProductEnum.values())
.map(e -> toProductResult(e, dbMap))
.sorted(Comparator.comparing(PayProductResult::getSortNo,
Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(PayProductResult::getId, Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
}
/// 支付产品下拉选项
public List<LabelValue> dropdown() {
return listAll().stream()
.map(e -> new LabelValue(e.getName(), e.getCode()))
.toList();
}
/// 枚举与库表合并为产品结果
private PayProductResult toProductResult(ProductEnum product, Map<String, PayProduct> dbMap) {
PayProductResult result = new PayProductResult()
.setCode(product.getCode())
.setChannel(product.getChannel())
.setName(I18nUtil.getEnumName(product))
.setChannelName(I18nUtil.getEnumName(ChannelEnum.findByCode(product.getChannel())));
PayProduct dbRow = dbMap.get(product.getCode());
if (dbRow != null) {
result.setId(dbRow.getId());
result.setSortNo(dbRow.getSortNo());
result.setDescription(dbRow.getDescription());
result.setIcon(dbRow.getIcon());
result.setSettlePeriods(dbRow.getSettlePeriods());
if (dbRow.getSandbox() != null) {
result.setSandbox(dbRow.getSandbox());
}
} else {
result.setSortNo(0);
}
AbsProductStrategy strategy = resolveStrategy(product.getCode());
if (strategy != null) {
applyStrategyFields(strategy, result);
}
return result;
}
/// 补全支付能力与展示字段
private PayProductResult enrich(PayProductResult result) {
payProductCapabilityService.fillCapabilities(result);
return fillProductFeatures(result);
}
/// 补全名称、通道名与策略特性
private PayProductResult fillProductFeatures(PayProductResult result) {
String code = result.getCode();
ProductEnum productEnum = ProductEnum.findByCode(code);
if (productEnum != null) {
result.setName(I18nUtil.getEnumName(productEnum));
}
result.setChannelName(I18nUtil.getEnumName(ChannelEnum.findByCode(result.getChannel())));
AbsProductStrategy strategy = resolveStrategy(code);
if (strategy != null) {
applyStrategyFields(strategy, result);
}
return result;
}
/// 按产品编码获取策略,不存在则返回 null
private AbsProductStrategy resolveStrategy(String productCode) {
if (!PaymentStrategyFactory.existsByProduct(productCode, AbsProductStrategy.class)) {
return null;
}
return PaymentStrategyFactory.createByProduct(productCode, AbsProductStrategy.class);
}
/// 写入策略层产品特性字段
private void applyStrategyFields(AbsProductStrategy strategy, PayProductResult target) {
target.setIsv(strategy.isIsv())
.setAllocatable(strategy.isAllocatable())
.setTerminal(strategy.isTerminal())
.setSandbox(strategy.isSandbox())
.setApiCallMode(strategy.getApiCallMode().getCode())
.setPayIdType(strategy.getPayIdType().getCode());
}
/// 按产品名称关键字过滤(分页后在内存中匹配 i18n 展示名)
private boolean matchNameKeyword(PayProductResult row, String nameKeyword) {
if (StrUtil.isBlank(nameKeyword)) {
return true;
}
String name = row.getName();
return name != null && name.contains(nameKeyword);
}
}

View File

@@ -0,0 +1,36 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.dao;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProvider;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/// # 支付渠道
@Repository
@RequiredArgsConstructor
public class PayProviderManager extends BaseManager<PayProviderMapper, PayProvider> {
/// 按编码查询
public Optional<PayProvider> findByCode(String code) {
return lambdaQuery().eq(PayProvider::getCode, code).oneOpt();
}
/// 全部未删除记录,按 sortNo、id 排序
public List<PayProvider> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayProvider::getSortNo)
.orderByAsc(PayProvider::getId)
.list();
}
/// code -> 实体
public Map<String, PayProvider> mapByCode() {
return listAllOrdered().stream()
.collect(Collectors.toMap(PayProvider::getCode, e -> e, (a, b) -> a));
}
}

View File

@@ -0,0 +1,10 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.dao;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProvider;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 支付渠道
@Mapper
public interface PayProviderMapper extends MPJBaseMapper<PayProvider> {
}

View File

@@ -0,0 +1,57 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.dao;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProviderMethod;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// # 渠道支付方式目录项
@Repository
@RequiredArgsConstructor
public class PayProviderMethodManager extends BaseManager<PayProviderMethodMapper, PayProviderMethod> {
/// 全部未删除记录
public List<PayProviderMethod> listAllOrdered() {
return lambdaQuery()
.orderByAsc(PayProviderMethod::getProvider)
.orderByAsc(PayProviderMethod::getSortNo)
.orderByAsc(PayProviderMethod::getId)
.list();
}
/// (provider|method) -> 实体
public Map<String, PayProviderMethod> mapByPairKey() {
return listAllOrdered().stream()
.collect(Collectors.toMap(this::pairKey, e -> e, (a, b) -> a));
}
private String pairKey(PayProviderMethod row) {
return pairKey(row.getProvider(), row.getMethod());
}
public static String pairKey(String provider, String method) {
return provider + "|" + method;
}
/// 某支付方式下的全部关联(未删除)
public List<PayProviderMethod> listByMethod(String methodCode) {
return lambdaQuery()
.eq(PayProviderMethod::getMethod, methodCode)
.orderByAsc(PayProviderMethod::getSortNo)
.orderByAsc(PayProviderMethod::getId)
.list();
}
/// 某支付渠道下的全部关联(未删除)
public List<PayProviderMethod> listByProvider(String providerCode) {
return lambdaQuery()
.eq(PayProviderMethod::getProvider, providerCode)
.orderByAsc(PayProviderMethod::getSortNo)
.orderByAsc(PayProviderMethod::getId)
.list();
}
}

View File

@@ -0,0 +1,10 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.dao;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProviderMethod;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 渠道支付方式目录项
@Mapper
public interface PayProviderMethodMapper extends MPJBaseMapper<PayProviderMethod> {
}

View File

@@ -0,0 +1,28 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.entity;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayProviderEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 支付渠道(运营元数据)
///
/// code 对齐 `PayProviderEnum`;展示名走 enum i18n。
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_provider")
public class PayProvider extends MpBaseEntity {
/// 支付渠道编码
/// @see PayProviderEnum
private String code;
/// 图标(可选)
private String icon;
/// 排序
private Integer sortNo;
}

View File

@@ -0,0 +1,31 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.entity;
import org.dromara.daxpay.platform.common.mybatisplus.base.MpBaseEntity;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayMethodEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 渠道下支付方式目录项
///
/// pay_provider + method 关联 `pay_method.code` 主数据。
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("pay_provider_method")
public class PayProviderMethod extends MpBaseEntity {
/// 支付渠道编码
private String provider;
/// 支付方式编码
/// @see PayMethodEnum
private String method;
/// 渠道内排序
private Integer sortNo;
/// 目录项说明
private String description;
}

View File

@@ -0,0 +1,29 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/// # 按支付渠道分组(管理端)
@Data
@Accessors(chain = true)
@Schema(title = "支付渠道分组")
public class PayProviderGroupResult {
@Schema(description = "支付渠道编码")
private String provider;
@Schema(description = "支付渠道展示名")
private String providerLabel;
@Schema(description = "图标")
private String icon;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "渠道下支付方式列表")
private List<PayProviderMethodResult> methods;
}

View File

@@ -0,0 +1,32 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/// # 渠道支持的支付方式
@Data
@Accessors(chain = true)
@Schema(title = "渠道支付方式")
public class PayProviderMethodResult {
@Schema(description = "支付渠道编码")
private String provider;
@Schema(description = "支付方式编码")
private String method;
@Schema(description = "支付方式展示名")
private String methodLabel;
@Schema(description = "渠道内排序")
private Integer sortNo;
@Schema(description = "目录项说明")
private String description;
@Schema(description = "支持的支付产品")
private List<PayProviderProductResult> supportedProducts;
}

View File

@@ -0,0 +1,24 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 渠道支付方式支持的支付产品
@Data
@Accessors(chain = true)
@Schema(title = "渠道支付方式支持的支付产品")
public class PayProviderProductResult {
@Schema(description = "产品名称")
private String label;
@Schema(description = "产品编码")
private String value;
@Schema(description = "所属通道编码")
private String channel;
@Schema(description = "通道名称")
private String channelName;
}

View File

@@ -0,0 +1,183 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.service;
import org.dromara.daxpay.payment.masterdata.constants.method.dao.PayMethodManager;
import org.dromara.daxpay.payment.masterdata.constants.provider.dao.PayProviderManager;
import org.dromara.daxpay.payment.masterdata.constants.provider.dao.PayProviderMethodManager;
import org.dromara.daxpay.payment.masterdata.constants.method.entity.PayMethod;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProvider;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProviderMethod;
import org.dromara.daxpay.payment.masterdata.constants.provider.result.PayProviderMethodResult;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayMethodEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayProviderEnum;
import org.dromara.daxpay.platform.core.model.PayProviderMethodEntry;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/// # 支付渠道与支付方式的配置与查询
///
/// 读取 `pay_provider`、`pay_method`、`pay_provider_method`,供下单校验和管理端「支付渠道」页面使用。
/// 内调约定:`providerCode` / `methodCode` 均为已解析的合法编码,不在本类重复判空。
@Service
@RequiredArgsConstructor
public class PayProviderMethodService {
private final PayMethodManager payMethodManager;
private final PayProviderManager payProviderManager;
private final PayProviderMethodManager payProviderMethodManager;
/// 预加载全部支付渠道、支付方式及二者关联
public DirectoryLoadContext loadDirectoryContext() {
Map<String, PayProvider> providerMap = payProviderManager.mapByCode();
Map<String, PayMethod> methodMap = payMethodManager.mapByCode();
Map<String, PayProviderMethod> relationMap = payProviderMethodManager.mapByPairKey();
Map<String, List<PayProviderMethod>> realsByProvider = payProviderMethodManager.listAllOrdered().stream()
.collect(Collectors.groupingBy(PayProviderMethod::getProvider));
Map<String, List<MergedRelationRow>> relationsByProvider = new HashMap<>();
for (Map.Entry<String, List<PayProviderMethod>> entry : realsByProvider.entrySet()) {
relationsByProvider.put(entry.getKey(), mergeRelations(entry.getValue(), methodMap));
}
return new DirectoryLoadContext(providerMap, methodMap, relationMap, relationsByProvider);
}
/// 从预加载数据中取目录中的「渠道 + 支付方式」
public List<PayProviderMethodEntry> listDirectoryEntriesFromContext(DirectoryLoadContext context) {
List<PayProviderMethodEntry> entries = new ArrayList<>();
for (PayProviderEnum providerEnum : PayProviderEnum.values()) {
for (MergedRelationRow row : context.relationsByProvider()
.getOrDefault(providerEnum.getCode(), List.of())) {
entries.add(new PayProviderMethodEntry(providerEnum, row.methodEnum()));
}
}
return entries;
}
/// 返回目录中全部「渠道 + 支付方式」
public List<PayProviderMethodEntry> listDirectoryEntries() {
DirectoryLoadContext context = loadDirectoryContext();
return listDirectoryEntriesFromContext(context);
}
/// 判断该支付渠道下是否存在该支付方式(目录有效组合)
public boolean contains(String providerCode, String methodCode) {
PayProviderEnum provider = PayProviderEnum.findByCode(providerCode);
if (provider == null) {
return false;
}
PayMethodEnum methodEnum = resolveMethodEnum(methodCode);
if (methodEnum == null) {
return false;
}
if (payMethodManager.findByCode(methodCode).isEmpty()) {
return false;
}
return listMergedRelationsForProvider(providerCode).stream()
.anyMatch(row -> Objects.equals(row.methodEnum().getCode(), methodCode));
}
/// 某支付渠道目录中的支付方式列表
public List<PayMethodEnum> listMethodsForProvider(PayProviderEnum provider) {
return listMergedRelationsForProvider(provider.getCode()).stream()
.map(MergedRelationRow::methodEnum)
.toList();
}
/// 同 {@link #listMethodsForProvider(PayProviderEnum)}
public List<PayMethodEnum> listMethodsForBrand(PayProviderEnum brand) {
return listMethodsForProvider(brand);
}
/// 平铺返回目录中的渠道与支付方式(不按渠道分组,不含支持的产品)
public List<PayProviderMethodResult> listDirectoryFlat() {
DirectoryLoadContext context = loadDirectoryContext();
return listDirectoryEntriesFromContext(context).stream()
.map(entry -> toFlatMethod(entry, context))
.toList();
}
/// 管理端:某支付渠道下的全部支付方式
public List<MergedRelationRow> listMergedRelationsForProvider(String providerCode) {
Map<String, PayMethod> methodMap = payMethodManager.mapByCode();
List<MergedRelationRow> rows = new ArrayList<>();
int ordinal = 0;
for (PayProviderMethod rel : payProviderMethodManager.listByProvider(providerCode)) {
PayMethod methodRow = methodMap.get(rel.getMethod());
if (methodRow == null) {
continue;
}
PayMethodEnum methodEnum = resolveMethodEnum(rel.getMethod());
if (methodEnum == null) {
continue;
}
int sortNo = rel.getSortNo() != null ? rel.getSortNo() : ordinal;
rows.add(new MergedRelationRow(methodEnum, sortNo, rel.getDescription()));
ordinal++;
}
rows.sort(Comparator.comparingInt(MergedRelationRow::sortNo));
return rows;
}
/// 转为平铺展示用的渠道+方式结果
private PayProviderMethodResult toFlatMethod(PayProviderMethodEntry entry, DirectoryLoadContext context) {
String pairKey = PayProviderMethodManager.pairKey(entry.getProviderCode(), entry.getMethodCode());
PayProviderMethod relation = context.relationMap().get(pairKey);
return new PayProviderMethodResult()
.setProvider(entry.getProviderCode())
.setMethod(entry.getMethodCode())
.setMethodLabel(I18nUtil.getEnumName(entry.getMethod()))
.setDescription(relation != null ? relation.getDescription() : null);
}
/// 合并关联表与支付方式主数据
private List<MergedRelationRow> mergeRelations(List<PayProviderMethod> relations, Map<String, PayMethod> methodMap) {
List<MergedRelationRow> rows = new ArrayList<>();
int ordinal = 0;
for (PayProviderMethod rel : relations) {
PayMethod methodRow = methodMap.get(rel.getMethod());
if (methodRow == null) {
continue;
}
PayMethodEnum methodEnum = resolveMethodEnum(rel.getMethod());
if (methodEnum == null) {
continue;
}
int sortNo = rel.getSortNo() != null ? rel.getSortNo() : ordinal;
rows.add(new MergedRelationRow(methodEnum, sortNo, rel.getDescription()));
ordinal++;
}
rows.sort(Comparator.comparingInt(MergedRelationRow::sortNo));
return rows;
}
/// 按编码解析支付方式枚举
private PayMethodEnum resolveMethodEnum(String methodCode) {
return Arrays.stream(PayMethodEnum.values())
.filter(e -> Objects.equals(e.getCode(), methodCode))
.findFirst()
.orElse(null);
}
/// 预加载的渠道、方式、关联数据
public record DirectoryLoadContext(
Map<String, PayProvider> providerMap,
Map<String, PayMethod> methodMap,
Map<String, PayProviderMethod> relationMap,
Map<String, List<MergedRelationRow>> relationsByProvider) {
}
/// 一条「渠道 + 支付方式」合并后的展示行
public record MergedRelationRow(
PayMethodEnum methodEnum,
int sortNo,
String description) {
}
}

View File

@@ -0,0 +1,57 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.service;
import org.dromara.daxpay.payment.masterdata.constants.provider.dao.PayProviderMethodManager;
import org.dromara.daxpay.payment.masterdata.constants.product.result.PayProductResult;
import org.dromara.daxpay.payment.masterdata.constants.provider.result.PayProviderProductResult;
import org.dromara.daxpay.payment.masterdata.constants.product.service.PayProductCapabilityService;
import org.dromara.daxpay.payment.masterdata.constants.product.service.PayProductService;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.ChannelEnum;
import org.dromara.daxpay.platform.core.model.PayProviderMethodEntry;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/// # 支付渠道与支付方式对应的产品
///
/// 计算每种「支付渠道 + 支付方式」可用的支付产品列表。
@Service
@RequiredArgsConstructor
public class PayProviderProductService {
private final PayProductService payProductService;
private final PayProductCapabilityService payProductCapabilityService;
/// 构建「渠道+方式 → 产品列表」索引,供管理端批量展示
public Map<String, List<PayProviderProductResult>> buildSupportedProductsIndex(
List<PayProviderMethodEntry> directoryEntries) {
Map<String, List<PayProviderProductResult>> index = new HashMap<>();
if (directoryEntries.isEmpty()) {
return index;
}
Map<String, Set<String>> capabilityCodesByProduct = payProductCapabilityService.loadCapabilityCodesByProduct();
for (PayProductResult product : payProductService.listAll()) {
PayProviderProductResult productItem = new PayProviderProductResult()
.setLabel(product.getName())
.setValue(product.getCode())
.setChannel(product.getChannel())
.setChannelName(product.getChannelName() != null
? product.getChannelName()
: I18nUtil.getEnumName(ChannelEnum.findByCode(product.getChannel())));
for (PayProviderMethodEntry entry : directoryEntries) {
if (!payProductCapabilityService.supportsMethod(
capabilityCodesByProduct, product.getCode(), entry.getMethodCode())) {
continue;
}
String pairKey = PayProviderMethodManager.pairKey(entry.getProviderCode(), entry.getMethodCode());
index.computeIfAbsent(pairKey, key -> new ArrayList<>()).add(productItem);
}
}
return index;
}
}

View File

@@ -0,0 +1,114 @@
package org.dromara.daxpay.payment.masterdata.constants.provider.service;
import org.dromara.daxpay.payment.masterdata.constants.provider.dao.PayProviderMethodManager;
import org.dromara.daxpay.payment.masterdata.constants.provider.entity.PayProvider;
import org.dromara.daxpay.payment.masterdata.constants.provider.result.PayProviderGroupResult;
import org.dromara.daxpay.payment.masterdata.constants.provider.result.PayProviderMethodResult;
import org.dromara.daxpay.payment.masterdata.constants.provider.result.PayProviderProductResult;
import org.dromara.daxpay.platform.common.i18n.util.I18nUtil;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayProviderEnum;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import org.dromara.daxpay.platform.core.model.PayProviderMethodEntry;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/// # 管理端支付渠道配置
///
/// 按支付渠道分组展示支付方式,并附带每种方式支持的产品列表。
@Service
@RequiredArgsConstructor
public class PayProviderService {
private final PayProviderMethodService payProviderMethodService;
private final PayProviderProductService payProviderProductService;
/// 按支付渠道分组,返回各渠道下的支付方式
public List<PayProviderGroupResult> listByProvider() {
return buildGroups(loadAdminDirectoryContext());
}
/// 按支付渠道编码与支付方式编码查一条配置详情(含支持的产品列表)
public PayProviderMethodResult get(String providerCode, String methodCode) {
if (PayProviderEnum.findByCode(providerCode) == null) {
throw new DataNotExistException("error.payment.capability.invalidProvider");
}
AdminDirectoryContext adminContext = loadAdminDirectoryContext();
PayProviderMethodService.MergedRelationRow row = adminContext.directoryContext().relationsByProvider()
.getOrDefault(providerCode, List.of()).stream()
.filter(r -> Objects.equals(r.methodEnum().getCode(), methodCode))
.findFirst()
.orElseThrow(() -> new DataNotExistException("error.payment.capability.methodNotInDirectory"));
return toAdminMethod(providerCode, row, adminContext);
}
/// 预加载管理端展示所需数据
private AdminDirectoryContext loadAdminDirectoryContext() {
PayProviderMethodService.DirectoryLoadContext directoryContext =
payProviderMethodService.loadDirectoryContext();
List<PayProviderMethodEntry> directoryEntries =
payProviderMethodService.listDirectoryEntriesFromContext(directoryContext);
Map<String, List<PayProviderProductResult>> supportedProductsIndex =
payProviderProductService.buildSupportedProductsIndex(directoryEntries);
return new AdminDirectoryContext(directoryContext, supportedProductsIndex);
}
/// 按支付渠道分组组装列表
private List<PayProviderGroupResult> buildGroups(AdminDirectoryContext adminContext) {
PayProviderMethodService.DirectoryLoadContext directoryContext = adminContext.directoryContext();
Map<String, PayProvider> providerMap = directoryContext.providerMap();
List<PayProviderGroupResult> groups = new ArrayList<>();
int brandOrdinal = 0;
for (PayProviderEnum brandEnum : PayProviderEnum.values()) {
PayProvider dbBrand = providerMap.get(brandEnum.getCode());
PayProviderGroupResult group = new PayProviderGroupResult()
.setProvider(brandEnum.getCode())
.setProviderLabel(I18nUtil.getEnumName(brandEnum))
.setIcon(dbBrand != null ? dbBrand.getIcon() : null)
.setSortNo(resolveSortNo(dbBrand, brandOrdinal));
List<PayProviderMethodResult> methods = directoryContext.relationsByProvider()
.getOrDefault(brandEnum.getCode(), List.of()).stream()
.map(row -> toAdminMethod(brandEnum.getCode(), row, adminContext))
.toList();
group.setMethods(methods);
groups.add(group);
brandOrdinal++;
}
groups.sort(Comparator.comparingInt(g -> g.getSortNo() != null ? g.getSortNo() : 0));
return groups;
}
/// 组装管理端单条渠道+方式详情
private PayProviderMethodResult toAdminMethod(
String providerCode,
PayProviderMethodService.MergedRelationRow row,
AdminDirectoryContext adminContext) {
String methodCode = row.methodEnum().getCode();
String pairKey = PayProviderMethodManager.pairKey(providerCode, methodCode);
List<PayProviderProductResult> supportedProducts =
adminContext.supportedProductsIndex().getOrDefault(pairKey, List.of());
return new PayProviderMethodResult()
.setProvider(providerCode)
.setMethod(methodCode)
.setMethodLabel(I18nUtil.getEnumName(row.methodEnum()))
.setSortNo(row.sortNo())
.setDescription(row.description())
.setSupportedProducts(supportedProducts);
}
/// 取渠道排序号,无则使用枚举顺序
private static int resolveSortNo(PayProvider dbBrand, int fallback) {
return dbBrand != null && dbBrand.getSortNo() != null ? dbBrand.getSortNo() : fallback;
}
/// 管理端预加载:渠道方式配置与支持的产品索引
private record AdminDirectoryContext(
PayProviderMethodService.DirectoryLoadContext directoryContext,
Map<String, List<PayProviderProductResult>> supportedProductsIndex) {
}
}

View File

@@ -1,11 +0,0 @@
package org.dromara.daxpay.payment.old.pay.service.route;
import org.dromara.daxpay.payment.unipay.param.trade.pay.PayParam;
/// # 支付通道路由门面
///
public interface PayRouteFacade {
/// 当 product 为空时执行路由并回填支付参数
void resolve(PayParam payParam);
}