mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 07:25:39 +08:00
feat(gateway): 网关/码牌鉴权与开发调试能力增强
补充网关授权 URL、聚合支付元数据、码牌辅助与订单状态查询;运营端 DevelopGateway 调试接口。
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package cn.daxpay.open.payment.admin.controller.develop;
|
||||
|
||||
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.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("/admin/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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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.unipay.param.gateway.GatewayPrePayParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 网关支付开发调试服务
|
||||
///
|
||||
/// 仅提供组参辅助与签名能力, **不发起真实交易**。
|
||||
/// 真实预下单由管理端调试页模拟商户 HTTP 请求调用 `/unipay/gateway/pre-pay` 完成,
|
||||
/// 与正式对接走同一入口, 避免 admin 内部直调支付核心形成后门。
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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.enums.unipay.ChannelAuthTypeEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -33,7 +34,8 @@ public class ChannelAuthFacade {
|
||||
/// 生成授权链接: 支付宝走平台级 OAuth, 其余按支付产品走通道策略
|
||||
public AuthUrlResult generateAuthUrl(GenerateAuthUrlParam param) {
|
||||
if (isAlipayAuth(param.getAuthType())) {
|
||||
return platformAuthService.generateAlipayAuthUrl();
|
||||
// 透传 returnPath, 供码牌等业务页授权完成后回跳
|
||||
return platformAuthService.generateAlipayAuthUrl(param.getReturnPath());
|
||||
}
|
||||
return channelAuthService.generateAuthUrl(param);
|
||||
}
|
||||
@@ -42,6 +44,11 @@ public class ChannelAuthFacade {
|
||||
public AuthResult auth(AuthCodeParam param) {
|
||||
AuthSession session = authSessionStore.loadSession(param.getAuthToken());
|
||||
AuthResult result = doAuth(param, session);
|
||||
// 平台级 auth 方法未回填 returnPath 时, 从会话补齐
|
||||
if (session != null && StrUtil.isNotBlank(session.getReturnPath())
|
||||
&& StrUtil.isBlank(result.getReturnPath())) {
|
||||
result.setReturnPath(session.getReturnPath());
|
||||
}
|
||||
// 成功后失效 authToken, 避免 TTL 内重复消费会话上下文
|
||||
authSessionStore.deleteSession(param.getAuthToken());
|
||||
return result;
|
||||
|
||||
@@ -83,11 +83,19 @@ public class PlatformAuthService {
|
||||
/// 回调指向固定的 `/auth/alipay`。会话标识 authToken 通过 OAuth state 参数透传, 回调后从 state 恢复会话。
|
||||
/// session 标记 `source=platform_alipay`, 认证分发层据此走平台级支付宝授权回调分支([#authAlipay])。
|
||||
public AuthUrlResult generateAlipayAuthUrl() {
|
||||
return generateAlipayAuthUrl(null);
|
||||
}
|
||||
|
||||
/// 生成支付宝授权链接, 可携带授权完成后前端回跳路径
|
||||
///
|
||||
/// @param returnPath 授权完成后前端业务回跳路径(如 `/cashier/{orderNo}/alipay`), 可空
|
||||
public AuthUrlResult generateAlipayAuthUrl(String returnPath) {
|
||||
String authToken = IdUtil.fastSimpleUUID();
|
||||
String queryCode = RandomUtil.randomString(10);
|
||||
AuthSession session = new AuthSession()
|
||||
.setSource(AuthSession.SOURCE_PLATFORM_ALIPAY)
|
||||
.setQueryCode(queryCode);
|
||||
.setQueryCode(queryCode)
|
||||
.setReturnPath(returnPath);
|
||||
authSessionStore.saveSession(authToken, session);
|
||||
String authUrl = buildAlipayAuthUrl(authToken);
|
||||
authSessionStore.saveWaitingResult(queryCode);
|
||||
@@ -120,6 +128,13 @@ public class PlatformAuthService {
|
||||
/// 回调指向固定的 `/auth/wechat`。会话标识 authToken 通过 OAuth state 参数透传, 回调后从 state 恢复会话。
|
||||
/// session 标记 `source=platform_mp`, 认证分发层据此走平台级微信授权回调分支([#authWechatMp])。
|
||||
public AuthUrlResult generateWechatMpAuthUrl() {
|
||||
return generateWechatMpAuthUrl(null);
|
||||
}
|
||||
|
||||
/// 生成微信公众号授权链接, 可携带授权完成后前端回跳路径
|
||||
///
|
||||
/// @param returnPath 授权完成后前端业务回跳路径(如 `/cashier/{orderNo}/wechat`), 可空
|
||||
public AuthUrlResult generateWechatMpAuthUrl(String returnPath) {
|
||||
PlatformWechatMpAuthConfig config = platformWechatMpAuthConfigService.getWechatMpAuthConfig();
|
||||
if (!isWechatMpConfigured(config)) {
|
||||
// 微信: 平台级微信公众号配置不完整, 请先在「平台配置」中配置
|
||||
@@ -134,7 +149,8 @@ public class PlatformAuthService {
|
||||
String queryCode = RandomUtil.randomString(10);
|
||||
AuthSession session = new AuthSession()
|
||||
.setSource(AuthSession.SOURCE_PLATFORM_MP)
|
||||
.setQueryCode(queryCode);
|
||||
.setQueryCode(queryCode)
|
||||
.setReturnPath(returnPath);
|
||||
authSessionStore.saveSession(authToken, session);
|
||||
// redirect_uri 为固定路径, authToken 通过 OAuth state 透传
|
||||
String redirectUri = StrUtil.removeSuffix(gatewayBase, "/") + WECHAT_AUTH_PATH;
|
||||
@@ -150,6 +166,13 @@ public class PlatformAuthService {
|
||||
/// 会话标识 authToken 通过 state 参数透传, 回调后从 state 恢复会话。
|
||||
/// session 标记 `source=platform_douyin`, 认证分发层据此走平台级抖音授权回调分支([#authDouyin])。
|
||||
public AuthUrlResult generateDouyinAuthUrl() {
|
||||
return generateDouyinAuthUrl(null);
|
||||
}
|
||||
|
||||
/// 生成抖音 H5 静默授权链接, 可携带授权完成后前端回跳路径
|
||||
///
|
||||
/// @param returnPath 授权完成后前端业务回跳路径, 可空
|
||||
public AuthUrlResult generateDouyinAuthUrl(String returnPath) {
|
||||
PlatformDouyinH5AuthConfig config = platformDouyinH5AuthConfigService.getDouyinH5AuthConfig();
|
||||
if (!isDouyinH5Configured(config)) {
|
||||
// 抖音: 平台级抖音 H5 应用配置不完整, 请先在「三方平台管理」中配置
|
||||
@@ -164,7 +187,8 @@ public class PlatformAuthService {
|
||||
String queryCode = RandomUtil.randomString(10);
|
||||
AuthSession session = new AuthSession()
|
||||
.setSource(AuthSession.SOURCE_PLATFORM_DOUYIN)
|
||||
.setQueryCode(queryCode);
|
||||
.setQueryCode(queryCode)
|
||||
.setReturnPath(returnPath);
|
||||
authSessionStore.saveSession(authToken, session);
|
||||
// redirect_uri 为固定路径(需与抖音开放平台配置完全一致), authToken 通过 state 透传
|
||||
String redirectUri = StrUtil.removeSuffix(gatewayBase, "/") + DOUYIN_AUTH_PATH;
|
||||
@@ -194,6 +218,8 @@ public class PlatformAuthService {
|
||||
.setUserId(userId)
|
||||
.setAccessToken(alipayResult.getAccessToken())
|
||||
.setStatus(ChannelAuthStatusEnum.SUCCESS.getCode());
|
||||
// 回填业务回跳路径, 供前端跳回收银台/聚合等页面
|
||||
fillReturnPath(authResult, session);
|
||||
authSessionStore.writeResultByQueryCode(param.getQueryCode(), session, authResult);
|
||||
return authResult;
|
||||
}
|
||||
@@ -214,6 +240,7 @@ public class PlatformAuthService {
|
||||
.setOpenId(data.getOpenId())
|
||||
.setAccessToken(data.getAccessToken())
|
||||
.setStatus(ChannelAuthStatusEnum.SUCCESS.getCode());
|
||||
fillReturnPath(authResult, session);
|
||||
authSessionStore.writeResultByQueryCode(param.getQueryCode(), session, authResult);
|
||||
return authResult;
|
||||
}
|
||||
@@ -240,6 +267,7 @@ public class PlatformAuthService {
|
||||
.setOpenId(data.getOpenId())
|
||||
.setAccessToken(data.getAccessToken())
|
||||
.setStatus(ChannelAuthStatusEnum.SUCCESS.getCode());
|
||||
fillReturnPath(authResult, session);
|
||||
authSessionStore.writeResultByQueryCode(param.getQueryCode(), session, authResult);
|
||||
return authResult;
|
||||
}
|
||||
@@ -248,4 +276,11 @@ public class PlatformAuthService {
|
||||
private boolean isDouyinH5Configured(PlatformDouyinH5AuthConfig config) {
|
||||
return StrUtil.isNotBlank(config.getClientKey()) && StrUtil.isNotBlank(config.getClientSecret());
|
||||
}
|
||||
|
||||
/// 将会话中的 returnPath 回填到认证结果
|
||||
private void fillReturnPath(AuthResult authResult, AuthSession session) {
|
||||
if (session != null && StrUtil.isNotBlank(session.getReturnPath())) {
|
||||
authResult.setReturnPath(session.getReturnPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.daxpay.open.payment.trade.order.dao;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.query.generator.QueryGenerator;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.daxpay.open.platform.core.annotation.IgnoreTenant;
|
||||
import cn.daxpay.open.platform.core.rest.param.PageParam;
|
||||
import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.payment.trade.order.param.NormalPayOrderQuery;
|
||||
@@ -24,6 +25,14 @@ public class NormalPayOrderManager extends BaseManager<NormalPayOrderMapper, Nor
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 根据平台业务单号查询(忽略租户, H5 码牌订单状态轮询用)
|
||||
@IgnoreTenant
|
||||
public Optional<NormalPayOrder> findByOrderNoNotTenant(String orderNo) {
|
||||
return lambdaQuery()
|
||||
.eq(NormalPayOrder::getOrderNo, orderNo)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 根据业务单号查询(按商户号自动租户隔离)
|
||||
public Optional<NormalPayOrder> findByBizOrderNo(String bizOrderNo) {
|
||||
return lambdaQuery()
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package cn.daxpay.open.payment.trade.runtime.service.pay.gateway;
|
||||
|
||||
import cn.daxpay.open.payment.merchant.dao.gateway.GatewayAggregateConfigManager;
|
||||
import cn.daxpay.open.payment.merchant.entity.gateway.GatewayAggregateConfig;
|
||||
import cn.daxpay.open.payment.merchant.enums.ClientEnvEnum;
|
||||
import cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum;
|
||||
import cn.daxpay.open.payment.merchant.service.gateway.ClientEnvPayResolveService;
|
||||
import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum;
|
||||
import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder;
|
||||
import cn.daxpay.open.payment.unipay.param.gateway.AggregateQrPayParam;
|
||||
import cn.daxpay.open.payment.unipay.result.gateway.AggregatePayMetaResult;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import cn.daxpay.open.platform.common.spring.util.WebServletUtil;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -16,18 +20,48 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/// # 聚合扫码支付服务
|
||||
///
|
||||
/// 按应用聚合配置的深度(level)解析支付方式, 委托 [ClientEnvPayResolveService] 与码牌共用解析。
|
||||
/// 按应用聚合配置的深度(level)解析支付方式, 委托 [ClientEnvPayResolveService]。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AggregatePayService {
|
||||
|
||||
/// 需要买家标识(openId/userId)的支付方式
|
||||
private static final Set<String> METHODS_NEED_OPEN_ID = Set.of(
|
||||
PayMethodEnum.WECHAT_JSAPI.getCode(),
|
||||
PayMethodEnum.WECHAT_MINI.getCode(),
|
||||
PayMethodEnum.ALIPAY_JSAPI.getCode(),
|
||||
PayMethodEnum.UNION_JSAPI.getCode(),
|
||||
PayMethodEnum.DOUYIN_JSAPI.getCode()
|
||||
);
|
||||
|
||||
private final GatewayPayAssistService gatewayPayAssistService;
|
||||
private final ClientEnvPayResolveService clientEnvPayResolveService;
|
||||
private final GatewayPayHandleService gatewayPayHandleService;
|
||||
private final GatewayAggregateConfigManager aggregateConfigManager;
|
||||
|
||||
/// H5 聚合元数据: autoLaunch / needOpenId(不下发敏感路由字段)
|
||||
public AggregatePayMetaResult getMeta(String orderNo, String clientEnvCode, String runtimeCode) {
|
||||
GatewayPayOrder order = gatewayPayAssistService.getOrderAndCheck(orderNo);
|
||||
if (!Objects.equals(order.getGatewayType(), GatewayPayTypeEnum.AGGREGATE.getCode())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.gateway.typeMismatch");
|
||||
}
|
||||
ClientEnvEnum clientEnv = ClientEnvEnum.findByCode(clientEnvCode);
|
||||
ClientRuntimeEnum runtime = ClientRuntimeEnum.ofOrDefault(runtimeCode);
|
||||
var resolved = clientEnvPayResolveService.resolveRequired(order.getAppId(), clientEnv, runtime);
|
||||
|
||||
GatewayAggregateConfig config = aggregateConfigManager.findByAppId(order.getAppId())
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.error.gateway.aggregateConfigMissing"));
|
||||
|
||||
return new AggregatePayMetaResult()
|
||||
.setAutoLaunch(Boolean.TRUE.equals(config.getAutoLaunch()))
|
||||
.setNeedOpenId(METHODS_NEED_OPEN_ID.contains(resolved.method()));
|
||||
}
|
||||
|
||||
/// 聚合扫码发起支付
|
||||
public NormalPayResult aggregateQrPay(AggregateQrPayParam param) {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.daxpay.open.payment.trade.runtime.service.pay.gateway;
|
||||
|
||||
import cn.daxpay.open.payment.auth.PlatformAuthService;
|
||||
import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder;
|
||||
import cn.daxpay.open.payment.unipay.param.gateway.GatewayAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.unipay.ChannelAuthTypeEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 网关 H5 授权服务
|
||||
///
|
||||
/// 公开端(无商户签名)根据网关订单生成 OAuth 链接, 用于收银台/聚合页取 openId。
|
||||
/// 安全约束:
|
||||
/// - 订单必须存在且可支付([GatewayPayAssistService#getOrderAndCheck])
|
||||
/// - returnPath 仅允许站内业务相对路径, 防止开放重定向
|
||||
///
|
||||
/// 一期使用平台级认证配置(与调试工具同源); 通道级按支付项解析可二期扩展。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GatewayAuthService {
|
||||
|
||||
private final GatewayPayAssistService gatewayPayAssistService;
|
||||
private final PlatformAuthService platformAuthService;
|
||||
|
||||
/// 生成授权链接
|
||||
public AuthUrlResult generateAuthUrl(GatewayAuthUrlParam param) {
|
||||
// 校验订单可支付并装载商户上下文
|
||||
GatewayPayOrder order = gatewayPayAssistService.getOrderAndCheck(param.getOrderNo());
|
||||
String returnPath = sanitizeReturnPath(param.getReturnPath(), order.getOrderNo());
|
||||
ChannelAuthTypeEnum authType = ChannelAuthTypeEnum.findByCode(param.getAuthType());
|
||||
return switch (authType) {
|
||||
case WECHAT -> platformAuthService.generateWechatMpAuthUrl(returnPath);
|
||||
case ALIPAY -> platformAuthService.generateAlipayAuthUrl(returnPath);
|
||||
case DOUYIN -> platformAuthService.generateDouyinAuthUrl(returnPath);
|
||||
default -> throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.error.gateway.clientEnvNotSupport");
|
||||
};
|
||||
}
|
||||
|
||||
/// 校验并规范化 returnPath: 必须是以 / 开头的相对路径, 禁止协议/外链/反斜杠
|
||||
private String sanitizeReturnPath(String returnPath, String orderNo) {
|
||||
if (StrUtil.isBlank(returnPath)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"validation.field.returnPath.notBlank");
|
||||
}
|
||||
String path = returnPath.trim();
|
||||
// 禁止绝对 URL / 协议相对 / 反斜杠逃逸
|
||||
if (!path.startsWith("/")
|
||||
|| path.startsWith("//")
|
||||
|| path.contains("://")
|
||||
|| path.contains("\\")
|
||||
|| path.contains("@")) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.error.gateway.returnPathInvalid");
|
||||
}
|
||||
// 仅允许网关业务落地前缀, 且路径中应含本订单号(防跨单串跳)
|
||||
boolean allowedPrefix = path.startsWith("/cashier/")
|
||||
|| path.startsWith("/aggregate/")
|
||||
|| path.startsWith("/h/");
|
||||
if (!allowedPrefix || !path.contains(orderNo)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"pay.error.gateway.returnPathInvalid");
|
||||
}
|
||||
// 去掉 hash, 保留 query
|
||||
int hash = path.indexOf('#');
|
||||
if (hash >= 0) {
|
||||
path = path.substring(0, hash);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.daxpay.open.payment.unipay.param.device;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/// # 码牌生成授权链接参数
|
||||
@Data
|
||||
@Schema(title = "码牌生成授权链接参数")
|
||||
public class CodePayAuthUrlParam {
|
||||
|
||||
/// 码牌编码
|
||||
@Schema(description = "码牌编码")
|
||||
@NotBlank(message = "{validation.field.code.notBlank}")
|
||||
@Size(max = 64, message = "{validation.field.code.size}")
|
||||
private String code;
|
||||
|
||||
/// 客户端环境
|
||||
/// @see cn.daxpay.open.payment.merchant.enums.ClientEnvEnum
|
||||
@Schema(description = "客户端环境(wechat/alipay/union_pay/douyin)")
|
||||
@NotBlank(message = "{validation.field.clientEnv.notBlank}")
|
||||
@Size(max = 32, message = "{validation.field.clientEnv.size}")
|
||||
private String clientEnv;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.daxpay.open.payment.unipay.param.gateway;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/// # 网关 H5 生成授权链接参数(无商户签名)
|
||||
///
|
||||
/// 凭网关订单装载商户上下文后生成 OAuth 链接; 用于收银台/聚合等落地页取 openId。
|
||||
/// 当前一期走平台级认证配置(微信公众号/支付宝/抖音 H5); 通道级(按支付项 DIRECT)可后续扩展。
|
||||
@Data
|
||||
@Schema(title = "网关授权链接参数")
|
||||
public class GatewayAuthUrlParam {
|
||||
|
||||
@Schema(description = "平台网关单号")
|
||||
@NotBlank(message = "{validation.field.orderNo.notBlank}")
|
||||
@Size(max = 64, message = "{validation.field.orderNo.size}")
|
||||
private String orderNo;
|
||||
|
||||
/// 认证类型: wechat / alipay / douyin(与 ChannelAuthTypeEnum / ClientEnv 对齐)
|
||||
@Schema(description = "认证类型 wechat/alipay/douyin")
|
||||
@NotBlank(message = "{validation.field.authType.notBlank}")
|
||||
@Size(max = 32, message = "{validation.field.authType.size}")
|
||||
private String authType;
|
||||
|
||||
/// 授权完成后前端回跳路径, 须为站内相对路径(如 /cashier/{orderNo}/wechat)
|
||||
@Schema(description = "授权完成后回跳路径")
|
||||
@NotBlank(message = "{validation.field.returnPath.notBlank}")
|
||||
@Size(max = 200, message = "{validation.field.returnPath.size}")
|
||||
private String returnPath;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.daxpay.open.payment.unipay.result.gateway;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 聚合扫码 H5 元数据(公开字段)
|
||||
///
|
||||
/// 仅下发前端决策所需信息, 不下发 method/通道商户等敏感路由字段。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "聚合扫码元数据")
|
||||
public class AggregatePayMetaResult {
|
||||
|
||||
/// 是否自动拉起支付
|
||||
@Schema(description = "是否自动拉起支付")
|
||||
private Boolean autoLaunch;
|
||||
|
||||
/// 当前环境解析出的支付方式是否需要 openId
|
||||
@Schema(description = "是否需要先完成 OAuth 获取 openId")
|
||||
private Boolean needOpenId;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.daxpay.open.payment.unipay.client.controller;
|
||||
|
||||
import cn.daxpay.open.payment.unipay.client.result.CodePayInfoResult;
|
||||
import cn.daxpay.open.payment.unipay.client.result.CodePayOrderStatusResult;
|
||||
import cn.daxpay.open.payment.unipay.client.service.CodePayAssistService;
|
||||
import cn.daxpay.open.payment.unipay.param.device.CodePayAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.param.device.CodePayParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import cn.daxpay.open.platform.core.annotation.IgnoreAuth;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
@@ -16,6 +19,7 @@ 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;
|
||||
|
||||
/// # 码牌支付(公开/H5/小程序侧)
|
||||
@@ -33,8 +37,10 @@ public class DeviceQrCodeClientController {
|
||||
|
||||
@Operation(summary = "根据码牌编码查询支付信息")
|
||||
@GetMapping("/get-by-code")
|
||||
public Result<CodePayInfoResult> getByCode(@NotBlank(message = "{validation.field.code.notBlank}") String code) {
|
||||
return Res.ok(codePayAssistService.getByCode(code));
|
||||
public Result<CodePayInfoResult> getByCode(
|
||||
@NotBlank(message = "{validation.field.code.notBlank}") String code,
|
||||
@RequestParam(required = false) String clientEnv) {
|
||||
return Res.ok(codePayAssistService.getByCode(code, clientEnv));
|
||||
}
|
||||
|
||||
@Operation(summary = "码牌发起支付")
|
||||
@@ -42,4 +48,17 @@ public class DeviceQrCodeClientController {
|
||||
public Result<NormalPayResult> pay(@RequestBody @Validated CodePayParam param) {
|
||||
return Res.ok(codePayAssistService.pay(param));
|
||||
}
|
||||
|
||||
@Operation(summary = "码牌生成 OAuth 授权链接")
|
||||
@PostMapping("/generate-auth-url")
|
||||
public Result<AuthUrlResult> generateAuthUrl(@RequestBody @Validated CodePayAuthUrlParam param) {
|
||||
return Res.ok(codePayAssistService.generateAuthUrl(param));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询码牌订单状态")
|
||||
@GetMapping("/order-status")
|
||||
public Result<CodePayOrderStatusResult> orderStatus(
|
||||
@NotBlank(message = "{validation.field.orderNo.notBlank}") String orderNo) {
|
||||
return Res.ok(codePayAssistService.orderStatus(orderNo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package cn.daxpay.open.payment.unipay.client.controller;
|
||||
|
||||
import cn.daxpay.open.payment.trade.runtime.service.pay.gateway.AggregatePayService;
|
||||
import cn.daxpay.open.payment.trade.runtime.service.pay.gateway.CashierPayService;
|
||||
import cn.daxpay.open.payment.trade.runtime.service.pay.gateway.GatewayAuthService;
|
||||
import cn.daxpay.open.payment.trade.runtime.service.pay.gateway.GatewayOrderQueryService;
|
||||
import cn.daxpay.open.payment.unipay.param.gateway.AggregateQrPayParam;
|
||||
import cn.daxpay.open.payment.unipay.param.gateway.CashierPayParam;
|
||||
import cn.daxpay.open.payment.unipay.param.gateway.GatewayAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
|
||||
import cn.daxpay.open.payment.unipay.result.gateway.AggregatePayMetaResult;
|
||||
import cn.daxpay.open.payment.unipay.result.gateway.CashierItemPublicResult;
|
||||
import cn.daxpay.open.payment.unipay.result.gateway.GatewayOrderResult;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
@@ -35,6 +39,7 @@ public class GatewayClientController {
|
||||
private final GatewayOrderQueryService gatewayOrderQueryService;
|
||||
private final AggregatePayService aggregatePayService;
|
||||
private final CashierPayService cashierPayService;
|
||||
private final GatewayAuthService gatewayAuthService;
|
||||
|
||||
@Operation(summary = "查询网关订单摘要")
|
||||
@GetMapping("/order")
|
||||
@@ -43,6 +48,15 @@ public class GatewayClientController {
|
||||
return Res.ok(gatewayOrderQueryService.queryByOrderNoNotTenant(orderNo));
|
||||
}
|
||||
|
||||
@Operation(summary = "聚合扫码元数据(autoLaunch/needOpenId)")
|
||||
@GetMapping("/aggregate/meta")
|
||||
public Result<AggregatePayMetaResult> aggregateMeta(
|
||||
@NotBlank(message = "{validation.field.orderNo.notBlank}") String orderNo,
|
||||
@NotBlank(message = "{validation.field.clientEnv.notBlank}") String clientEnv,
|
||||
String runtime) {
|
||||
return Res.ok(aggregatePayService.getMeta(orderNo, clientEnv, runtime));
|
||||
}
|
||||
|
||||
@Operation(summary = "聚合扫码发起支付")
|
||||
@PostMapping("/aggregate/pay")
|
||||
public Result<NormalPayResult> aggregatePay(@RequestBody @Validated AggregateQrPayParam param) {
|
||||
@@ -63,4 +77,10 @@ public class GatewayClientController {
|
||||
public Result<NormalPayResult> cashierPay(@RequestBody @Validated CashierPayParam param) {
|
||||
return Res.ok(cashierPayService.pay(param));
|
||||
}
|
||||
|
||||
@Operation(summary = "网关 H5 生成授权链接(取 openId, 无商户签名)")
|
||||
@PostMapping("/auth/generate-url")
|
||||
public Result<AuthUrlResult> generateAuthUrl(@RequestBody @Validated GatewayAuthUrlParam param) {
|
||||
return Res.ok(gatewayAuthService.generateAuthUrl(param));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,5 +29,9 @@ public class CodePayInfoResult {
|
||||
/// @see cn.daxpay.open.payment.device.enums.QrCodeProgramTypeEnum
|
||||
@Schema(description = "落地程序类型(h5/mini_app)")
|
||||
private String programType;
|
||||
|
||||
/// 是否需要 openId(传入 clientEnv 时按策略 method 判定; 未传 clientEnv 时为 null)
|
||||
@Schema(description = "是否需要 openId")
|
||||
private Boolean needOpenId;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.daxpay.open.payment.unipay.client.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 码牌订单状态(公开脱敏)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "码牌订单状态")
|
||||
public class CodePayOrderStatusResult {
|
||||
|
||||
@Schema(description = "平台业务单号")
|
||||
private String orderNo;
|
||||
|
||||
/// @see cn.daxpay.open.payment.trade.enums.NormalPayOrderStatusEnum
|
||||
@Schema(description = "订单状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "金额(分)")
|
||||
private Long amount;
|
||||
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.daxpay.open.payment.unipay.client.service;
|
||||
|
||||
import cn.daxpay.open.payment.auth.ChannelAuthFacade;
|
||||
import cn.daxpay.open.payment.common.access.MerchantAccessInfo;
|
||||
import cn.daxpay.open.payment.common.context.MerchantContextLoader;
|
||||
import cn.daxpay.open.payment.device.enums.QrCodeAmountTypeEnum;
|
||||
@@ -11,15 +12,25 @@ import cn.daxpay.open.payment.merchant.enums.ClientRuntimeEnum;
|
||||
import cn.daxpay.open.payment.merchant.enums.CodePayFormEnum;
|
||||
import cn.daxpay.open.payment.merchant.service.access.MerchantAccessQueryService;
|
||||
import cn.daxpay.open.payment.merchant.service.gateway.CodePayResolveService;
|
||||
import cn.daxpay.open.payment.route.service.runtime.PayRouteService;
|
||||
import cn.daxpay.open.payment.trade.order.dao.NormalPayOrderManager;
|
||||
import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.payment.trade.runtime.service.pay.normal.NormalPayService;
|
||||
import cn.daxpay.open.payment.unipay.client.result.CodePayInfoResult;
|
||||
import cn.daxpay.open.payment.unipay.client.result.CodePayOrderStatusResult;
|
||||
import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.param.device.CodePayAuthUrlParam;
|
||||
import cn.daxpay.open.payment.unipay.param.device.CodePayParam;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import cn.daxpay.open.platform.common.spring.util.WebServletUtil;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.trade.TradeSourceEnum;
|
||||
import cn.daxpay.open.platform.core.enums.unipay.ChannelAuthTypeEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.daxpay.open.platform.core.exception.operation.OperationFailException;
|
||||
@@ -31,8 +42,10 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 码牌支付编排(公开/H5/小程序侧)
|
||||
///
|
||||
/// - 查询: 按编码返回脱敏码牌信息(含 programType)
|
||||
/// - 查询: 按编码返回脱敏码牌信息(含 programType / needOpenId)
|
||||
/// - 授权: 按码牌解析商户与策略后生成 OAuth 链接, returnPath 指向分端页
|
||||
/// - 支付: 读**码牌支付策略**解析 method(不读聚合配置); payForm 由 programType 映射
|
||||
/// - 状态: 按 orderNo 查询 cashier_code 来源订单脱敏状态
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -43,20 +56,36 @@ public class CodePayAssistService {
|
||||
private final MerchantContextLoader merchantContextLoader;
|
||||
private final CodePayResolveService codePayResolveService;
|
||||
private final NormalPayService normalPayService;
|
||||
private final PayRouteService payRouteService;
|
||||
private final ChannelAuthFacade channelAuthFacade;
|
||||
private final NormalPayOrderManager normalPayOrderManager;
|
||||
|
||||
/// 根据码牌编码查询支付信息(公开接口, 脱敏返回)
|
||||
public CodePayInfoResult getByCode(String code) {
|
||||
///
|
||||
/// @param clientEnv 可选; 传入时解析策略 method 并填充 needOpenId
|
||||
public CodePayInfoResult getByCode(String code, String clientEnv) {
|
||||
DeviceQrCode entity = this.loadEnabledAssigned(code);
|
||||
MerchantAccessInfo merchant = merchantAccessQueryService.getMerchantByMchNo(entity.getMchNo());
|
||||
if (merchant == null) {
|
||||
throw new DataNotExistException("error.device.qrcode.mchNotFound");
|
||||
}
|
||||
return new CodePayInfoResult()
|
||||
CodePayInfoResult result = new CodePayInfoResult()
|
||||
.setCode(entity.getCode())
|
||||
.setName(entity.getName())
|
||||
.setAmountType(entity.getAmountType())
|
||||
.setFixedAmount(entity.getFixedAmount())
|
||||
.setProgramType(entity.getProgramType());
|
||||
if (StrUtil.isNotBlank(clientEnv)) {
|
||||
try {
|
||||
String method = this.resolveMethod(entity, clientEnv);
|
||||
result.setNeedOpenId(this.needOpenId(method));
|
||||
} catch (Exception e) {
|
||||
// 策略未配置等: 默认 JSAPI 路径需要 openId, 避免前端跳过授权
|
||||
log.warn("码牌 needOpenId 解析失败 code={} clientEnv={}: {}", code, clientEnv, e.getMessage());
|
||||
result.setNeedOpenId(true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 码牌发起支付: 普通订单 + source=cashier_code; 策略仅读码牌配置
|
||||
@@ -98,6 +127,62 @@ public class CodePayAssistService {
|
||||
return normalPayService.pay(payParam);
|
||||
}
|
||||
|
||||
/// 生成码牌 OAuth 授权链接(公开, 按码牌解析商户/策略/路由)
|
||||
public AuthUrlResult generateAuthUrl(CodePayAuthUrlParam param) {
|
||||
DeviceQrCode entity = this.loadEnabledAssigned(param.getCode());
|
||||
merchantContextLoader.initMch(entity.getMchNo());
|
||||
var mchApp = merchantContextLoader.resolveApp(entity.getMchNo(), entity.getAppId());
|
||||
|
||||
ClientEnvEnum clientEnv = ClientEnvEnum.findByCode(param.getClientEnv());
|
||||
if (clientEnv == ClientEnvEnum.BROWSER) {
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.clientEnvNotSupport");
|
||||
}
|
||||
CodePayFormEnum payForm = CodePayFormEnum.fromProgramType(entity.getProgramType());
|
||||
var resolved = codePayResolveService.resolveRequired(mchApp.getAppId(), clientEnv, payForm);
|
||||
|
||||
// 跟随支付同路径路由, 拿到 product/channelMchNo/capability 供通道认证
|
||||
NormalPayParam routeParam = new NormalPayParam();
|
||||
routeParam.setMchNo(entity.getMchNo());
|
||||
routeParam.setAppId(mchApp.getAppId());
|
||||
routeParam.setMethod(resolved.method());
|
||||
routeParam.setChannelMchNo(resolved.channelMchNo());
|
||||
routeParam.setCapability(resolved.capability());
|
||||
payRouteService.resolve(routeParam);
|
||||
|
||||
String returnPath = this.buildReturnPath(clientEnv, entity.getCode());
|
||||
GenerateAuthUrlParam authParam = new GenerateAuthUrlParam();
|
||||
authParam.setMchNo(entity.getMchNo());
|
||||
authParam.setAppId(mchApp.getAppId());
|
||||
authParam.setProduct(routeParam.getProduct());
|
||||
authParam.setChannelMchNo(routeParam.getChannelMchNo());
|
||||
authParam.setCapability(routeParam.getCapability());
|
||||
authParam.setReturnPath(returnPath);
|
||||
authParam.setAuthType(this.mapAuthType(clientEnv));
|
||||
// channel: 由产品反推
|
||||
if (StrUtil.isNotBlank(routeParam.getProduct())) {
|
||||
authParam.setChannel(ProductEnum.findByCode(routeParam.getProduct()).getChannel());
|
||||
}
|
||||
return channelAuthFacade.generateAuthUrl(authParam);
|
||||
}
|
||||
|
||||
/// 查询码牌订单状态(忽略租户; 仅 cashier_code 来源)
|
||||
public CodePayOrderStatusResult orderStatus(String orderNo) {
|
||||
if (StrUtil.isBlank(orderNo)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "validation.field.orderNo.notBlank");
|
||||
}
|
||||
NormalPayOrder order = normalPayOrderManager.findByOrderNoNotTenant(orderNo)
|
||||
.orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist"));
|
||||
if (!TradeSourceEnum.CASHIER_CODE.getCode().equals(order.getSource())) {
|
||||
// 非码牌订单, 视为不存在防越权探测
|
||||
throw new DataNotExistException("pay.error.payOrderNotExist");
|
||||
}
|
||||
return new CodePayOrderStatusResult()
|
||||
.setOrderNo(order.getOrderNo())
|
||||
.setStatus(order.getStatus())
|
||||
.setAmount(order.getAmount())
|
||||
.setTitle(order.getTitle());
|
||||
}
|
||||
|
||||
/// 请求 runtime 非空时须与 programType 映射的 payForm 一致
|
||||
private void validateRuntimeMatchesPayForm(String runtime, CodePayFormEnum payForm) {
|
||||
if (StrUtil.isBlank(runtime)) {
|
||||
@@ -143,4 +228,53 @@ public class CodePayAssistService {
|
||||
}
|
||||
return requestAmount;
|
||||
}
|
||||
|
||||
/// 解析码牌策略 method(供 needOpenId)
|
||||
private String resolveMethod(DeviceQrCode entity, String clientEnvCode) {
|
||||
ClientEnvEnum clientEnv = ClientEnvEnum.findByCode(clientEnvCode);
|
||||
if (clientEnv == ClientEnvEnum.BROWSER) {
|
||||
throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.clientEnvNotSupport");
|
||||
}
|
||||
merchantContextLoader.initMch(entity.getMchNo());
|
||||
var mchApp = merchantContextLoader.resolveApp(entity.getMchNo(), entity.getAppId());
|
||||
CodePayFormEnum payForm = CodePayFormEnum.fromProgramType(entity.getProgramType());
|
||||
return codePayResolveService.resolveRequired(mchApp.getAppId(), clientEnv, payForm).method();
|
||||
}
|
||||
|
||||
/// method 是否需要用户标识(JSAPI/小程序类)
|
||||
private boolean needOpenId(String methodCode) {
|
||||
if (StrUtil.isBlank(methodCode)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
PayMethodEnum method = PayMethodEnum.findByCode(methodCode);
|
||||
return switch (method) {
|
||||
case WECHAT_JSAPI, WECHAT_MINI, ALIPAY_JSAPI, UNION_JSAPI, DOUYIN_JSAPI -> true;
|
||||
default -> false;
|
||||
};
|
||||
} catch (Exception e) {
|
||||
// 未知 method: 含 jsapi/mini 视作需要
|
||||
return methodCode.contains("jsapi") || methodCode.contains("mini");
|
||||
}
|
||||
}
|
||||
|
||||
/// 分端页 returnPath: /h/wechat|{alipay}/:code?authed=1
|
||||
private String buildReturnPath(ClientEnvEnum clientEnv, String code) {
|
||||
String segment = switch (clientEnv) {
|
||||
case WECHAT -> "wechat";
|
||||
case ALIPAY -> "alipay";
|
||||
case UNION_PAY -> "union-pay";
|
||||
case DOUYIN -> "douyin";
|
||||
default -> throw new OperationFailException(CommonCode.FAIL_CODE, "error.device.qrcode.clientEnvNotSupport");
|
||||
};
|
||||
return "/h/" + segment + "/" + code + "?authed=1";
|
||||
}
|
||||
|
||||
private String mapAuthType(ClientEnvEnum clientEnv) {
|
||||
return switch (clientEnv) {
|
||||
case ALIPAY -> ChannelAuthTypeEnum.ALIPAY.getCode();
|
||||
case WECHAT -> ChannelAuthTypeEnum.WECHAT.getCode();
|
||||
default -> ChannelAuthTypeEnum.WECHAT.getCode();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user