mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 14:15:34 +08:00
refactor(payment): 交易调试仅保留签名组参,去掉直调支付后门
管理端 develop 不再调用 NormalPayService;真实支付由前端模拟商户请求走 /unipay/pay。 同步放宽直传模式 method 校验,路由模式由 PayRouteService 显式校验。
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
package cn.daxpay.open.payment.web.develop.controller;
|
||||
package cn.daxpay.open.payment.admin.controller.develop;
|
||||
|
||||
import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthResult;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
|
||||
import cn.daxpay.open.payment.web.develop.service.DevelopAuthService;
|
||||
import cn.daxpay.open.payment.admin.service.develop.DevelopAuthService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
@@ -1,9 +1,9 @@
|
||||
package cn.daxpay.open.payment.web.develop.controller;
|
||||
package cn.daxpay.open.payment.admin.controller.develop;
|
||||
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopSignParam;
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopVerifyParam;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.web.develop.service.DevelopSignService;
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopSignParam;
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopVerifyParam;
|
||||
import cn.daxpay.open.payment.admin.result.develop.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.admin.service.develop.DevelopSignService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
@@ -1,9 +1,8 @@
|
||||
package cn.daxpay.open.payment.web.develop.controller;
|
||||
package cn.daxpay.open.payment.admin.controller.develop;
|
||||
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopParam;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopPayResult;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.web.develop.service.DevelopTradeService;
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopParam;
|
||||
import cn.daxpay.open.payment.admin.result.develop.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.admin.service.develop.DevelopTradeService;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.result.PayProviderMethodResult;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
@@ -24,6 +23,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
/// 交易开发调试(管理)
|
||||
///
|
||||
/// 仅提供签名与元数据辅助, 真实支付由前端模拟商户请求调用 `/unipay/pay`。
|
||||
@PermCode(menuCode = "develop:trade")
|
||||
@Tag(name = "交易开发调试服务")
|
||||
@RestController
|
||||
@@ -40,13 +41,6 @@ public class DevelopTradeController {
|
||||
return Res.ok(developTradeService.sign(param));
|
||||
}
|
||||
|
||||
@PermCode(code = "pay", nameCn = "支付", nameEn = "Pay")
|
||||
@Operation(summary = "支付调试(真实发起)")
|
||||
@PostMapping("/pay")
|
||||
public Result<DevelopPayResult> pay(@RequestBody DevelopParam<NormalPayParam> param) {
|
||||
return Res.ok(developTradeService.pay(param));
|
||||
}
|
||||
|
||||
@PermCode(code = "view", nameCn = "查看", nameEn = "View")
|
||||
@Operation(summary = "已启用渠道支付方式目录")
|
||||
@GetMapping("/method-directory")
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.payment.web.develop.param;
|
||||
package cn.daxpay.open.payment.admin.param.develop;
|
||||
|
||||
import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.payment.web.develop.param;
|
||||
package cn.daxpay.open.payment.admin.param.develop;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.payment.web.develop.param;
|
||||
package cn.daxpay.open.payment.admin.param.develop;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.payment.web.develop.result;
|
||||
package cn.daxpay.open.payment.admin.result.develop;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.payment.web.develop.service;
|
||||
package cn.daxpay.open.payment.admin.service.develop;
|
||||
|
||||
import cn.daxpay.open.payment.core.assist.AuthSessionStore;
|
||||
import cn.daxpay.open.payment.core.assist.ChannelAuthService;
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.daxpay.open.payment.web.develop.service;
|
||||
package cn.daxpay.open.payment.admin.service.develop;
|
||||
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopSignParam;
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopVerifyParam;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopSignParam;
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopVerifyParam;
|
||||
import cn.daxpay.open.payment.admin.result.develop.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.common.util.JsonSignStrUtil;
|
||||
import cn.daxpay.open.platform.core.util.RsaSignUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.daxpay.open.payment.admin.service.develop;
|
||||
|
||||
import cn.daxpay.open.payment.admin.param.develop.DevelopParam;
|
||||
import cn.daxpay.open.payment.admin.result.develop.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.common.util.ObjectSignStrUtil;
|
||||
import cn.daxpay.open.payment.common.util.PaySignUtil;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.result.PayProviderMethodResult;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.service.PayProviderMethodService;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteStrategyCapabilitySupport;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.platform.core.rest.dto.LabelValue;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 交易开发调试服务
|
||||
///
|
||||
/// 仅提供组参辅助与签名能力, **不发起真实交易**。
|
||||
/// 真实支付由管理端调试页模拟商户 HTTP 请求调用 `/unipay/pay` 完成,
|
||||
/// 与正式对接走同一入口, 避免 admin 内部直调支付核心形成后门。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DevelopTradeService {
|
||||
|
||||
private final PayProviderMethodService payProviderMethodService;
|
||||
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
|
||||
|
||||
/// 生成支付参数签名(与正式签名逻辑一致)
|
||||
///
|
||||
/// 调用方需在 param 中自备 reqTime / nonceStr 等公共字段, 本方法只负责签名串与签名值。
|
||||
public DevelopSignResult sign(DevelopParam<NormalPayParam> param) {
|
||||
// 签名串(与 PaySignUtil 内部一致)
|
||||
String signStr = ObjectSignStrUtil.buildSignStr(param.getParam());
|
||||
// 签名值
|
||||
String sign = PaySignUtil.sign(param.getParam(), param.getPrivateKey());
|
||||
return new DevelopSignResult().setSignStr(signStr).setSign(sign);
|
||||
}
|
||||
|
||||
/// 已启用渠道支付方式目录(供调试页支付方式下拉)
|
||||
public List<PayProviderMethodResult> listMethodDirectory() {
|
||||
return payProviderMethodService.listDirectoryFlat();
|
||||
}
|
||||
|
||||
/// 传值模式:商户全部启用通道商户候选, provider 为空返回全部,
|
||||
/// 非空按支付渠道过滤(如 wechat 返回所有声明支持微信的产品通道商户, 含官方与三方聚合通道)
|
||||
public List<LabelValue> listChannelMchCandidates(String mchNo, String provider) {
|
||||
return payRouteStrategyCapabilitySupport.listDirectChannelMchCandidates(mchNo, provider);
|
||||
}
|
||||
|
||||
/// 传值模式:按通道商户(产品)返回全部启用支付能力候选
|
||||
public List<LabelValue> listCapabilityCandidates(String channelMchNo) {
|
||||
return payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo);
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,10 @@ public class PayRouteService implements PayRouteFacade {
|
||||
resolveDirect(payParam);
|
||||
return;
|
||||
}
|
||||
// 路由模式: 支付方式必填(Bean Validation 已放宽以兼容直定可空, 此处显式校验)
|
||||
if (StrUtil.isBlank(payParam.getMethod())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.route.error.methodRequired");
|
||||
}
|
||||
String appId = payParam.getAppId();
|
||||
var bundle = loadBundle(appId);
|
||||
if (bundle == null || bundle.getStrategy() == null) {
|
||||
|
||||
@@ -55,9 +55,10 @@ public class NormalPayParam extends MerchantPaymentCommonParam {
|
||||
private String product;
|
||||
|
||||
/// 支付方式编码
|
||||
/// - 路由模式: 必填, 由路由引擎按 appId+method 匹配
|
||||
/// - 直定模式(已传 channelMchNo+capability): 可空, 由 PayRouteService 按能力反推回填
|
||||
/// @see PayMethodEnum
|
||||
@Schema(description = "支付方式编码")
|
||||
@NotBlank(message = "{validation.field.method.notBlank}")
|
||||
@Schema(description = "支付方式编码(路由模式必填; 直定模式可空, 由能力反推)")
|
||||
@Size(max = 32, message = "{validation.field.method.size}")
|
||||
private String method;
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package cn.daxpay.open.payment.web.develop.result;
|
||||
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付调试结果
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付调试结果")
|
||||
public class DevelopPayResult {
|
||||
|
||||
/// 支付结果
|
||||
@Schema(description = "支付结果")
|
||||
private NormalPayResult payResult;
|
||||
|
||||
/// 响应签名(平台私钥签名, 可用于验证响应完整性)
|
||||
@Schema(description = "响应签名")
|
||||
private String sign;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package cn.daxpay.open.payment.web.develop.service;
|
||||
|
||||
import cn.daxpay.open.payment.web.develop.param.DevelopParam;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopPayResult;
|
||||
import cn.daxpay.open.payment.web.develop.result.DevelopSignResult;
|
||||
import cn.daxpay.open.payment.common.runtime.PaymentContext;
|
||||
import cn.daxpay.open.payment.common.service.MerchantContextLoader;
|
||||
import cn.daxpay.open.payment.common.service.PaySignService;
|
||||
import cn.daxpay.open.payment.common.util.ObjectSignStrUtil;
|
||||
import cn.daxpay.open.payment.common.util.PaySignUtil;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.result.PayProviderMethodResult;
|
||||
import cn.daxpay.open.payment.masterdata.constants.provider.service.PayProviderMethodService;
|
||||
import cn.daxpay.open.payment.merchant.service.route.support.PayRouteStrategyCapabilitySupport;
|
||||
import cn.daxpay.open.payment.core.trade.runtime.service.pay.NormalPayService;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import cn.daxpay.open.platform.common.config.properties.PlatformConfigProperties;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.rest.dto.LabelValue;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 交易开发调试服务
|
||||
///
|
||||
/// 复用普通支付核心服务 NormalPayService, 在管理端以指定商户/应用身份真实发起一笔支付,
|
||||
/// 便于联调验证通道配置与签名是否正确
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DevelopTradeService {
|
||||
|
||||
private final NormalPayService normalPayService;
|
||||
private final PaymentContext paymentContext;
|
||||
private final MerchantContextLoader merchantContextLoader;
|
||||
private final PaySignService paySignService;
|
||||
private final PlatformConfigProperties platformConfigProperties;
|
||||
private final PayProviderMethodService payProviderMethodService;
|
||||
private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport;
|
||||
|
||||
/// 生成支付参数签名(与正式签名逻辑一致)
|
||||
public DevelopSignResult sign(DevelopParam<NormalPayParam> param) {
|
||||
// 签名串(与 PaySignUtil 内部一致)
|
||||
String signStr = ObjectSignStrUtil.buildSignStr(param.getParam());
|
||||
// 签名值
|
||||
String sign = PaySignUtil.sign(param.getParam(), param.getPrivateKey());
|
||||
return new DevelopSignResult().setSignStr(signStr).setSign(sign);
|
||||
}
|
||||
|
||||
/// 支付调试(真实发起)
|
||||
///
|
||||
/// 安全流程与正式支付一致:
|
||||
/// 1. 使用传入的商户私钥对参数签名, 再用系统配置的商户公钥验签, 校验私钥是否正确
|
||||
/// 2. 发起真实支付
|
||||
/// 3. 使用平台私钥对支付结果签名, 便于客户端验证响应完整性
|
||||
public DevelopPayResult pay(DevelopParam<NormalPayParam> param) {
|
||||
NormalPayParam payParam = param.getParam();
|
||||
// 私钥必填
|
||||
if (StrUtil.isBlank(param.getPrivateKey())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.assist.privateKeyEmpty");
|
||||
}
|
||||
// 在支付作用域内执行, 初始化商户身份(mchNo 进入线程上下文)
|
||||
return paymentContext.runAs(() -> {
|
||||
// 初始化商户身份(含状态校验)
|
||||
merchantContextLoader.initMch(payParam.getMchNo());
|
||||
// 私钥验证: 用传入私钥签名, 再用系统配置的商户公钥验签
|
||||
String sign = PaySignUtil.sign(payParam, param.getPrivateKey());
|
||||
payParam.setSign(sign);
|
||||
paySignService.signVerify(payParam);
|
||||
// 发起真实支付
|
||||
NormalPayResult payResult = normalPayService.pay(payParam);
|
||||
// 使用平台私钥对支付结果签名
|
||||
String platformPrivateKey = platformConfigProperties.getKeyConfig().getPrivateKey();
|
||||
String resultSign = PaySignUtil.sign(payResult, platformPrivateKey);
|
||||
return new DevelopPayResult()
|
||||
.setPayResult(payResult)
|
||||
.setSign(resultSign);
|
||||
});
|
||||
}
|
||||
|
||||
/// 已启用渠道支付方式目录(供调试页支付方式下拉)
|
||||
public List<PayProviderMethodResult> listMethodDirectory() {
|
||||
return payProviderMethodService.listDirectoryFlat();
|
||||
}
|
||||
|
||||
/// 传值模式:商户全部启用通道商户候选, provider 为空返回全部,
|
||||
/// 非空按支付渠道过滤(如 wechat 返回所有声明支持微信的产品通道商户, 含官方与三方聚合通道)
|
||||
public List<LabelValue> listChannelMchCandidates(String mchNo, String provider) {
|
||||
return payRouteStrategyCapabilitySupport.listDirectChannelMchCandidates(mchNo, provider);
|
||||
}
|
||||
|
||||
/// 传值模式:按通道商户(产品)返回全部启用支付能力候选
|
||||
public List<LabelValue> listCapabilityCandidates(String channelMchNo) {
|
||||
return payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"strategyNotFound": "Payment route strategy not configured for this app",
|
||||
"noMatch": "No available payment product matched",
|
||||
"methodRequired": "Pay method is required in route mode",
|
||||
"providerRequired": "Pay brand is required when product is not specified",
|
||||
"methodRequiredWhenProductSet": "Pay method is required when product is specified",
|
||||
"basicProviderInvalid": "Invalid pay brand",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"strategyNotFound": "应用未配置通道路由策略",
|
||||
"noMatch": "未匹配到可用支付产品",
|
||||
"methodRequired": "路由模式下支付方式不能为空",
|
||||
"providerRequired": "未指定支付产品时,支付渠道不能为空",
|
||||
"methodRequiredWhenProductSet": "指定支付产品时,支付方式不能为空",
|
||||
"basicProviderInvalid": "支付渠道无效",
|
||||
|
||||
Reference in New Issue
Block a user