mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 06:05:33 +08:00
refactor(channel): 通道应用解析 appType 自动推导改为唯一性校验, >1 拒绝猜测
alipay/douyin/wechat 三通道 AppManager 新增 listByChannelMshNoAndAppType 返回全量列表; CapabilityService 推导时若同类型应用存在多个, 抛 appNotUnique 要求显式配置能力绑定, 避免静默选错应用。 同步清理 resolveAppNotTenant 等冗余方法。
This commit is contained in:
@@ -56,6 +56,16 @@ public class AlipayDirectAppManager extends BaseManager<AlipayDirectAppMapper, A
|
||||
.orderByAsc(AlipayDirectApp::getId));
|
||||
}
|
||||
|
||||
/// 根据通道商户号与应用类型查询全部应用,供应用解析做唯一性判断
|
||||
public List<AlipayDirectApp> listByChannelMchNoAndAppType(String channelMchNo, String appType) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayDirectApp::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayDirectApp::getAppType, appType)
|
||||
.orderByAsc(AlipayDirectApp::getCreateTime)
|
||||
.orderByAsc(AlipayDirectApp::getId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/// 根据通道商户号查询首个应用(支付/回调,租户内)
|
||||
public Optional<AlipayDirectApp> findFirstByChannelMchNo(String channelMchNo) {
|
||||
return firstOpt(q -> q
|
||||
|
||||
@@ -112,11 +112,12 @@ public class AlipayDirectAppCapabilityService {
|
||||
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导(须已装载 mchNo,租户内)
|
||||
///
|
||||
/// 认证无上下文请用 [#resolveAppNotTenant]。
|
||||
/// appType 推导要求该通道商户下该类型应用唯一命中:
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个应用:抛 appNotUnique,要求显式配置能力绑
|
||||
/// - 该类型无应用:返回 empty,由调用方走最终报错
|
||||
///
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param capability 支付能力编码
|
||||
/// @return 命中的应用;两者均未命中返回 empty,由调用方兜底回退
|
||||
/// 认证无上下文请用 [#resolveAppNotTenant]。
|
||||
public Optional<AlipayDirectApp> resolveApp(String channelMchNo, String capability) {
|
||||
if (StrUtil.hasBlank(channelMchNo, capability)) {
|
||||
return Optional.empty();
|
||||
@@ -126,20 +127,22 @@ public class AlipayDirectAppCapabilityService {
|
||||
if (rel.isPresent()) {
|
||||
return alipayDirectAppManager.findById(rel.get().getAlipayDirectAppId());
|
||||
}
|
||||
// 2. 未配置则按能力 → appType 自动推导
|
||||
// 2. appType 推导:要求该通道商户下该类型应用唯一命中,>1 拒绝猜测
|
||||
String appType = AlipayDirectAppTypeCode.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
return alipayDirectAppManager.findFirstByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
List<AlipayDirectApp> apps = alipayDirectAppManager.listByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return Optional.of(apps.getFirst());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/// 认证等无租户上下文时解析应用(忽略租户)
|
||||
@IgnoreTenant
|
||||
public Optional<AlipayDirectApp> resolveAppNotTenant(String channelMchNo, String capability) {
|
||||
return resolveApp(channelMchNo, capability);
|
||||
}
|
||||
|
||||
/// 查询支付宝直连产品支持的支付能力候选列表(含国际化名称)
|
||||
public List<AlipayDirectCapabilityOption> listSupportedCapabilities() {
|
||||
AbsProductStrategy strategy = PaymentStrategyFactory.createByProduct(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package cn.daxpay.open.channel.alipay.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectApp;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectAppKeyConfig;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectChannelMerchant;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -21,7 +22,7 @@ import java.util.Optional;
|
||||
/// 沙箱标识直接读通道商户固化的 [AlipayDirectChannelMerchant#isSandbox]
|
||||
/// (创建时按当时产品 activeEnv 写入, 不随产品切换改变), 据此选择对应环境的密钥与网关地址。
|
||||
///
|
||||
/// 应用解析优先级:能力关联(显式配置 > appType自动推导) > 通道商户首个应用 > 商户号首个应用(兜底)。
|
||||
/// 应用解析优先级:能力关联(显式配置 > appType 唯一推导) > 未命中报错(拒绝首个兜底)。
|
||||
///
|
||||
/// 供支付策略([cn.daxpay.open.channel.alipay.strategy.direct.AlipayDirectPayStrategy])组装通道调用凭证。
|
||||
@Slf4j
|
||||
@@ -29,7 +30,6 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayDirectConfigAssembler {
|
||||
|
||||
private final AlipayDirectAppManager alipayDirectAppManager;
|
||||
private final AlipayDirectChannelMerchantManager alipayDirectChannelMerchantManager;
|
||||
private final AlipayDirectAppKeyConfigService alipayDirectAppKeyConfigService;
|
||||
private final AlipayDirectAppCapabilityService alipayDirectAppCapabilityService;
|
||||
@@ -64,21 +64,16 @@ public class AlipayDirectConfigAssembler {
|
||||
}
|
||||
|
||||
/// 解析支付使用的应用(须已装载 mchNo,租户内)
|
||||
/// 优先级:能力关联 > 通道商户首个 > 商户号首个兜底
|
||||
/// 优先级:能力关联(显式配置 > appType 唯一推导)> 未命中报错(不再首个兜底)
|
||||
private AlipayDirectApp resolveApp(String mchNo, String channelMchNo, String capability) {
|
||||
// 1. 能力关联解析(显式配置 > appType自动推导)
|
||||
// 能力关联解析(显式配置 > appType 唯一推导)
|
||||
Optional<AlipayDirectApp> resolved = alipayDirectAppCapabilityService.resolveApp(channelMchNo, capability);
|
||||
if (resolved.isPresent()) {
|
||||
return resolved.get();
|
||||
}
|
||||
// 2. 兜底:按通道商户号取首个应用
|
||||
Optional<AlipayDirectApp> byChannel = alipayDirectAppManager.findFirstByChannelMchNo(channelMchNo);
|
||||
if (byChannel.isPresent()) {
|
||||
return byChannel.get();
|
||||
}
|
||||
// 3. 最终兜底:按商户号取首个应用(兼容单应用旧场景)
|
||||
// 支付宝: 直连商户应用不存在
|
||||
return alipayDirectAppManager.findFirstByMchNo(mchNo)
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.mchAppNotFound"));
|
||||
// 未命中:不再做通道商户首个/商户号首个兜底,直接报错
|
||||
// 支付宝: 未配置该能力对应的应用
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.appNotConfigured", capability);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,16 @@ public class DouyinDirectAppManager extends BaseManager<DouyinDirectAppMapper, D
|
||||
.orderByAsc(DouyinDirectApp::getId));
|
||||
}
|
||||
|
||||
/// 根据通道商户号与应用类型取全部应用,供应用解析做唯一性判断
|
||||
public List<DouyinDirectApp> listByChannelMchNoAndAppType(String channelMchNo, String appType) {
|
||||
return lambdaQuery()
|
||||
.eq(DouyinDirectApp::getChannelMchNo, channelMchNo)
|
||||
.eq(DouyinDirectApp::getAppType, appType)
|
||||
.orderByAsc(DouyinDirectApp::getCreateTime)
|
||||
.orderByAsc(DouyinDirectApp::getId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/// 按通道商户号与 douyinAppId 查询应用(支付/回调,租户内)
|
||||
public Optional<DouyinDirectApp> findByChannelMchNoAndDouyinAppId(String channelMchNo, String douyinAppId) {
|
||||
return lambdaQuery()
|
||||
|
||||
@@ -109,13 +109,15 @@ public class DouyinDirectAppCapabilityService {
|
||||
capabilityManager.deleteByDouyinDirectAppId(douyinDirectAppId);
|
||||
}
|
||||
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导 > 通道商户首个兜底(须已装载 mchNo,租户内)
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导(须已装载 mchNo,租户内)
|
||||
///
|
||||
/// appType 推导要求该通道商户下该类型应用唯一命中:
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个应用:抛 appNotUnique,要求显式配置能力绑
|
||||
/// - 该类型无应用:返回 empty,由调用方走最终报错
|
||||
/// 不再"通道商户首个"兜底。
|
||||
///
|
||||
/// 认证无上下文请用 [#resolveAppNotTenant]。
|
||||
///
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param capability 支付能力编码
|
||||
/// @return 命中的应用;三者均未命中返回 empty,由调用方兜底回退
|
||||
public Optional<DouyinDirectApp> resolveApp(String channelMchNo, String capability) {
|
||||
if (StrUtil.hasBlank(channelMchNo, capability)) {
|
||||
return Optional.empty();
|
||||
@@ -125,16 +127,21 @@ public class DouyinDirectAppCapabilityService {
|
||||
if (rel.isPresent()) {
|
||||
return douyinDirectAppManager.findById(rel.get().getDouyinDirectAppId());
|
||||
}
|
||||
// 2. 未配置则按能力 → appType 自动推导
|
||||
// 2. appType 推导:要求该通道商户下该类型应用唯一命中,>1 拒绝猜测
|
||||
String appType = DouyinAppTypeCode.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
Optional<DouyinDirectApp> byType = douyinDirectAppManager.findFirstByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (byType.isPresent()) {
|
||||
return byType;
|
||||
List<DouyinDirectApp> apps = douyinDirectAppManager.listByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return Optional.of(apps.getFirst());
|
||||
}
|
||||
}
|
||||
// 3. 最终兜底:按通道商户号取首个应用
|
||||
return douyinDirectAppManager.findFirstByChannelMchNo(channelMchNo);
|
||||
// 3. 不再首个兜底,返回 empty 由调用方报错
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/// 认证等无租户上下文时解析应用(忽略租户)
|
||||
@@ -145,8 +152,8 @@ public class DouyinDirectAppCapabilityService {
|
||||
|
||||
/// H5 silent_auth / JS-SDK 验签用网站应用解析(忽略租户)
|
||||
///
|
||||
/// 优先级: channelAppId 显式 > capability 命中且为 web_app > 通道商户首个 web_app。
|
||||
/// 勿盲跟 DOUYIN_JSAPI→mini_program 推导。
|
||||
/// 优先级: channelAppId 显式 > capability 命中且为 web_app > 该通道商户唯一 web_app。
|
||||
/// 不再"首个 web_app"兜底;web_app 不唯一或不存在均直接报错。
|
||||
@IgnoreTenant
|
||||
public DouyinDirectApp resolveWebAppForH5Auth(String channelMchNo, String capability, String channelAppId) {
|
||||
if (StrUtil.isNotBlank(channelAppId)) {
|
||||
@@ -160,10 +167,19 @@ public class DouyinDirectAppCapabilityService {
|
||||
return byCap.get();
|
||||
}
|
||||
}
|
||||
return douyinDirectAppManager.findFirstByChannelMchNoAndAppTypeNotTenant(
|
||||
channelMchNo, DouyinAppTypeCode.WEB_APP)
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.webAppNotFound"));
|
||||
// H5 验签需 web_app:要求该类型唯一命中,>1 抛 notUnique,==0 抛 notConfigured
|
||||
List<DouyinDirectApp> webApps = douyinDirectAppManager.listByChannelMchNoAndAppType(
|
||||
channelMchNo, DouyinAppTypeCode.WEB_APP);
|
||||
if (webApps.size() > 1) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.appNotUnique", DouyinAppTypeCode.WEB_APP);
|
||||
}
|
||||
if (webApps.size() == 1) {
|
||||
return webApps.getFirst();
|
||||
}
|
||||
// 抖音: 未配置 H5 验签所需的 web_app 类型应用
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.appNotConfigured", DouyinAppTypeCode.WEB_APP);
|
||||
}
|
||||
|
||||
/// 查询抖音直连产品支持的支付能力候选列表(含国际化名称)
|
||||
|
||||
@@ -5,6 +5,8 @@ import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManag
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectApp;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectKeyConfig;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -35,9 +37,10 @@ public class DouyinDirectConfigAssembler {
|
||||
/// @param capability 支付能力编码(用于选择匹配的应用)
|
||||
/// @return 抖音 SDK 凭证, 字段对齐子应用 DouyinSdkCredential
|
||||
public DouyinSdkCredential buildConfig(String mchNo, String channelMchNo, String capability) {
|
||||
// 1. 解析支付使用的应用(显式能力关联 > appType 推导 > 通道商户首个兜底)
|
||||
// 1. 解析支付使用的应用(显式能力关联 > appType 唯一推导;未命中报错,不兜底)
|
||||
DouyinDirectApp app = appCapabilityService.resolveApp(channelMchNo, capability)
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.douyin.mchAppNotFound"));
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.appNotConfigured", capability));
|
||||
|
||||
// 2. 读取通道商户绑定(获取抖音商户号 dyMchId 作为 mchId)
|
||||
DouyinDirectChannelMerchant merchant = channelMerchantManager.lambdaQuery()
|
||||
|
||||
@@ -4,10 +4,9 @@ import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectApp;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectAppAuthConfig;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectAppAuthConfigService;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectAppCapabilityService;
|
||||
import cn.daxpay.open.payment.auth.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.AuthSession;
|
||||
import cn.daxpay.open.payment.strategy.auth.AbsChannelAuthStrategy;
|
||||
import cn.daxpay.open.payment.strategy.auth.AuthContext;
|
||||
import cn.daxpay.open.payment.auth.core.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.core.AuthSession;
|
||||
import cn.daxpay.open.payment.auth.merchant.AbsProductAuthStrategy;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.AuthCodeParam;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthResult;
|
||||
@@ -32,7 +31,7 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DouyinDirectAuthStrategy extends AbsChannelAuthStrategy {
|
||||
public class DouyinDirectAuthStrategy extends AbsProductAuthStrategy {
|
||||
|
||||
private final DouyinH5AuthService douyinH5AuthService;
|
||||
private final DouyinDirectAppCapabilityService douyinDirectAppCapabilityService;
|
||||
@@ -63,7 +62,7 @@ public class DouyinDirectAuthStrategy extends AbsChannelAuthStrategy {
|
||||
/// 通过授权 code 换取 openId
|
||||
@Override
|
||||
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
|
||||
AuthContext ctx = resolveContext(param, session);
|
||||
var ctx = resolveContext(param, session);
|
||||
DouyinDirectApp app = douyinDirectAppCapabilityService.resolveWebAppForH5Auth(
|
||||
ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
|
||||
DouyinDirectAppAuthConfig authConfig = douyinDirectAppAuthConfigService.findByDouyinDirectAppIdForAuth(app.getId());
|
||||
|
||||
@@ -45,6 +45,16 @@ public class WechatDirectAppManager extends BaseManager<WechatDirectAppMapper, W
|
||||
.orderByAsc(WechatDirectApp::getId));
|
||||
}
|
||||
|
||||
/// 按通道商户号与应用类型查询全部应用,供应用解析做唯一性判断
|
||||
public List<WechatDirectApp> listByChannelMchNoAndAppType(String channelMchNo, String appType) {
|
||||
return lambdaQuery()
|
||||
.eq(WechatDirectApp::getChannelMchNo, channelMchNo)
|
||||
.eq(WechatDirectApp::getAppType, appType)
|
||||
.orderByAsc(WechatDirectApp::getCreateTime)
|
||||
.orderByAsc(WechatDirectApp::getId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/// 按通道商户号与wxAppId查询应用(支付/回调,租户内)
|
||||
public Optional<WechatDirectApp> findByChannelMchNoAndWxAppId(String channelMchNo, String wxAppId) {
|
||||
return firstOpt(q -> q
|
||||
|
||||
@@ -39,6 +39,15 @@ public class WechatIsvAppManager extends BaseManager<WechatIsvAppMapper, WechatI
|
||||
.orderByAsc(WechatIsvApp::getId));
|
||||
}
|
||||
|
||||
/// 按应用类型查询全部应用,供应用解析做唯一性判断
|
||||
public List<WechatIsvApp> listByAppType(String appType) {
|
||||
return lambdaQuery()
|
||||
.eq(WechatIsvApp::getAppType, appType)
|
||||
.orderByAsc(WechatIsvApp::getCreateTime)
|
||||
.orderByAsc(WechatIsvApp::getId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/// 按 wxAppId 查询应用(channelAppId显式指定认证应用时使用, 校验该appId在系统中预配过)
|
||||
public Optional<WechatIsvApp> findByWxAppId(String wxAppId) {
|
||||
return firstOpt(q -> q.eq(WechatIsvApp::getWxAppId, wxAppId));
|
||||
|
||||
@@ -102,14 +102,15 @@ public class WechatDirectAppCapabilityService {
|
||||
capabilityManager.deleteByWechatDirectAppId(wechatDirectAppId);
|
||||
}
|
||||
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导 > 通道商户首个兜底
|
||||
/// 支付/回调解析应用:显式配置 > appType 自动推导(须已装载 mchNo,租户内)
|
||||
///
|
||||
/// appType 推导要求该通道商户下该类型应用唯一命中:
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个应用:抛 appNotUnique,要求显式配置能力绑
|
||||
/// - 该类型无应用:返回 empty,由调用方走最终报错
|
||||
/// 不再"通道商户首个"兜底。
|
||||
///
|
||||
/// **须已装载 mchNo**([PaymentVerify] 的 initMch / 回调 bindMchNoForCallback),走租户过滤。
|
||||
/// 认证无上下文请用 [#resolveAppNotTenant]。
|
||||
///
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param capability 支付能力编码
|
||||
/// @return 命中的应用;三者均未命中返回 empty,由调用方兜底回退
|
||||
public Optional<WechatDirectApp> resolveApp(String channelMchNo, String capability) {
|
||||
if (StrUtil.hasBlank(channelMchNo, capability)) {
|
||||
return Optional.empty();
|
||||
@@ -119,16 +120,21 @@ public class WechatDirectAppCapabilityService {
|
||||
if (rel.isPresent()) {
|
||||
return wechatDirectAppManager.findById(rel.get().getWechatDirectAppId());
|
||||
}
|
||||
// 2. 未配置则按能力 → appType 自动推导
|
||||
// 2. appType 推导:要求该通道商户下该类型应用唯一命中,>1 拒绝猜测
|
||||
String appType = WechatAppTypeCode.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
Optional<WechatDirectApp> byType = wechatDirectAppManager.findFirstByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (byType.isPresent()) {
|
||||
return byType;
|
||||
List<WechatDirectApp> apps = wechatDirectAppManager.listByChannelMchNoAndAppType(channelMchNo, appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.wechat.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return Optional.of(apps.getFirst());
|
||||
}
|
||||
}
|
||||
// 3. 最终兜底:按通道商户号取首个应用
|
||||
return wechatDirectAppManager.findFirstByChannelMchNo(channelMchNo);
|
||||
// 3. 不再首个兜底,返回 empty
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/// 认证等无租户上下文时解析应用(忽略租户,内部复用 [#resolveApp] 逻辑)
|
||||
|
||||
@@ -95,10 +95,13 @@ public class WechatIsvAppCapabilityService {
|
||||
capabilityManager.deleteByWechatIsvAppId(wechatIsvAppId);
|
||||
}
|
||||
|
||||
/// 支付时解析当前支付能力对应的应用:显式配置 > appType自动推导 > 取首个兜底
|
||||
/// 支付时解析当前支付能力对应的应用:显式配置 > appType自动推导(要求唯一命中)
|
||||
///
|
||||
/// @param capability 支付能力编码
|
||||
/// @return 命中的应用;三者均未命中返回 empty,由调用方兜底回退
|
||||
/// appType 推导要求该类型服务商应用全局唯一命中:
|
||||
/// - 唯一命中:返回该应用
|
||||
/// - 该类型存在多个应用:抛 appNotUnique,要求显式配置能力绑
|
||||
/// - 该类型无应用:返回 empty,由调用方走最终报错
|
||||
/// 不再"首个服务商应用"兜底。
|
||||
public Optional<WechatIsvApp> resolveApp(String capability) {
|
||||
if (StrUtil.isBlank(capability)) {
|
||||
return Optional.empty();
|
||||
@@ -108,16 +111,21 @@ public class WechatIsvAppCapabilityService {
|
||||
if (rel.isPresent()) {
|
||||
return wechatIsvAppManager.findById(rel.get().getWechatIsvAppId());
|
||||
}
|
||||
// 2. 未配置则按能力 → appType 自动推导
|
||||
// 2. appType 推导:要求该类型服务商应用唯一命中,>1 拒绝猜测
|
||||
String appType = WechatAppTypeCode.resolveAppType(capability);
|
||||
if (appType != null) {
|
||||
Optional<WechatIsvApp> byType = wechatIsvAppManager.findFirstByAppType(appType);
|
||||
if (byType.isPresent()) {
|
||||
return byType;
|
||||
List<WechatIsvApp> apps = wechatIsvAppManager.listByAppType(appType);
|
||||
if (apps.size() > 1) {
|
||||
// 存在多个同类型服务商应用,请显式配置能力绑定以明确选择
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.wechat.appNotUnique", appType);
|
||||
}
|
||||
if (apps.size() == 1) {
|
||||
return Optional.of(apps.getFirst());
|
||||
}
|
||||
}
|
||||
// 3. 最终兜底:取首个服务商应用
|
||||
return wechatIsvAppManager.findFirst();
|
||||
// 3. 不再首个兜底,返回 empty
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/// 查询微信服务商产品支持的支付能力候选列表(含国际化名称)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.direct.auth;
|
||||
|
||||
import cn.daxpay.open.payment.auth.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.AuthSession;
|
||||
import cn.daxpay.open.payment.auth.core.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.core.AuthSession;
|
||||
import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager;
|
||||
import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant;
|
||||
import cn.daxpay.open.payment.strategy.auth.AbsChannelAuthStrategy;
|
||||
import cn.daxpay.open.payment.strategy.auth.AuthContext;
|
||||
import cn.daxpay.open.payment.auth.merchant.AbsProductAuthStrategy;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.AuthCodeParam;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthResult;
|
||||
@@ -34,7 +33,7 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatDirectAuthStrategy extends AbsChannelAuthStrategy {
|
||||
public class WechatDirectAuthStrategy extends AbsProductAuthStrategy {
|
||||
|
||||
private final WechatMpAuthService wechatMpAuthService;
|
||||
private final WxAppFacade wxAppFacade;
|
||||
@@ -65,7 +64,7 @@ public class WechatDirectAuthStrategy extends AbsChannelAuthStrategy {
|
||||
/// 应用上下文优先从 session(H5 会话码场景)恢复, 否则取 param(小程序直连场景)。
|
||||
@Override
|
||||
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
|
||||
AuthContext ctx = resolveContext(param, session);
|
||||
var ctx = resolveContext(param, session);
|
||||
WxAppView app = resolveApp(param.getMchNo(), ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
|
||||
WechatAuthResult data = wechatMpAuthService.getTokenAndOpenId(
|
||||
param.getAuthCode(), app.wxAppId(), app.appSecret());
|
||||
|
||||
@@ -3,12 +3,11 @@ package cn.daxpay.open.channel.wechat.strategy.isv.auth;
|
||||
import cn.daxpay.open.channel.wechat.code.WechatAuthAppTypeEnum;
|
||||
import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvChannelMerchant;
|
||||
import cn.daxpay.open.payment.auth.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.AuthSession;
|
||||
import cn.daxpay.open.payment.auth.core.AuthRedirectUri;
|
||||
import cn.daxpay.open.payment.auth.core.AuthSession;
|
||||
import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager;
|
||||
import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant;
|
||||
import cn.daxpay.open.payment.strategy.auth.AbsChannelAuthStrategy;
|
||||
import cn.daxpay.open.payment.strategy.auth.AuthContext;
|
||||
import cn.daxpay.open.payment.auth.merchant.AbsProductAuthStrategy;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.AuthCodeParam;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthResult;
|
||||
@@ -41,7 +40,7 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvAuthStrategy extends AbsChannelAuthStrategy {
|
||||
public class WechatIsvAuthStrategy extends AbsProductAuthStrategy {
|
||||
|
||||
private final WechatMpAuthService wechatMpAuthService;
|
||||
private final WxAppFacade wxAppFacade;
|
||||
@@ -68,7 +67,7 @@ public class WechatIsvAuthStrategy extends AbsChannelAuthStrategy {
|
||||
/// 通过授权 code 换取 openId
|
||||
@Override
|
||||
public AuthResult doAuth(AuthCodeParam param, AuthSession session) {
|
||||
AuthContext ctx = resolveContext(param, session);
|
||||
var ctx = resolveContext(param, session);
|
||||
ResolvedAuthApp app = resolveAuthApp(param.getMchNo(), ctx.channelMchNo(), ctx.capability(), ctx.channelAppId());
|
||||
WechatAuthResult data = wechatMpAuthService.getTokenAndOpenId(
|
||||
param.getAuthCode(), app.wxAppId(), app.appSecret());
|
||||
|
||||
Reference in New Issue
Block a user