feat(wx): 微信应用能力解析增强(WxAppFacade/Resolve/Capability)

This commit is contained in:
daxpay
2026-08-05 14:22:56 +08:00
parent 28b6f21926
commit d52df2871d
3 changed files with 68 additions and 32 deletions

View File

@@ -2,6 +2,8 @@ package cn.daxpay.open.payment.wx.facade;
import cn.daxpay.open.payment.auth.core.AppScopeEnum;
import java.util.Optional;
/// # 微信开放应用解析门面
///
/// 供微信/拉卡拉/易宝等通道统一解析 wxAppId + Secret避免依赖通道私有应用表。
@@ -16,6 +18,15 @@ public interface WxAppFacade {
/// @param product 支付产品编码PayProduct.code平台默认绑按产品隔离
WxAppView resolve(String mchNo, String channelMchNo, String capability, String channelAppId, String product);
/// 尽力解析单应用,解析不到返回 [Optional.empty] 而非抛异常
///
/// 解析顺序与 [resolve] 一致,区别仅在于未命中时不抛异常。
/// 供聚合通道(如 Adapay)在 wxAppId 可选场景使用: 通道商户未绑定微信应用时,
/// 凭证 wxAppId 留空, 由通道层决定是否上送。
///
/// @param product 支付产品编码PayProduct.code平台默认绑按产品隔离
Optional<WxAppView> resolveOptional(String mchNo, String channelMchNo, String capability, String channelAppId, String product);
/// 解析 ISV 双应用platform(sp) + optional merchant(sub)
///
/// @param product 支付产品编码PayProduct.code

View File

@@ -67,24 +67,64 @@ public class WxAppResolveService implements WxAppFacade {
/// 仅服务商(WECHAT_ISV)及其他产品允许平台档兜底。
@Override
public WxAppView resolve(String mchNo, String channelMchNo, String capability, String channelAppId, String product) {
Optional<WxAppView> resolved = this.tryResolve(mchNo, channelMchNo, capability, channelAppId, product);
if (resolved.isPresent()) {
return resolved.get();
}
// 解析失败, 按场景抛对应异常(语义与重构前完全一致)
boolean direct = ProductEnum.WECHAT_PAY.getCode().equals(product);
// 显式指定了 channelAppId 但未命中
if (StrUtil.isNotBlank(channelAppId)) {
if (direct) {
// 直连商户未配置商户档应用
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.directMchAppNotConfigured", channelAppId);
}
// 微信: 指定 AppId 未配置
throw new DataNotExistException("error.payment.wx.channelAppIdNotFound", channelAppId);
}
// 未指定 channelAppId, 能力绑定/默认绑均未命中
if (direct) {
// 微信: 直连商户未配置商户档应用
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.directMchAppNotConfigured", capability);
}
// 微信: 未配置该能力对应的应用
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.appNotConfigured", capability);
}
/// 尽力解析单应用,解析不到返回 [Optional.empty] 而非抛异常
///
/// 解析顺序与 [resolve] 完全一致, 供聚合通道(如 Adapay)在 wxAppId 可选场景使用。
@Override
public Optional<WxAppView> resolveOptional(String mchNo, String channelMchNo, String capability, String channelAppId, String product) {
return this.tryResolve(mchNo, channelMchNo, capability, channelAppId, product);
}
/// 尝试解析单应用, 解析失败返回 empty(不抛异常)
///
/// 解析顺序: 显式 channelAppId → 通道能力绑 → 产品级平台默认绑。
/// [resolve] 与 [resolveOptional] 的共享内核, 区别仅在上层是否对 empty 抛异常。
private Optional<WxAppView> tryResolve(String mchNo, String channelMchNo, String capability, String channelAppId, String product) {
boolean direct = ProductEnum.WECHAT_PAY.getCode().equals(product);
// 1. 显式 channelAppId
if (StrUtil.isNotBlank(channelAppId)) {
return this.resolveByChannelAppId(mchNo, channelAppId, direct);
return this.tryResolveByChannelAppId(mchNo, channelAppId, direct);
}
// 2. 通道能力绑定(同能力优先 merchant其次 platform
if (StrUtil.isNotBlank(channelMchNo) && StrUtil.isNotBlank(capability)) {
var merchantBind = wxChannelAppCapabilityManager.findByChannelMchNoAndCapabilityAndScope(
channelMchNo, capability, AppScopeEnum.MERCHANT.getCode());
if (merchantBind.isPresent()) {
return this.getById(AppScopeEnum.MERCHANT, merchantBind.get().getWxAppRefId());
return Optional.of(this.getById(AppScopeEnum.MERCHANT, merchantBind.get().getWxAppRefId()));
}
// 直连商户不使用平台档应用, 跳过平台能力绑定
if (!direct) {
var platformBind = wxChannelAppCapabilityManager.findByChannelMchNoAndCapabilityAndScope(
channelMchNo, capability, AppScopeEnum.PLATFORM.getCode());
if (platformBind.isPresent()) {
return this.getById(AppScopeEnum.PLATFORM, platformBind.get().getWxAppRefId());
return Optional.of(this.getById(AppScopeEnum.PLATFORM, platformBind.get().getWxAppRefId()));
}
}
}
@@ -92,16 +132,10 @@ public class WxAppResolveService implements WxAppFacade {
if (!direct) {
WxAppView productDefault = this.resolveProductDefault(product, capability);
if (productDefault != null) {
return productDefault;
return Optional.of(productDefault);
}
}
// 微信: 直连商户未配置商户档应用 / 未配置该能力对应的应用
if (direct) {
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.directMchAppNotConfigured", capability);
}
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.appNotConfigured", capability);
return Optional.empty();
}
/// 解析 ISV 双应用platform(sp) 必填 + merchant(sub) 可选
@@ -152,30 +186,21 @@ public class WxAppResolveService implements WxAppFacade {
return new WxIsvAppPair(platform, merchant);
}
/// 按 channelAppId 解析单应用
/// 按 channelAppId 尝试解析, 未命中返回 empty(不抛异常)
///
/// @param direct 是否直连产品(直连时商户表优先且不回退平台表)
private WxAppView resolveByChannelAppId(String mchNo, String channelAppId, boolean direct) {
private Optional<WxAppView> tryResolveByChannelAppId(String mchNo, String channelAppId, boolean direct) {
// 直连商户优先查商户表, 且不回退到平台表
if (direct) {
var mchApp = wxMchAppManager.findByMchNoAndWxAppId(mchNo, channelAppId);
if (mchApp.isPresent()) {
return this.toMerchantView(mchApp.get());
}
// 直连商户未配置商户档应用
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.payment.wx.directMchAppNotConfigured", channelAppId);
return wxMchAppManager.findByMchNoAndWxAppId(mchNo, channelAppId)
.map(this::toMerchantView);
}
var platform = wxPlatformAppManager.findByWxAppId(channelAppId);
if (platform.isPresent()) {
return this.toPlatformView(platform.get());
return Optional.of(this.toPlatformView(platform.get()));
}
var mchApp = wxMchAppManager.findByMchNoAndWxAppId(mchNo, channelAppId);
if (mchApp.isPresent()) {
return this.toMerchantView(mchApp.get());
}
// 微信: 指定 AppId 未配置
throw new DataNotExistException("error.payment.wx.channelAppIdNotFound", channelAppId);
return wxMchAppManager.findByMchNoAndWxAppId(mchNo, channelAppId)
.map(this::toMerchantView);
}
/// 产品级平台默认能力绑兜底:按 (product, capability) 查 wx_platform_app_capability
@@ -211,7 +236,7 @@ public class WxAppResolveService implements WxAppFacade {
/// 按真实 wxAppId 解析:商户档优先, 平台档兜底
///
/// 供开放接口认证场景: 对接方传入真实微信 AppId, 系统自行定位到对应应用。
/// 与 [resolveByChannelAppId] 的平台优先不同, 此方法商户优先(更严格安全边界)。
/// 与 [tryResolveByChannelAppId] 的平台优先不同, 此方法商户优先(更严格安全边界)。
@Override
public WxAppView resolveByWxAppId(String mchNo, String wxAppId) {
var mchApp = wxMchAppManager.findByMchNoAndWxAppId(mchNo, wxAppId);

View File

@@ -14,6 +14,7 @@ import cn.daxpay.open.payment.wx.param.platform.WxPlatformAppCapabilityBatchPara
import cn.daxpay.open.payment.wx.param.platform.WxPlatformAppCapabilityParam;
import cn.daxpay.open.payment.wx.result.WxCapabilityOption;
import cn.daxpay.open.payment.wx.result.platform.WxPlatformAppCapabilityResult;
import cn.daxpay.open.payment.wx.service.WxAppResolveService;
import cn.daxpay.open.platform.common.i18n.util.I18nUtil;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum;
@@ -29,7 +30,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -53,13 +53,13 @@ public class WxPlatformAppCapabilityService {
if (StrUtil.isBlank(product)) {
return List.of();
}
List<WxPlatformAppCapability> rels = capabilityManager.listByProduct(product);
if (rels.isEmpty()) {
List<WxPlatformAppCapability> reals = capabilityManager.listByProduct(product);
if (reals.isEmpty()) {
return List.of();
}
Map<Long, WxPlatformApp> appMap = wxPlatformAppManager.listAll().stream()
.collect(Collectors.toMap(WxPlatformApp::getId, Function.identity()));
return rels.stream()
return reals.stream()
.map(rel -> fillResult(rel, appMap.get(rel.getWxPlatformAppId())))
.toList();
}