refactor(auth): 认证参数 capability → method 改名 + 默认推导

认证链路中 capability 与 PayMethodEnum code 同构, 改名为 method 更直观。
内部应用解析层(WxAppFacade/WxAppResolveService)保留 capability 不动。

新增 resolveDefaultMethod: method 缺失时按 authType 补默认值
(wechat 补 wechat_jsapi, douyin 补 douyin_h5), 解决 OPEN/调试/签名API
等入口未传 method 导致应用定位失败的问题。
This commit is contained in:
DaxPay Dev
2026-07-28 00:18:13 +08:00
parent 116e81417f
commit a6d3363583
11 changed files with 53 additions and 30 deletions

View File

@@ -47,7 +47,7 @@ public class DouyinDirectAuthStrategy extends AbsProductAuthStrategy {
@Override
public AuthUrlResult generateAuthUrl(GenerateAuthUrlParam param, String authToken) {
DouyinDirectApp app = douyinDirectAppCapabilityService.resolveWebAppForH5Auth(
param.getChannelMchNo(), param.getCapability(), param.getChannelAppId());
param.getChannelMchNo(), param.getMethod(), param.getChannelAppId());
DouyinDirectAppAuthConfig authConfig = douyinDirectAppAuthConfigService.findByDouyinDirectAppIdForAuth(app.getId());
if (StrUtil.isBlank(authConfig.getAppSecret())) {
// 抖音: 直连应用授权密钥未配置
@@ -64,7 +64,7 @@ public class DouyinDirectAuthStrategy extends AbsProductAuthStrategy {
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
var ctx = resolveContext(param, session);
DouyinDirectApp app = douyinDirectAppCapabilityService.resolveWebAppForH5Auth(
ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
ctx.channelMchNo(), ctx.method(), ctx.channelAppId());
DouyinDirectAppAuthConfig authConfig = douyinDirectAppAuthConfigService.findByDouyinDirectAppIdForAuth(app.getId());
if (StrUtil.isBlank(authConfig.getAppSecret())) {
// 抖音: 直连应用授权密钥未配置

View File

@@ -52,7 +52,7 @@ public class WechatDirectAuthStrategy extends AbsProductAuthStrategy {
@Override
public AuthUrlResult generateAuthUrl(GenerateAuthUrlParam param, String authToken) {
WxAppView app = resolveApp(param.getMchNo(), param.getChannelMchNo(),
param.getCapability(), param.getChannelAppId());
param.getMethod(), param.getChannelAppId());
String redirectUri = AuthRedirectUri.WECHAT.buildRedirectUri(platformUrlConfigService);
WechatAuthUrlResult result = wechatMpAuthService.generateAuthUrl(
redirectUri, app.wxAppId(), app.appSecret(), authToken);
@@ -65,7 +65,7 @@ public class WechatDirectAuthStrategy extends AbsProductAuthStrategy {
@Override
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
var ctx = resolveContext(param, session);
WxAppView app = resolveApp(param.getMchNo(), ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
WxAppView app = resolveApp(param.getMchNo(), ctx.channelMchNo(), ctx.method(), ctx.channelAppId());
WechatAuthResult data = wechatMpAuthService.getTokenAndOpenId(
param.getAuthCode(), app.wxAppId(), app.appSecret());
if (StrUtil.isBlank(data.getOpenId())) {

View File

@@ -57,7 +57,7 @@ public class WechatIsvAuthStrategy extends AbsProductAuthStrategy {
@Override
public AuthUrlResult generateAuthUrl(GenerateAuthUrlParam param, String authToken) {
ResolvedAuthApp app = resolveAuthApp(param.getMchNo(), param.getChannelMchNo(),
param.getCapability(), param.getChannelAppId());
param.getMethod(), param.getChannelAppId());
String redirectUri = AuthRedirectUri.WECHAT.buildRedirectUri(platformUrlConfigService);
WechatAuthUrlResult result = wechatMpAuthService.generateAuthUrl(
redirectUri, app.wxAppId(), app.appSecret(), authToken);
@@ -68,7 +68,7 @@ public class WechatIsvAuthStrategy extends AbsProductAuthStrategy {
@Override
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
var ctx = resolveContext(param, session);
ResolvedAuthApp app = resolveAuthApp(param.getMchNo(), ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
ResolvedAuthApp app = resolveAuthApp(param.getMchNo(), ctx.channelMchNo(), ctx.method(), ctx.channelAppId());
WechatAuthResult data = wechatMpAuthService.getTokenAndOpenId(
param.getAuthCode(), app.wxAppId(), app.appSecret());
if (StrUtil.isBlank(data.getOpenId())) {

View File

@@ -53,9 +53,9 @@ public class AuthSession {
/// 通道商户号
private String channelMchNo;
/// 支付能力编码(用于解析具体应用)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum
private String capability;
/// 支付方式编码(用于解析具体应用)
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum
private String method;
/// 指定认证应用AppId(可选, 优先级高于配置自动解析)
private String channelAppId;

View File

@@ -23,22 +23,22 @@ public abstract class AbsProductAuthStrategy implements PaymentStrategy {
/// 通过AuthCode获取认证结果
///
/// @param session 认证会话上下文(H5场景从 authToken 恢复; 小程序直连场景可为空, 此时从 param 取上下文)。
/// 策略需兼容 session 为 null 的情况(优先用 param 的 channelMchNo/capability/channelAppId)。
/// 策略需兼容 session 为 null 的情况(优先用 param 的 channelMchNo/method/channelAppId)。
public abstract AuthResult doAuth(AuthCodeParam param, AuthSession session);
/// 解析认证上下文: session 字段优先, param 兜底
///
/// H5 OAuth 重定向场景从 session 恢复(param 仅含 authToken);
/// 小程序直连场景 session 为空, 从 param 取上下文。
/// 返回 [ProductAuthContext](channelMchNo / capability / channelAppId)。
/// 返回 [ProductAuthContext](channelMchNo / method / channelAppId)。
protected ProductAuthContext resolveContext(AuthCodeParam param, AuthSession session) {
String channelMchNo = session != null && StrUtil.isNotBlank(session.getChannelMchNo())
? session.getChannelMchNo() : param.getChannelMchNo();
String capability = session != null && StrUtil.isNotBlank(session.getCapability())
? session.getCapability() : param.getCapability();
String method = session != null && StrUtil.isNotBlank(session.getMethod())
? session.getMethod() : param.getMethod();
String channelAppId = session != null && StrUtil.isNotBlank(session.getChannelAppId())
? session.getChannelAppId() : param.getChannelAppId();
return new ProductAuthContext(channelMchNo, capability, channelAppId);
return new ProductAuthContext(channelMchNo, method, channelAppId);
}
}

View File

@@ -7,7 +7,7 @@ package cn.daxpay.open.payment.auth.merchant;
///
/// ## 字段含义
/// - `channelMchNo`: 通道商户号(定位特约商户主数据 / 反查 mchNo)
/// - `capability`: 支付能力编码(公众号 / 小程序, 决定应用维度与 openId 类型)
/// - `method`: 支付方式编码(公众号 / 小程序, 决定应用维度与 openId 类型)
/// - `channelAppId`: 显式指定的应用 AppId(可选, 优先级高于配置自动解析)
public record ProductAuthContext(String channelMchNo, String capability, String channelAppId) {
public record ProductAuthContext(String channelMchNo, String method, String channelAppId) {
}

View File

@@ -13,7 +13,9 @@ import cn.daxpay.open.payment.unipay.result.assist.AuthResult;
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
import cn.daxpay.open.platform.core.enums.unipay.ChannelAuthStatusEnum;
import cn.daxpay.open.platform.core.enums.unipay.ChannelAuthTypeEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
@@ -47,6 +49,8 @@ public class ProductAuthService {
/// 不含 queryCode, 需随会话保存)。
public AuthUrlResult generateAuthUrl(GenerateAuthUrlParam param) {
initMchContext(param.getAppId(), param.getMchNo(), null);
// 支付方式缺失时按认证类型推导默认值(OAuth 重定向天然只适用于公众号场景)
param.setMethod(resolveDefaultMethod(param.getAuthType(), param.getMethod()));
// 支付产品: 显式传入优先, 否则从通道商户号反查(调试工具/直接指定通道商户场景)
String product = resolveProduct(param);
assertNotAlipayProduct(product);
@@ -58,7 +62,7 @@ public class ProductAuthService {
AuthSession session = new AuthSession()
.setProduct(product)
.setChannelMchNo(param.getChannelMchNo())
.setCapability(param.getCapability())
.setMethod(param.getMethod())
.setChannelAppId(param.getChannelAppId())
.setReturnPath(param.getReturnPath())
.setQueryCode(queryCode)
@@ -78,6 +82,8 @@ public class ProductAuthService {
/// 由认证分发层在调用前通过 [AuthSessionStore#loadSession] 加载后注入。
public AuthResult auth(AuthCodeParam param, AuthSession session) {
initMchContext(param.getAppId(), param.getMchNo(), session);
// 支付方式缺失时按认证类型推导(session 恢复场景已有 method, 小程序直连场景需补)
param.setMethod(resolveDefaultMethod(param.getAuthType(), param.getMethod()));
// product 优先从会话恢复, 其次取参数(小程序直连场景)
String product = (session != null && StrUtil.isNotBlank(session.getProduct()))
? session.getProduct() : param.getProduct();
@@ -136,4 +142,21 @@ public class ProductAuthService {
throw new IllegalStateException("支付宝认证应走平台级 Provider, 不应进入产品策略: " + product);
}
}
/// 支付方式缺失时按认证类型推导默认值
///
/// OAuth 重定向认证天然只适用于公众号(H5网页授权)场景, 微信默认补 jsapi, 抖音默认补 h5。
/// 已显式传入 method 的场景(网关/码牌)不受影响。
private static String resolveDefaultMethod(String authType, String method) {
if (StrUtil.isNotBlank(method)) {
return method;
}
if (ChannelAuthTypeEnum.WECHAT.getCode().equals(authType)) {
return PayMethodEnum.WECHAT_JSAPI.getCode();
}
if (ChannelAuthTypeEnum.DOUYIN.getCode().equals(authType)) {
return PayMethodEnum.DOUYIN_H5.getCode();
}
return method;
}
}

View File

@@ -85,7 +85,7 @@ public class GatewayAuthService {
RouteSnapshot route = resolveRoute(order, param);
authParam.setProduct(route.product());
authParam.setChannelMchNo(route.channelMchNo());
authParam.setCapability(route.capability());
authParam.setMethod(route.method());
authParam.setChannelAppId(route.channelAppId());
}
return channelAuthService.generateAuthUrl(authParam);
@@ -261,6 +261,6 @@ public class GatewayAuthService {
}
/// 路由快照(供组装 GenerateAuthUrlParam)
private record RouteSnapshot(String product, String channelMchNo, String capability, String channelAppId) {
private record RouteSnapshot(String product, String channelMchNo, String method, String channelAppId) {
}
}

View File

@@ -52,11 +52,11 @@ public class AuthCodeParam extends MerchantPaymentCommonParam {
@Schema(description = "支付产品编码")
private String product;
/// 支付能力编码, 用于解析具体应用, 小程序场景需要; 会话码场景从会话恢复
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum
@Size(max = 32, message = "{validation.field.capability.size}")
@Schema(description = "支付能力编码")
private String capability;
/// 支付方式编码, 用于解析具体应用, 小程序场景需要; 会话码场景从会话恢复
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum
@Size(max = 32, message = "{validation.field.method.size}")
@Schema(description = "支付方式编码")
private String method;
/// 指定认证使用的应用AppId, 会话码恢复上下文时可不传, 优先级高于配置自动解析
@Size(max = 128, message = "{validation.field.channelAppId.size}")

View File

@@ -38,10 +38,10 @@ public class GenerateAuthUrlParam extends MerchantPaymentCommonParam {
@Schema(description = "来源回跳路径")
private String returnPath;
/// 支付能力编码, 用于解析具体应用(公众号/小程序), 不同能力对应不同应用维度的openId
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum
@Size(max = 32, message = "{validation.field.capability.size}")
@Schema(description = "支付能力编码")
private String capability;
/// 支付方式编码, 决定解析哪个应用(公众号/小程序), 不同方式对应不同应用维度的 openId
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum
@Size(max = 32, message = "{validation.field.method.size}")
@Schema(description = "支付方式编码")
private String method;
}

View File

@@ -182,7 +182,7 @@ public class CodePayAssistService {
authParam.setAppId(mchApp.getAppId());
authParam.setProduct(routeParam.getProduct());
authParam.setChannelMchNo(routeParam.getChannelMchNo());
authParam.setCapability(routeParam.getCapability());
authParam.setMethod(routeParam.getCapability());
authParam.setReturnPath(returnPath);
authParam.setAuthType(this.mapAuthType(clientEnv));
return channelAuthService.generateAuthUrl(authParam);