feat(merchant): 商户端支付调试功能(交易/网关/签名/认证)

后端仅提供签名与元数据辅助, 真实交易由前端模拟商户请求直调 /unipay/pay, 与正式对接走同一入口避免后门; 复用 payment-core 的 PayProviderMethodService/PayRouteStrategyCapabilitySupport/PlatformAuthService 等 bean。

update-datas.sql 新增商户端 develop 菜单(顶级目录 91600 + 4 子菜单, sort=5 排在回调通知之后)。
This commit is contained in:
DaxPay Dev
2026-07-25 15:48:23 +08:00
parent 4c0bdba82f
commit 358755628f
13 changed files with 523 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
-- ============================================================
-- 升级数据脚本 (上一个版本 -> 当前版本)
-- 执行顺序: update-tables.sql -> update-datas.sql
-- 注意: 不可跨版本升级, 仅限相邻版本间执行
-- ============================================================
-- ----------------------------
-- 商户端 开发调试(develop) 菜单
-- 顶级目录 + 4 子菜单(交易/网关/签名/认证调试)
-- 对应前端 apps/daxpay-merchant, 后端 /mch/develop/* 接口
-- 排序: 顶级目录 sort=5, 位于「回调通知/交易记录」(sort=4.5)之后
-- ----------------------------
INSERT INTO "public"."iam_perm_menu" VALUES (91600, NULL, NULL, 'merchant', 'Develop', 'menu.develop', 'lucide:wrench', 'f', 'f', NULL, '/develop', '/develop/trade', 5, 'f', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (91601, 91600, 'develop:trade', 'merchant', 'DevelopTrade', 'menu.develop.trade', 'lucide:credit-card', 'f', 'f', '/payment/develop/trade/DevelopTrade', '/develop/trade', NULL, 1, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (91602, 91600, 'develop:gateway', 'merchant', 'DevelopGateway', 'menu.develop.gateway', 'lucide:globe', 'f', 'f', '/payment/develop/gateway/DevelopGateway', '/develop/gateway', NULL, 2, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (91603, 91600, 'develop:sign', 'merchant', 'DevelopSign', 'menu.develop.sign', 'lucide:pen-tool', 'f', 'f', '/payment/develop/sign/DevelopSign', '/develop/sign', NULL, 3, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');
INSERT INTO "public"."iam_perm_menu" VALUES (91604, 91600, 'develop:auth', 'merchant', 'ChannelAuth', 'menu.develop.auth', 'lucide:shield-check', 'f', 'f', '/payment/develop/auth/ChannelAuth', '/develop/auth', NULL, 4, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00');

View File

@@ -0,0 +1,80 @@
package cn.daxpay.open.payment.merchant.controller.develop;
import cn.daxpay.open.payment.merchant.service.develop.DevelopAuthService;
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.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/// 认证调试(商户端)
///
/// 调试入口:
/// - 支付宝H5平台级配置(中间页授权链接)
/// - 微信公众号配置(平台级 OAuth)
/// - 抖音 H5 应用配置(平台级 silent_auth 静默授权)
/// - 微信支付(直连/服务商, 需商户参数)
/// - 支付宝小程序(暂未实现)
/// - 微信小程序(商户端/运营端, 暂未实现)
///
/// 已实现项均通过查询码轮询认证结果。
@Validated
@PermCode(menuCode = PermCodes.Develop.Auth.MENU)
@Tag(name = "认证调试服务(商户端)")
@RestController
@RequestMapping("/mch/develop/auth")
@RequiredArgsConstructor
public class DevelopAuthController {
private final DevelopAuthService developAuthService;
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "生成支付宝H5授权链接")
@PostMapping("/generate-alipay-auth-url")
public Result<AuthUrlResult> generateAlipayAuthUrl() {
return Res.ok(developAuthService.generateAlipayAuthUrl());
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "生成微信公众号配置授权链接")
@PostMapping("/generate-wechat-mp-auth-url")
public Result<AuthUrlResult> generateWechatMpAuthUrl() {
return Res.ok(developAuthService.generateWechatMpAuthUrl());
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "生成抖音H5授权链接")
@PostMapping("/generate-douyin-auth-url")
public Result<AuthUrlResult> generateDouyinAuthUrl() {
return Res.ok(developAuthService.generateDouyinAuthUrl());
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "生成微信支付授权链接")
@PostMapping("/generate-channel-auth-url")
public Result<AuthUrlResult> generateChannelAuthUrl(@RequestBody GenerateAuthUrlParam param) {
// 不加 @Valid: GenerateAuthUrlParam 继承 PaymentCommonParam.reqTime(@NotNull), 但认证不走签名/防重放, 无需 reqTime;
// channel/mchNo 由 ChannelProductAuthService 业务层兜底校验, 与 unipay ChannelAuthController 同类接口保持一致
return Res.ok(developAuthService.generateChannelAuthUrl(param));
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "通过查询码获取认证结果")
@GetMapping("/query-auth-result")
public Result<AuthResult> queryAuthResult(
@NotBlank(message = "{validation.field.queryCode.notBlank}") String queryCode) {
return Res.ok(developAuthService.queryAuthResult(queryCode));
}
}

View File

@@ -0,0 +1,37 @@
package cn.daxpay.open.payment.merchant.controller.develop;
import cn.daxpay.open.payment.merchant.param.develop.DevelopParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.payment.merchant.service.develop.DevelopGatewayService;
import cn.daxpay.open.payment.unipay.param.gateway.GatewayPrePayParam;
import cn.daxpay.open.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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;
/// 网关支付开发调试(商户端)
///
/// 仅提供签名辅助, 真实预下单由前端模拟商户请求调用 `/unipay/gateway/pre-pay`。
@PermCode(menuCode = PermCodes.Develop.Gateway.MENU)
@Tag(name = "网关支付开发调试服务(商户端)")
@RestController
@RequestMapping("/mch/develop/gateway")
@RequiredArgsConstructor
public class DevelopGatewayController {
private final DevelopGatewayService developGatewayService;
@PermCode(code = PermCodes.Action.SIGN)
@Operation(summary = "网关预下单参数签名")
@PostMapping("/sign")
public Result<DevelopSignResult> sign(@RequestBody DevelopParam<GatewayPrePayParam> param) {
return Res.ok(developGatewayService.sign(param));
}
}

View File

@@ -0,0 +1,42 @@
package cn.daxpay.open.payment.merchant.controller.develop;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.payment.merchant.param.develop.DevelopSignParam;
import cn.daxpay.open.payment.merchant.param.develop.DevelopVerifyParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.payment.merchant.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;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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;
/// 签名调试(商户端)
@PermCode(menuCode = PermCodes.Develop.Sign.MENU)
@Tag(name = "签名调试服务(商户端)")
@RestController
@RequestMapping("/mch/develop/sign")
@RequiredArgsConstructor
public class DevelopSignController {
private final DevelopSignService developSignService;
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "生成签名")
@PostMapping("/gen")
public Result<DevelopSignResult> sign(@RequestBody DevelopSignParam param) {
return Res.ok(developSignService.sign(param));
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "验签")
@PostMapping("/verify")
public Result<Boolean> verify(@RequestBody DevelopVerifyParam param) {
return Res.ok(developSignService.verify(param));
}
}

View File

@@ -0,0 +1,69 @@
package cn.daxpay.open.payment.merchant.controller.develop;
import cn.daxpay.open.payment.merchant.param.develop.DevelopParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.payment.merchant.service.develop.DevelopTradeService;
import cn.daxpay.open.payment.masterdata.result.provider.PayProviderMethodResult;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.platform.core.annotation.PermCode;
import cn.daxpay.open.platform.core.code.PermCodes;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.dto.ChannelMchOption;
import cn.daxpay.open.platform.core.rest.dto.LabelValue;
import cn.daxpay.open.platform.core.rest.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/// 交易开发调试(商户端)
///
/// 仅提供签名与元数据辅助, 真实支付由前端模拟商户请求调用 `/unipay/pay`。
@PermCode(menuCode = PermCodes.Develop.Trade.MENU)
@Tag(name = "交易开发调试服务(商户端)")
@RestController
@RequestMapping("/mch/develop/trade")
@RequiredArgsConstructor
public class DevelopTradeController {
private final DevelopTradeService developTradeService;
@PermCode(code = PermCodes.Action.SIGN)
@Operation(summary = "支付参数签名")
@PostMapping("/sign")
public Result<DevelopSignResult> sign(@RequestBody DevelopParam<NormalPayParam> param) {
return Res.ok(developTradeService.sign(param));
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "已启用渠道支付方式目录")
@GetMapping("/method-directory")
public Result<List<PayProviderMethodResult>> methodDirectory() {
return Res.ok(developTradeService.listMethodDirectory());
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "直接指定通道商户候选")
@GetMapping("/channel-mch-candidates")
public Result<List<ChannelMchOption>> channelMchCandidates(
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
@RequestParam(required = false) String provider) {
return Res.ok(developTradeService.listChannelMchCandidates(mchNo, provider));
}
@PermCode(code = PermCodes.Action.VIEW)
@Operation(summary = "直接指定支付能力候选")
@GetMapping("/capability-candidates")
public Result<List<LabelValue>> capabilityCandidates(
@NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo) {
return Res.ok(developTradeService.listCapabilityCandidates(channelMchNo));
}
}

View File

@@ -0,0 +1,23 @@
package cn.daxpay.open.payment.merchant.param.develop;
import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 开发调试参数包装
///
/// 包装业务参数与调试用手填私钥, 私钥仅用于本次签名生成, 不入库
@Data
@Accessors(chain = true)
@Schema(title = "开发调试参数包装")
public class DevelopParam<T extends MerchantPaymentCommonParam> {
/// 业务参数
@Schema(description = "业务参数")
private T param;
/// 生成签名使用的私钥(PEM 格式, 调试页手填, 不入库)
@Schema(description = "生成签名使用的私钥(PEM格式)")
private String privateKey;
}

View File

@@ -0,0 +1,20 @@
package cn.daxpay.open.payment.merchant.param.develop;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 签名调试-生成签名参数
@Data
@Accessors(chain = true)
@Schema(title = "签名调试-生成签名参数")
public class DevelopSignParam {
/// 待签名 JSON 字符串
@Schema(description = "待签名JSON字符串")
private String json;
/// 私钥(PEM 格式)
@Schema(description = "私钥(PEM格式)")
private String privateKey;
}

View File

@@ -0,0 +1,24 @@
package cn.daxpay.open.payment.merchant.param.develop;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 签名调试-验签参数
@Data
@Accessors(chain = true)
@Schema(title = "签名调试-验签参数")
public class DevelopVerifyParam {
/// 待验签 JSON 字符串(不含 sign 字段)
@Schema(description = "待验签JSON字符串(不含sign字段)")
private String json;
/// 签名值
@Schema(description = "签名值")
private String sign;
/// 公钥(PEM 格式)
@Schema(description = "公钥(PEM格式)")
private String publicKey;
}

View File

@@ -0,0 +1,20 @@
package cn.daxpay.open.payment.merchant.result.develop;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 签名调试结果
@Data
@Accessors(chain = true)
@Schema(title = "签名调试结果")
public class DevelopSignResult {
/// 待签名原文(键值拼接串)
@Schema(description = "待签名原文")
private String signStr;
/// 签名值
@Schema(description = "签名值")
private String sign;
}

View File

@@ -0,0 +1,61 @@
package cn.daxpay.open.payment.merchant.service.develop;
import cn.daxpay.open.payment.auth.AuthSessionStore;
import cn.daxpay.open.payment.auth.ChannelProductAuthService;
import cn.daxpay.open.payment.auth.PlatformAuthService;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/// # 认证调试服务
///
/// 调试入口, 按认证来源分别委托:
/// - **支付宝(平台级)**: 委托 [PlatformAuthService] 生成 OAuth 授权链接, 轮询 queryCode 取结果
/// - **微信公众号配置(平台级)**: 委托 [PlatformAuthService], OAuth 重定向取 openId, 仅验证配置是否正确
/// - **抖音H5(平台级)**: 委托 [PlatformAuthService], silent_auth 静默授权取 openId, 仅验证配置是否正确
/// - **微信支付(直连/服务商)**: 委托 [ChannelProductAuthService] 按支付产品路由认证策略,
/// 依赖商户上下文(channelMchNo/产品/能力)
/// - **支付宝小程序**: 暂未实现
/// - **微信小程序(商户端/运营端)**: 暂未实现
///
/// 已实现项共用 queryCode 轮询机制(由 [AuthSessionStore#queryAuthResult] 统一查询)。
@Slf4j
@Service
@RequiredArgsConstructor
public class DevelopAuthService {
private final PlatformAuthService platformAuthService;
private final ChannelProductAuthService channelProductAuthService;
private final AuthSessionStore authSessionStore;
/// 生成支付宝授权链接(平台级 OAuth + queryCode 轮询)
public AuthUrlResult generateAlipayAuthUrl() {
return platformAuthService.generateAlipayAuthUrl();
}
/// 生成微信公众号配置授权链接(平台级, 仅调试)
public AuthUrlResult generateWechatMpAuthUrl() {
return platformAuthService.generateWechatMpAuthUrl();
}
/// 生成抖音 H5 授权链接(平台级, 仅调试)
public AuthUrlResult generateDouyinAuthUrl() {
return platformAuthService.generateDouyinAuthUrl();
}
/// 生成微信支付(直连/服务商)授权链接
///
/// 委托 [ChannelProductAuthService#generateAuthUrl], 按支付产品路由对应认证策略:
/// 直连(WECHAT_PAY) → WechatDirectAuthStrategy; 服务商(WECHAT_ISV) → WechatIsvAuthStrategy。
public AuthUrlResult generateChannelAuthUrl(GenerateAuthUrlParam param) {
return channelProductAuthService.generateAuthUrl(param);
}
/// 通过查询码获取认证结果
public AuthResult queryAuthResult(String queryCode) {
return authSessionStore.queryAuthResult(queryCode);
}
}

View File

@@ -0,0 +1,32 @@
package cn.daxpay.open.payment.merchant.service.develop;
import cn.daxpay.open.payment.common.util.ObjectSignStrUtil;
import cn.daxpay.open.payment.common.util.PaySignUtil;
import cn.daxpay.open.payment.merchant.param.develop.DevelopParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.payment.unipay.param.gateway.GatewayPrePayParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/// # 网关支付开发调试服务
///
/// 仅提供组参辅助与签名能力, **不发起真实交易**。
/// 真实预下单由商户端调试页模拟商户 HTTP 请求调用 `/unipay/gateway/pre-pay` 完成,
/// 与正式对接走同一入口, 避免内部直调支付核心形成后门。
@Slf4j
@Service
@RequiredArgsConstructor
public class DevelopGatewayService {
/// 生成网关预下单参数签名(与正式签名逻辑一致)
///
/// 调用方需在 param 中自备 reqTime / nonceStr 等公共字段, 本方法只负责签名串与签名值。
public DevelopSignResult sign(DevelopParam<GatewayPrePayParam> param) {
// 签名串(与 PaySignUtil 内部一致)
String signStr = ObjectSignStrUtil.buildSignStr(param.getParam());
// 签名值
String sign = PaySignUtil.sign(param.getParam(), param.getPrivateKey());
return new DevelopSignResult().setSignStr(signStr).setSign(sign);
}
}

View File

@@ -0,0 +1,40 @@
package cn.daxpay.open.payment.merchant.service.develop;
import cn.daxpay.open.payment.common.util.JsonSignStrUtil;
import cn.daxpay.open.payment.merchant.param.develop.DevelopSignParam;
import cn.daxpay.open.payment.merchant.param.develop.DevelopVerifyParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.platform.core.util.RsaSignUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/// # 签名调试服务
///
/// 提供参数签名生成与验签功能, 便于商户对接联调
@Slf4j
@Service
@RequiredArgsConstructor
public class DevelopSignService {
/// 生成签名
public DevelopSignResult sign(DevelopSignParam param) {
// 待签名原文(扁平化排序后的键值拼接串)
String signStr = JsonSignStrUtil.buildSignStr(param.getJson());
// 使用私钥生成签名
String sign = RsaSignUtil.sign(signStr, param.getPrivateKey());
return new DevelopSignResult().setSignStr(signStr).setSign(sign);
}
/// 验签
public boolean verify(DevelopVerifyParam param) {
String signStr = JsonSignStrUtil.buildSignStr(param.getJson());
try {
return RsaSignUtil.verify(signStr, param.getSign(), param.getPublicKey());
} catch (Exception e) {
// 验签失败不抛异常, 返回 false 便于前端展示
log.warn("验签失败: {}", e.getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,58 @@
package cn.daxpay.open.payment.merchant.service.develop;
import cn.daxpay.open.payment.masterdata.result.provider.PayProviderMethodResult;
import cn.daxpay.open.payment.masterdata.service.provider.PayProviderMethodService;
import cn.daxpay.open.payment.merchant.param.develop.DevelopParam;
import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult;
import cn.daxpay.open.payment.common.util.ObjectSignStrUtil;
import cn.daxpay.open.payment.common.util.PaySignUtil;
import cn.daxpay.open.payment.route.service.support.PayRouteStrategyCapabilitySupport;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.platform.core.rest.dto.ChannelMchOption;
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` 完成,
/// 与正式对接走同一入口, 避免内部直调支付核心形成后门。
@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<ChannelMchOption> listChannelMchCandidates(String mchNo, String provider) {
return payRouteStrategyCapabilitySupport.listDirectChannelMchCandidates(mchNo, provider);
}
/// 直接指定:按通道商户(产品)返回全部启用支付能力候选
public List<LabelValue> listCapabilityCandidates(String channelMchNo) {
return payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo);
}
}