refactor(payment): 移除商户产品配置模块,功能整合到产品主数据管理

This commit is contained in:
bootx
2026-06-13 21:41:33 +08:00
parent bbab670743
commit 0eefd23bf1
19 changed files with 102 additions and 510 deletions

View File

@@ -12,7 +12,7 @@
Target Server Version : 160009 (160009)
File Encoding : 65001
Date: 13/06/2026 15:50:03
Date: 13/06/2026 17:02:31
*/
@@ -138,12 +138,39 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040112, 40401, 'payment:merchant:c
INSERT INTO "public"."iam_perm_menu" VALUES (4040113, 40401, 'payment:merchant:channelMerchant:wechatApp', 'admin', 'WechatMchAppManage', '微信通道商户应用', 'WeChat Channel Merchant App', 'menu.payment.merchant.channelMerchant.wechatApp', NULL, 't', 'f', '/payment/channel/wechat/manage/mch/app/WechatMchAppManage', '/payment/merchant/channel-merchant/wechat-app-manage', NULL, 11, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-07 23:57:13.620205+00', '2026-06-07 23:57:13.620205+00');
INSERT INTO "public"."iam_perm_menu" VALUES (4040114, 40401, 'payment:merchant:channelMerchant:alipayApp', 'admin', 'AlipayMchAppManage', '支付宝通道商户应用', 'Alipay Channel Merchant App', 'menu.payment.merchant.channelMerchant.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/mch/app/AlipayMchAppManage', '/payment/merchant/channel-merchant/alipay-app-manage', NULL, 12, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-11 22:28:11.274785+00', '2026-06-11 22:28:11.274785+00');
INSERT INTO "public"."iam_perm_menu" VALUES (404, NULL, 'payment:merchant', 'admin', 'PaymentMerchant', '商户管理', 'Merchant Management', 'menu.payment.merchant', 'lucide:store', 'f', 'f', NULL, '/payment/merchant', NULL, 4, 't', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-05 16:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (402, 4, 'payment:isv:basic', 'admin', 'IsvBasic', '基础管理', 'Basic Management', 'menu.payment.isv.basic', 'lucide:layers', 'f', 'f', NULL, '/payment/isv/basic', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (403, 4, 'payment:isv:pay-config', 'admin', 'IsvPayConfig', '支付配置', 'Payment Config', 'menu.payment.isv.payConfig', 'lucide:settings-2', 'f', 'f', NULL, '/payment/isv/pay-config', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (405, 4, 'payment:isv:business', 'admin', 'IsvBusiness', '业务配置', 'Business Config', 'menu.payment.isv.business', 'lucide:briefcase', 'f', 'f', NULL, '/payment/isv/business', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (40201, 402, 'payment:isv:user', 'admin', 'IsvUserList', '用户管理', 'User Management', 'menu.payment.isv.basic.user', 'lucide:users', 'f', 'f', '/_core/fallback/coming-soon', '/payment/isv/user', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (40203, 402, 'payment:isv:site', 'admin', 'IsvSiteConfig', '站点配置', 'Site Config', 'menu.payment.isv.basic.site', 'lucide:globe', 'f', 'f', '/_core/fallback/coming-soon', '/payment/isv/site', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (40301, 403, 'payment:isv:product', 'admin', 'IsvProductConfig', '支付产品管理', 'Payment Product', 'menu.payment.isv.payConfig.product', 'lucide:package', 'f', 'f', '/_core/fallback/coming-soon', '/payment/isv/product', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (30403, 304, 'system:credential:config', 'admin', 'PlatformCredentialConfig', '对接配置', 'Credential Config', 'menu.system.config.credential', 'lucide:key-round', 'f', 'f', '/system/config/credential/PlatformCredentialConfig', '/system/config/credential', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00');
-- 支付配置目录
INSERT INTO "public"."iam_perm_menu" VALUES (405, 4, 'payment:config', 'admin', 'PaymentConfig', '支付配置', 'Payment Config', 'menu.payment.config', 'lucide:settings-2', 'f', 'f', NULL, '/payment/config', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00')
ON CONFLICT (id) DO UPDATE SET
menu_code = EXCLUDED.menu_code,
name = EXCLUDED.name,
title_cn = EXCLUDED.title_cn,
title_en = EXCLUDED.title_en,
i18n_key = EXCLUDED.i18n_key,
icon = EXCLUDED.icon,
path = EXCLUDED.path,
sort_no = EXCLUDED.sort_no,
keep_alive = EXCLUDED.keep_alive,
menu_type = EXCLUDED.menu_type,
last_modified_time = EXCLUDED.last_modified_time;
-- 支付配置:支付产品管理(卡片式)
INSERT INTO "public"."iam_perm_menu" VALUES (40105, 405, 'payment:config:productConfig', 'admin', 'ProductConfig', '支付产品管理', 'Payment Product Management', 'menu.payment.config.productConfig', 'lucide:layout-grid', 'f', 'f', '/payment/config/product/ProductConfig', '/payment/config/product', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00')
ON CONFLICT (id) DO UPDATE SET
pid = EXCLUDED.pid,
menu_code = EXCLUDED.menu_code,
name = EXCLUDED.name,
title_cn = EXCLUDED.title_cn,
title_en = EXCLUDED.title_en,
i18n_key = EXCLUDED.i18n_key,
icon = EXCLUDED.icon,
component = EXCLUDED.component,
path = EXCLUDED.path,
sort_no = EXCLUDED.sort_no,
keep_alive = EXCLUDED.keep_alive,
menu_type = EXCLUDED.menu_type,
last_modified_time = EXCLUDED.last_modified_time;
-- ----------------------------
-- Primary Key structure for table iam_perm_menu

View File

@@ -53,4 +53,12 @@ public class PayProductMasterDataController {
return Res.ok(payProductMasterDataService.dropdown());
}
/// 全量查询支付产品列表(卡片式管理页使用)
@PermCode(code = "view", nameCn = "产品查看", nameEn = "Product View")
@Operation(summary = "全量查询支付产品")
@GetMapping("/list-all")
public Result<List<PayProductResult>> listAll() {
return Res.ok(payProductMasterDataService.listAll());
}
}

View File

@@ -1,53 +0,0 @@
package org.dromara.daxpay.payment.admin.controller.merchant.config;
import org.dromara.daxpay.platform.core.annotation.PermCode;
import org.dromara.daxpay.platform.core.rest.Res;
import org.dromara.daxpay.platform.core.rest.result.Result;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigBatchParam;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigEnableParam;
import org.dromara.daxpay.payment.merchant.result.config.MchProductConfigResult;
import org.dromara.daxpay.payment.merchant.service.config.MchProductConfigService;
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.*;
import java.util.List;
/// # 商户产品配置管理控制器
///
@PermCode(menuCode = "payment:merchant")
@Validated
@Tag(name = "商户产品配置管理")
@RestController
@RequestMapping("/admin/merchant/product/config")
@RequiredArgsConstructor
public class MchProductConfigAdminController {
private final MchProductConfigService service;
@PermCode(code = "view", nameCn = "商户查看", nameEn = "Merchant View")
@Operation(summary = "根据商户号查询产品配置列表")
@GetMapping("/all-by-mch-no")
public Result<List<MchProductConfigResult>> findAllByMchNo(@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo) {
return Res.ok(service.findAllByMchNo(mchNo));
}
@PermCode(code = "product_config_update", nameCn = "产品配置更新", nameEn = "Product Config Update")
@Operation(summary = "更新启用状态,如果配置不存在则自动创建")
@PostMapping("/update-enable")
public Result<Void> updateEnable(@RequestBody @Validated MchProductConfigEnableParam param) {
service.updateEnable(param);
return Res.ok();
}
@PermCode(code = "product_config_update", nameCn = "产品配置更新", nameEn = "Product Config Update")
@Operation(summary = "批量保存商户产品配置")
@PostMapping("/save-batch")
public Result<Void> saveBatch(@RequestBody @Validated MchProductConfigBatchParam param) {
service.saveBatch(param);
return Res.ok();
}
}

View File

@@ -62,12 +62,12 @@ public class PayProductMasterDataService {
}
/// 返回全部支付产品,按 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())
.filter(e -> PaymentStrategyFactory.existsByProduct(e.getCode(), AbsProductStrategy.class))
.map(e -> toProductResult(e, dbMap))
.sorted(Comparator.comparing(PayProductResult::getSortNo,
Comparator.nullsLast(Comparator.naturalOrder()))
@@ -91,7 +91,6 @@ public class PayProductMasterDataService {
/// 枚举与库表合并为产品结果
private PayProductResult toProductResult(ProductEnum product, Map<String, PayProduct> dbMap) {
var strategy = PaymentStrategyFactory.createByProduct(product.getCode(), AbsProductStrategy.class);
PayProductResult result = new PayProductResult()
.setCode(product.getCode())
.setChannel(product.getChannel())
@@ -111,7 +110,10 @@ public class PayProductMasterDataService {
} else {
result.setSortNo(0);
}
applyStrategyFields(strategy, result);
AbsProductStrategy strategy = resolveStrategy(product.getCode());
if (strategy != null) {
applyStrategyFields(strategy, result);
}
return result;
}

View File

@@ -1,51 +0,0 @@
package org.dromara.daxpay.payment.merchant.controller.config;
import org.dromara.daxpay.platform.core.rest.Res;
import org.dromara.daxpay.platform.core.rest.result.Result;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigBatchParam;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigEnableParam;
import org.dromara.daxpay.payment.merchant.result.config.MchProductConfigResult;
import org.dromara.daxpay.payment.merchant.service.config.MchProductConfigService;
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.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;
/// # 商户产品配置
///
@Validated
@Tag(name = "商户产品配置")
@RestController
@RequestMapping("/merchant/product/config")
@RequiredArgsConstructor
public class MchProductConfigController {
private final MchProductConfigService service;
@Operation(summary = "根据商户号查询产品配置列表")
@GetMapping("/all-by-mch-no")
public Result<List<MchProductConfigResult>> findAllByMchNo(@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo){
return Res.ok(service.findAllByMchNo(mchNo));
}
@Operation(summary = "更新启用状态,如果配置不存在则自动创建")
@PostMapping("/update-enable")
public Result<Void> updateEnable(@RequestBody @Validated MchProductConfigEnableParam param){
service.updateEnable(param);
return Res.ok();
}
@Operation(summary = "批量保存商户产品配置")
@PostMapping("/save-batch")
public Result<Void> saveBatch(@RequestBody @Validated MchProductConfigBatchParam param){
service.saveBatch(param);
return Res.ok();
}
}

View File

@@ -1,16 +0,0 @@
package org.dromara.daxpay.payment.merchant.convert.config;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.result.config.MchProductConfigResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/// # 商户产品配置转换
///
@Mapper
public interface MchProductConfigConvert {
MchProductConfigConvert CONVERT = Mappers.getMapper(MchProductConfigConvert.class);
MchProductConfigResult toResult(MchProductConfig entity);
}

View File

@@ -1,29 +0,0 @@
package org.dromara.daxpay.payment.merchant.dao.config;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.platform.common.mybatisplus.impl.BaseManager;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/// # 商户产品配置
///
@Repository
public class MchProductConfigManager extends BaseManager<MchProductConfigMapper, MchProductConfig> {
/// 根据商户号查询产品配置列表
public List<MchProductConfig> findByMchNo(String mchNo) {
return lambdaQuery()
.eq(MchProductConfig::getMchNo, mchNo)
.list();
}
/// 根据商户号和产品编码查询产品配置
public Optional<MchProductConfig> findByMchNoAndProduct(String mchNo, String product) {
return lambdaQuery()
.eq(MchProductConfig::getMchNo, mchNo)
.eq(MchProductConfig::getProduct, product)
.oneOpt();
}
}

View File

@@ -1,11 +0,0 @@
package org.dromara.daxpay.payment.merchant.dao.config;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 商户产品配置
///
@Mapper
public interface MchProductConfigMapper extends MPJBaseMapper<MchProductConfig> {
}

View File

@@ -1,36 +0,0 @@
package org.dromara.daxpay.payment.merchant.entity.config;
import org.dromara.daxpay.platform.common.mybatisplus.function.ToResult;
import org.dromara.daxpay.payment.common.entity.merchant.MchBaseEntity;
import org.dromara.daxpay.payment.merchant.convert.config.MchProductConfigConvert;
import org.dromara.daxpay.payment.merchant.result.config.MchProductConfigResult;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 商户产品配置
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("mch_product_config")
public class MchProductConfig extends MchBaseEntity implements ToResult<MchProductConfigResult> {
/// 产品编码
/// @see ProductEnum
private String product;
/// 通道编码
private String channel;
/// 是否启用
private boolean enable;
@Override
public MchProductConfigResult toResult() {
return MchProductConfigConvert.CONVERT.toResult(this);
}
}

View File

@@ -1,23 +0,0 @@
package org.dromara.daxpay.payment.merchant.param.config;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/// # 商户产品配置批量保存参数
///
@Data
@Accessors(chain = true)
@Schema(title = "商户产品配置批量保存参数")
public class MchProductConfigBatchParam {
@Schema(description = "商户号")
@NotBlank(message = "{validation.field.mchNo.notBlank}")
private String mchNo;
@Schema(description = "产品配置列表")
private List<MchProductConfigItem> items;
}

View File

@@ -1,29 +0,0 @@
package org.dromara.daxpay.payment.merchant.param.config;
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 MchProductConfigEnableParam {
@Schema(description = "商户号")
@NotBlank(message = "{validation.field.mchNo.notBlank}")
private String mchNo;
@Schema(description = "产品编码")
@NotBlank(message = "{validation.field.product.notBlank}")
private String product;
@Schema(description = "通道编码")
@NotBlank(message = "{validation.field.channel.notBlank}")
private String channel;
@Schema(description = "是否启用")
private Boolean enable;
}

View File

@@ -1,22 +0,0 @@
package org.dromara.daxpay.payment.merchant.param.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 商户产品配置项
///
@Data
@Accessors(chain = true)
@Schema(title = "商户产品配置项")
public class MchProductConfigItem {
@Schema(description = "产品编码")
private String product;
@Schema(description = "通道编码")
private String channel;
@Schema(description = "是否启用")
private Boolean enable;
}

View File

@@ -1,34 +0,0 @@
package org.dromara.daxpay.payment.merchant.result.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 商户产品配置
///
@Data
@Accessors(chain = true)
@Schema(title = "商户产品配置")
public class MchProductConfigResult {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "产品编码")
private String product;
@Schema(description = "产品名称")
private String name;
@Schema(description = "通道编码")
private String channel;
@Schema(description = "通道名称")
private String channelName;
@Schema(description = "商户号")
private String mchNo;
@Schema(description = "是否启用")
private Boolean enable;
}

View File

@@ -1,107 +0,0 @@
package org.dromara.daxpay.payment.merchant.service.config;
import org.dromara.daxpay.payment.merchant.dao.config.MchProductConfigManager;
import org.dromara.daxpay.payment.merchant.dao.info.MerchantInfoManager;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.entity.info.MerchantInfo;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigBatchParam;
import org.dromara.daxpay.payment.merchant.param.config.MchProductConfigEnableParam;
import org.dromara.daxpay.payment.merchant.result.config.MchProductConfigResult;
import org.dromara.daxpay.payment.pay.service.masterdata.product.PayProductMasterDataService;
import org.dromara.daxpay.platform.common.mybatisplus.function.CollectorsFunction;
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.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
/// # 商户产品配置
///
@Slf4j
@Service
@RequiredArgsConstructor
public class MchProductConfigService {
private final MchProductConfigManager productConfigManager;
private final PayProductMasterDataService PayProductMasterDataService;
private final MerchantInfoManager merchantInfoManager;
/// 产品配置列表, 根据商户进行查询, 默认返回所有产品配置, 未配置的产品默认启用状态为false
public List<MchProductConfigResult> findAllByMchNo(String mchNo){
Map<String, MchProductConfig> productConfigMap = productConfigManager.findByMchNo(mchNo)
.stream()
.collect(Collectors.toMap(MchProductConfig::getProduct, Function.identity(), CollectorsFunction::retainFirst));
var productList = PayProductMasterDataService.listAllByApply();
MerchantInfo merchantInfo = merchantInfoManager.findByMchNo(mchNo)
.orElseThrow(() -> new DataNotExistException("error.payment.merchant.merchantNotExist"));
return productList.stream().map(o -> {
var productConfig = productConfigMap.get(o.getCode());
if (Objects.isNull(productConfig)) {
return new MchProductConfigResult()
.setProduct(o.getCode())
.setName(o.getName())
.setChannel(o.getChannel())
.setChannelName(o.getChannelName())
.setMchNo(merchantInfo.getMchNo())
.setEnable(false);
}
return productConfig.toResult()
.setName(o.getName())
.setChannel(o.getChannel())
.setChannelName(o.getChannelName())
.setMchNo(merchantInfo.getMchNo());
}).collect(Collectors.toList());
}
/// 更新启用状态,如果配置不存在则自动创建
@Transactional(rollbackFor = Exception.class)
public void updateEnable(MchProductConfigEnableParam param) {
Optional<MchProductConfig> optional = productConfigManager
.findByMchNoAndProduct(param.getMchNo(), param.getProduct());
if (optional.isPresent()) {
MchProductConfig config = optional.get();
config.setEnable(param.getEnable());
productConfigManager.updateById(config);
} else {
MchProductConfig config = new MchProductConfig();
config.setMchNo(param.getMchNo());
config.setProduct(param.getProduct());
config.setChannel(param.getChannel());
config.setEnable(param.getEnable());
productConfigManager.save(config);
}
}
/// 批量保存商户产品配置
/// 已有的配置更新启用状态,没有的则新增
@Transactional(rollbackFor = Exception.class)
public void saveBatch(MchProductConfigBatchParam param) {
String mchNo = param.getMchNo();
for (var item : param.getItems()) {
Optional<MchProductConfig> existingConfig = productConfigManager.findByMchNoAndProduct(mchNo, item.getProduct());
if (existingConfig.isPresent()) {
MchProductConfig config = existingConfig.get();
config.setEnable(item.getEnable());
productConfigManager.updateById(config);
}
else {
MchProductConfig config = new MchProductConfig();
config.setMchNo(mchNo);
config.setProduct(item.getProduct());
config.setChannel(item.getChannel());
config.setEnable(item.getEnable());
productConfigManager.save(config);
}
}
}
}

View File

@@ -2,10 +2,8 @@ package org.dromara.daxpay.payment.merchant.service.route.basic;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.merchant.dao.appinfo.MchAppInfoManager;
import org.dromara.daxpay.payment.merchant.dao.config.MchProductConfigManager;
import org.dromara.daxpay.payment.merchant.dao.route.basic.PayRouteBasicConfigManager;
import org.dromara.daxpay.payment.merchant.dao.route.strategy.PayRouteStrategyManager;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.entity.route.basic.PayRouteBasicConfig;
import org.dromara.daxpay.payment.merchant.entity.route.strategy.PayRouteStrategy;
import org.dromara.daxpay.payment.merchant.param.route.basic.PayRouteBasicConfigBatchParam;
@@ -14,6 +12,7 @@ import org.dromara.daxpay.payment.merchant.result.route.basic.PayRouteBasicConfi
import org.dromara.daxpay.payment.merchant.service.route.support.PayRouteConfigProviders;
import org.dromara.daxpay.payment.pay.strategy.AbsProductStrategy;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayProviderEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
import org.dromara.daxpay.platform.core.exception.BizInfoException;
import org.dromara.daxpay.platform.core.exception.DataNotExistException;
import org.dromara.daxpay.platform.core.code.CommonErrorCode;
@@ -22,14 +21,15 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// # 通道路由基础模式配置
///
/// 按支付渠道配置默认支付产品,保存前校验商户已开通产品能力。
///
/// 按支付渠道配置默认支付产品,保存前校验产品策略能力。
/// 开源版:所有产品默认可用,不再检查商户启用状态。
@Service
@RequiredArgsConstructor
public class PayRouteBasicConfigService {
@@ -37,25 +37,21 @@ public class PayRouteBasicConfigService {
private final PayRouteStrategyManager strategyManager;
private final PayRouteBasicConfigManager basicConfigManager;
private final MchAppInfoManager mchAppInfoManager;
private final MchProductConfigManager mchProductConfigManager;
/// 查询基础模式面板数据(已保存 product + 各渠道可选 products
public List<PayRouteBasicConfigResult> listBasicByAppId(String appId) {
PayRouteStrategy strategy = requireStrategy(appId);
String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId);
List<MchProductConfig> mchProducts = enabledMchProducts(mchNo);
Map<String, String> productMap = basicConfigManager.findByStrategyId(strategy.getId()).stream()
.filter(config -> StrUtil.isNotBlank(config.getProvider()))
.collect(Collectors.toMap(PayRouteBasicConfig::getProvider, PayRouteBasicConfig::getProduct, (a, b) -> a));
return PayRouteConfigProviders.enumsInWhitelistOrder().stream()
.map(provider -> toPanelResult(provider, productMap.get(provider.getCode()), mchProducts))
.map(provider -> toPanelResult(provider, productMap.get(provider.getCode())))
.toList();
}
/// 批量保存基础模式配置(先删后插)
@Transactional(rollbackFor = Exception.class)
public void saveBasicBatch(PayRouteBasicConfigBatchParam param) {
String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(param.getAppId());
PayRouteStrategy strategy = requireStrategy(param.getAppId());
basicConfigManager.deleteByStrategyId(strategy.getId());
for (PayRouteBasicConfigItem item : param.getItems()) {
@@ -63,7 +59,7 @@ public class PayRouteBasicConfigService {
continue;
}
PayProviderEnum provider = validateBasicPayProviderCode(item.getProvider());
validateBasicProduct(mchNo, item.getProduct(), provider);
validateBasicProduct(item.getProduct(), provider);
PayRouteBasicConfig config = new PayRouteBasicConfig();
config.setStrategyId(strategy.getId());
config.setProvider(item.getProvider());
@@ -72,11 +68,14 @@ public class PayRouteBasicConfigService {
}
}
/// 校验产品属于该商户且支持指定支付渠道
public void validateBasicProduct(String mchNo, String product, PayProviderEnum provider) {
List<MchProductConfig> mchProducts = enabledMchProducts(mchNo);
List<String> products = productsForProvider(mchProducts, provider);
if (!products.contains(product)) {
/// 校验产品存在且支持指定支付渠道(开源版:不再检查商户启用状态)
public void validateBasicProduct(String product, PayProviderEnum provider) {
ProductEnum productEnum = ProductEnum.findByCode(product);
if (productEnum == null) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.productInvalid", product);
}
if (!PaymentStrategyFactory.productSupportsProvider(product, provider)) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.basicProductNotAvailable", provider.getCode());
}
@@ -96,36 +95,26 @@ public class PayRouteBasicConfigService {
}
/// 组装单渠道基础模式面板行:已保存 product + 该渠道下可选 products
private PayRouteBasicConfigResult toPanelResult(
PayProviderEnum provider,
String savedProduct,
List<MchProductConfig> mchProducts) {
List<String> products = productsForProvider(mchProducts, provider);
private PayRouteBasicConfigResult toPanelResult(PayProviderEnum provider, String savedProduct) {
List<String> products = productsForProvider(provider);
return new PayRouteBasicConfigResult()
.setProvider(provider.getCode())
.setProduct(savedProduct)
.setProducts(products);
}
/// 从商户已开通产品中筛出支持指定支付渠道的产品编码
private List<String> productsForProvider(List<MchProductConfig> mchProducts, PayProviderEnum provider) {
return mchProducts.stream()
.map(MchProductConfig::getProduct)
/// 从产品枚举中筛出支持指定支付渠道的产品编码(开源版:所有产品默认可用)
private List<String> productsForProvider(PayProviderEnum provider) {
return Arrays.stream(ProductEnum.values())
.map(ProductEnum::getCode)
.filter(product -> PaymentStrategyFactory.productSupportsProvider(product, provider))
.distinct()
.toList();
}
/// 查询商户已启用的支付产品配置
private List<MchProductConfig> enabledMchProducts(String mchNo) {
return mchProductConfigManager.findByMchNo(mchNo).stream()
.filter(MchProductConfig::isEnable)
.toList();
}
/// 按应用号加载路由策略,不存在则抛业务异常
private PayRouteStrategy requireStrategy(String appId) {
return strategyManager.findByAppId(appId)
.orElseThrow(() -> new DataNotExistException("pay.route.error.routeStrategyNotExist"));
}
}
}

View File

@@ -1,8 +1,6 @@
package org.dromara.daxpay.payment.merchant.service.route.basic;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.merchant.dao.config.MchProductConfigManager;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.entity.route.basic.PayRouteBasicConfig;
import org.dromara.daxpay.payment.merchant.service.route.support.PayRouteCapabilityService;
import org.dromara.daxpay.payment.merchant.service.route.model.RouteHit;
@@ -22,12 +20,12 @@ import java.util.Objects;
/// # 基础模式通道路由匹配器
///
/// 按已配置的支付渠道与支付产品,结合产品策略解析通道与支付方式
/// 按已配置的支付渠道与支付产品,结合产品策略解析通道与支付方式
/// 开源版:所有产品默认可用,不再检查商户启用状态。
@Component
@RequiredArgsConstructor
public class PayRouteBasicMatcher {
private final MchProductConfigManager productConfigManager;
private final PayRouteProductResolver productResolver;
private final PayRouteCapabilityService payRouteCapabilityService;
@@ -45,7 +43,7 @@ public class PayRouteBasicMatcher {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.basicProductNotConfigured", PayRouteI18nHelper.provider(payParam.getProvider()));
}
assertProductConfigured(payParam.getMchNo(), product, provider);
assertProductConfigured(product, provider);
if (!PaymentStrategyFactory.existsByProduct(product, AbsProductStrategy.class)) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.productStrategyMissing");
@@ -75,18 +73,11 @@ public class PayRouteBasicMatcher {
.orElse(null);
}
/// 路由配置的产品仍须在商户已开通产品范围内,且产品策略支持该支付渠道
private void assertProductConfigured(String mchNo, String product, PayProviderEnum provider) {
boolean mchEnabled = productConfigManager.findByMchNo(mchNo).stream()
.filter(MchProductConfig::isEnable)
.anyMatch(config -> Objects.equals(config.getProduct(), product));
if (!mchEnabled) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.basicProductNotAvailable", PayRouteI18nHelper.provider(provider.getCode()));
}
/// 校验产品策略支持该支付渠道(开源版:不再检查商户启用状态)
private void assertProductConfigured(String product, PayProviderEnum provider) {
if (!PaymentStrategyFactory.productSupportsProvider(product, provider)) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.basicProductNotAvailable", PayRouteI18nHelper.provider(provider.getCode()));
}
}
}
}

View File

@@ -1,7 +1,5 @@
package org.dromara.daxpay.payment.merchant.service.route.runtime;
import org.dromara.daxpay.payment.merchant.dao.config.MchProductConfigManager;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.service.route.support.PayRouteStrategyCapabilitySupport;
import org.dromara.daxpay.platform.core.enums.pay.channel.PayMethodEnum;
import org.dromara.daxpay.platform.core.enums.pay.channel.ProductEnum;
@@ -11,23 +9,23 @@ import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Objects;
/// # 通道路由产品解析器
///
/// 开源版:所有产品默认可用,直接从产品枚举中按通道+支付方式解析产品编码。
@Service
@RequiredArgsConstructor
public class PayRouteProductResolver {
private final MchProductConfigManager productConfigManager;
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
/// 根据商户已开通产品及支付方式解析产品编码
/// 根据通道及支付方式解析产品编码(开源版:所有产品默认可用)
public String resolve(String mchNo, String channel, String method) {
return productConfigManager.findByMchNo(mchNo).stream()
.filter(MchProductConfig::isEnable)
.filter(config -> Objects.equals(config.getChannel(), channel))
.map(MchProductConfig::getProduct)
return Arrays.stream(ProductEnum.values())
.filter(pe -> Objects.equals(pe.getChannel(), channel))
.map(ProductEnum::getCode)
.filter(product -> payRouteStrategyCapabilitySupport.routeProductSupportsMethod(
product, PayMethodEnum.findByCode(method)))
.findFirst()
@@ -54,4 +52,4 @@ public class PayRouteProductResolver {
}
return productEnum.getChannel();
}
}
}

View File

@@ -95,7 +95,7 @@ public class PayRouteSceneConfigService {
PayRouteI18nHelper.payMethod(item.getMethod()), PayRouteI18nHelper.product(item.getProduct()));
}
payRouteStrategyCapabilitySupport.validateSceneCapability(
mchNo, item.getProvider(), item.getMethod(), item.getProduct(), item.getCapability());
item.getProvider(), item.getMethod(), item.getProduct(), item.getCapability());
var route = sceneRouteResolver.resolve(item.getProduct(), item.getProvider(), item.getMethod());
channel = route.channel();
method = route.method();
@@ -143,7 +143,7 @@ public class PayRouteSceneConfigService {
return List.of();
}
String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId);
return payRouteStrategyCapabilitySupport.listSceneProductCandidates(mchNo, provider, method);
return payRouteStrategyCapabilitySupport.listSceneProductCandidates(provider, method);
}
/// 按目录项与支付产品筛选支付能力候选(策略 Map ∩ DB不落库
@@ -153,7 +153,7 @@ public class PayRouteSceneConfigService {
return List.of();
}
String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId);
return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidates(mchNo, provider, method, product);
return payRouteStrategyCapabilitySupport.listSceneCapabilityCandidates(provider, method, product);
}
/// 回显推断支付能力(候选唯一时返回编码)
@@ -162,7 +162,7 @@ public class PayRouteSceneConfigService {
return null;
}
String mchNo = mchAppInfoManager.requireMchNoByAppIdNotTenant(appId);
return payRouteStrategyCapabilitySupport.inferSceneCapability(mchNo, provider, method, product);
return payRouteStrategyCapabilitySupport.inferSceneCapability(provider, method, product);
}
/// 校验批量保存项provider + method 在策略内唯一
@@ -210,7 +210,7 @@ public class PayRouteSceneConfigService {
if (provider == null) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.route.error.basicProviderInvalid");
}
basicConfigService.validateBasicProduct(mchNo, product, provider);
basicConfigService.validateBasicProduct(product, provider);
}
/// 校验通道+方式可解析为商户产品且存在对应产品策略

View File

@@ -1,8 +1,6 @@
package org.dromara.daxpay.payment.merchant.service.route.support;
import org.dromara.daxpay.payment.common.util.PaymentStrategyFactory;
import org.dromara.daxpay.payment.merchant.dao.config.MchProductConfigManager;
import org.dromara.daxpay.payment.merchant.entity.config.MchProductConfig;
import org.dromara.daxpay.payment.merchant.param.route.scene.PayRouteSceneCapabilityBatchItem;
import org.dromara.daxpay.payment.pay.dao.masterdata.capability.PayCapabilityManager;
import org.dromara.daxpay.payment.pay.dao.masterdata.capability.PayProductCapabilityManager;
@@ -26,19 +24,20 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/// # 通道路由:策略方式→能力与 DB/商户求交
/// # 通道路由:策略方式→能力与 DB 求交
///
/// 统一场景模式产品/能力候选及保存校验,数据源为 {@link AbsProductStrategy#methodCapabilityMapping()}。
/// 开源版:所有产品默认可用,不再检查商户启用状态。
@Service
@RequiredArgsConstructor
public class PayRouteStrategyCapabilitySupport {
private final MchProductConfigManager mchProductConfigManager;
private final PayProviderMethodService payProviderMethodService;
private final PayProductCapabilityService payProductCapabilityService;
private final PayProductCapabilityManager payProductCapabilityManager;
@@ -54,7 +53,7 @@ public class PayRouteStrategyCapabilitySupport {
String provider = entry.getProviderCode();
String method = entry.getMethodCode();
String key = PayProviderMethodManager.pairKey(provider, method);
index.put(key, listSceneProductCandidates(mchNo, provider, method));
index.put(key, listSceneProductCandidates(provider, method));
}
return index;
}
@@ -72,7 +71,7 @@ public class PayRouteStrategyCapabilitySupport {
}
String key = capabilityBatchKey(item.getProvider(), item.getMethod(), item.getProduct());
index.put(key, listSceneCapabilityCandidates(
mchNo, item.getProvider(), item.getMethod(), item.getProduct()));
item.getProvider(), item.getMethod(), item.getProduct()));
}
return index;
}
@@ -82,23 +81,19 @@ public class PayRouteStrategyCapabilitySupport {
return provider + "|" + method + "|" + product;
}
/// 目录项下商户可用支付产品候选
public List<LabelValue> listSceneProductCandidates(String mchNo, String provider, String method) {
/// 目录项下可用支付产品候选(开源版:所有产品默认可用)
public List<LabelValue> listSceneProductCandidates(String provider, String method) {
if (!payProviderMethodService.contains(provider, method)) {
return List.of();
}
PayMethodEnum methodEnum = PayMethodEnum.findByCode(method);
List<LabelValue> results = new ArrayList<>();
for (MchProductConfig config : mchProductConfigManager.findByMchNo(mchNo)) {
if (!config.isEnable() || StrUtil.isBlank(config.getProduct())) {
continue;
}
String product = config.getProduct();
for (ProductEnum pe : ProductEnum.values()) {
String product = pe.getCode();
if (!routeProductSupportsMethod(product, methodEnum)) {
continue;
}
ProductEnum productEnum = ProductEnum.findByCode(product);
String label = productEnum != null ? I18nUtil.getEnumName(productEnum) : product;
String label = I18nUtil.getEnumName(pe);
if (results.stream().noneMatch(item -> Objects.equals(item.getValue(), product))) {
results.add(new LabelValue(label, product));
}
@@ -107,13 +102,11 @@ public class PayRouteStrategyCapabilitySupport {
}
/// 指定产品+目录项下支付能力候选(策略 Map ∩ DB ∩ 能力主数据启用)
public List<LabelValue> listSceneCapabilityCandidates(String mchNo, String provider, String method, String product) {
/// 开源版:不再检查商户产品启用状态
public List<LabelValue> listSceneCapabilityCandidates(String provider, String method, String product) {
if (!payProviderMethodService.contains(provider, method)) {
return List.of();
}
if (!merchantProductEnabled(mchNo, product)) {
return List.of();
}
if (!PaymentStrategyFactory.existsByProduct(product, AbsProductStrategy.class)) {
return List.of();
}
@@ -126,8 +119,8 @@ public class PayRouteStrategyCapabilitySupport {
}
/// 候选唯一时返回能力编码(仅供回显)
public String inferSceneCapability(String mchNo, String provider, String method, String product) {
List<LabelValue> candidates = listSceneCapabilityCandidates(mchNo, provider, method, product);
public String inferSceneCapability(String provider, String method, String product) {
List<LabelValue> candidates = listSceneCapabilityCandidates(provider, method, product);
if (candidates.size() == 1) {
return candidates.getFirst().getValue();
}
@@ -135,12 +128,12 @@ public class PayRouteStrategyCapabilitySupport {
}
/// 校验场景配置项所选能力在候选集合内
public void validateSceneCapability(String mchNo, String provider, String method, String product, String capability) {
public void validateSceneCapability(String provider, String method, String product, String capability) {
if (StrUtil.isBlank(capability)) {
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
"pay.route.error.sceneCapabilityRequired");
}
boolean matched = listSceneCapabilityCandidates(mchNo, provider, method, product).stream()
boolean matched = listSceneCapabilityCandidates(provider, method, product).stream()
.anyMatch(item -> Objects.equals(item.getValue(), capability));
if (!matched) {
PayCapabilityEnum capabilityEnum = PayCapabilityEnum.findByCode(capability);
@@ -173,11 +166,6 @@ public class PayRouteStrategyCapabilitySupport {
return routeProductSupportsMethod(product, PayMethodEnum.findByCode(methodCode));
}
private boolean merchantProductEnabled(String mchNo, String product) {
return mchProductConfigManager.findByMchNo(mchNo).stream()
.anyMatch(config -> config.isEnable() && Objects.equals(config.getProduct(), product));
}
private boolean productCapabilityEnabled(String productCode, String capabilityCode) {
if (!payProductCapabilityManager.exists(productCode, capabilityCode)) {
return false;