diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/enums/DouyinPayBodyType.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/enums/DouyinPayBodyType.java index 27488e8a7..12d98ba7a 100644 --- a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/enums/DouyinPayBodyType.java +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/enums/DouyinPayBodyType.java @@ -8,6 +8,8 @@ public enum DouyinPayBodyType { QR_CODE, /// 跳转链接(H5 支付) LINK, - /// 标识符(JSAPI/APP 返回的 prepayId) + /// JSAPI 调起参数(JSON, 含 appId/timeStamp/nonceStr/package/signType/paySign) + JSAPI, + /// 标识符(APP 返回的 prepayId) IDENTIFIER } diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/service/payment/pay/DouyinPayService.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/service/payment/pay/DouyinPayService.java index 97bbf10e1..065c61f5a 100644 --- a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/service/payment/pay/DouyinPayService.java +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/service/payment/pay/DouyinPayService.java @@ -96,6 +96,7 @@ public class DouyinPayService { switch (bodyType) { case QR_CODE -> bo.setPayBodyType(PayBodyTypeEnum.QR_CODE); case LINK -> bo.setPayBodyType(PayBodyTypeEnum.LINK); + case JSAPI -> bo.setPayBodyType(PayBodyTypeEnum.JSAPI); case IDENTIFIER -> bo.setPayBodyType(PayBodyTypeEnum.IDENTIFIER); } } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/DouyinJsapiController.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/DouyinJsapiController.java new file mode 100644 index 000000000..4490ae5d5 --- /dev/null +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/DouyinJsapiController.java @@ -0,0 +1,63 @@ +package cn.daxpay.open.payment.unipay.trade.controller; + +import cn.daxpay.open.platform.capability.douyin.auth.result.DouyinJsapiConfigResult; +import cn.daxpay.open.platform.capability.douyin.auth.service.DouyinOpenTokenService; +import cn.daxpay.open.platform.core.annotation.IgnoreAuth; +import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import cn.daxpay.open.platform.system.entity.config.platform.auth.PlatformDouyinH5AuthConfig; +import cn.daxpay.open.platform.system.service.config.auth.PlatformDouyinH5AuthConfigService; +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/// # 抖音 H5 JSAPI 调起前置 - sdk.config 验签接口 +/// +/// 给前端 H5(抖音 APP webview) 调用: 前端拉起 `ttcjpay.dypay` 前必须先 `window.DouyinOpenJSBridge.config({...})` +/// 通过签名验证, 此接口返回该签名包(clientKey + timestamp + nonceStr + signature)。 +/// +/// 签名基于平台级 [PlatformDouyinH5AuthConfig](clientKey/clientSecret) 换取的 jsapi_ticket 计算, +/// 不依赖商户上下文, 故接口路径不挂 `@PaymentVerify`, 仅 `@IgnoreAuth` 跳过登录即可。 +/// +/// 算法: `MD5(jsapi_ticket={}&nonce_str={}×tamp={}&url={})`, 字典序拼接。 +/// +/// 参考文档: +/// - JS 接入指南: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/js/js-access +/// - 验证签名: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/js/signature +@IgnoreAuth +@Tag(name = "抖音 JSAPI 调起辅助") +@RestController +@RequestMapping("/unipay/assist/channel/douyin") +@RequiredArgsConstructor +public class DouyinJsapiController { + + private final PlatformDouyinH5AuthConfigService platformDouyinH5AuthConfigService; + private final DouyinOpenTokenService douyinOpenTokenService; + + @Operation(summary = "获取抖音 JS-SDK sdk.config 验签包") + @Parameter(name = "url", description = "调用 JS 接口页面的完整 URL(不含 # 及后面部分)", required = true) + @GetMapping("/jsapi-config") + public Result getJsapiConfig(@RequestParam("url") String url) { + if (StrUtil.isBlank(url)) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.douyin.jsapiConfigParamBlank"); + } + PlatformDouyinH5AuthConfig config = platformDouyinH5AuthConfigService.getDouyinH5AuthConfig(); + if (StrUtil.hasBlank(config.getClientKey(), config.getClientSecret())) { + // 抖音 H5 应用认证配置不完整, 请先在「平台配置」中配置 clientKey/clientSecret + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.h5AuthNotConfigured"); + } + DouyinJsapiConfigResult result = douyinOpenTokenService.buildJsapiConfig( + config.getClientKey(), config.getClientSecret(), url); + return Res.ok(result); + } +} diff --git a/daxpay-platform/daxpay-platform-capability/capability-douyin/pom.xml b/daxpay-platform/daxpay-platform-capability/capability-douyin/pom.xml index 3004c857a..20b863ff1 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-douyin/pom.xml +++ b/daxpay-platform/daxpay-platform-capability/capability-douyin/pom.xml @@ -36,5 +36,11 @@ hutool-json ${hutool.version} + + + cn.daxpay.open + common-redis + ${project.version} + diff --git a/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/result/DouyinJsapiConfigResult.java b/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/result/DouyinJsapiConfigResult.java new file mode 100644 index 000000000..b0f74a6a5 --- /dev/null +++ b/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/result/DouyinJsapiConfigResult.java @@ -0,0 +1,29 @@ +package cn.daxpay.open.platform.capability.douyin.auth.result; + +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 抖音 JS-SDK config 验签结果 +/// +/// 返回给前端的 `window.DouyinOpenJSBridge.config()` 必需参数包, +/// 前端拿到后直接透传给 `sdk.config({params: {...}})` 完成鉴权。 +/// +/// 参考文档: +/// - JS 接入指南: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/js/js-access +/// - 验证签名: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/js/signature +@Data +@Accessors(chain = true) +public class DouyinJsapiConfigResult { + + /// 抖音开放平台 Client Key(网站应用 appid) + private String clientKey; + + /// 时间戳(秒, 字符串) + private String timestamp; + + /// 随机字符串 + private String nonceStr; + + /// 服务端计算的 MD5 签名(基于 jsapi_ticket + nonce_str + timestamp + url) + private String signature; +} diff --git a/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/service/DouyinOpenTokenService.java b/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/service/DouyinOpenTokenService.java new file mode 100644 index 000000000..dc062fd32 --- /dev/null +++ b/daxpay-platform/daxpay-platform-capability/capability-douyin/src/main/java/cn/daxpay/open/platform/capability/douyin/auth/service/DouyinOpenTokenService.java @@ -0,0 +1,217 @@ +package cn.daxpay.open.platform.capability.douyin.auth.service; + +import cn.daxpay.open.platform.capability.douyin.auth.result.DouyinJsapiConfigResult; +import cn.daxpay.open.platform.common.redis.lock.LockExecutor; +import cn.daxpay.open.platform.common.redis.lock.TryLockResult; +import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/// # 抖音开放平台 Token / Ticket 管理服务 +/// +/// 用于 H5 JSAPI 调起前置 `sdk.config` 验签: +/// - **client_access_token**: 由 client_key + client_secret 换取, 缓存 ~7200s +/// - **jsapi_ticket**: 由 client_access_token 换取, 缓存 ~7200s +/// +/// 参考范式: [cn.daxpay.open.platform.capability.wechat.token.service.WechatTokenService] +/// +/// 参考文档: +/// - client_token: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/account-permission/client-token +/// - get_ticket: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/tools-ability/jsb-management/get-jsticket +@Slf4j +@Service +@RequiredArgsConstructor +public class DouyinOpenTokenService { + + private final RedisTemplate redisTemplate; + private final LockExecutor lockExecutor; + + /// client_token 换取地址 + private static final String CLIENT_TOKEN_URL = "https://open.douyin.com/oauth/client_token/"; + /// jsapi_ticket 换取地址(注意是 getticket 一个词, 无下划线; HTTP 方法为 GET, 不要 body) + private static final String JSAPI_TICKET_URL = "https://open.douyin.com/js/getticket/"; + + /// Redis Key 前缀 + private static final String CLIENT_TOKEN_KEY = "douyin:open:client_token:"; + private static final String TICKET_KEY = "douyin:open:jsapi_ticket:"; + private static final String CLIENT_TOKEN_LOCK_KEY = "douyin:open:client_token:lock:"; + private static final String TICKET_LOCK_KEY = "douyin:open:jsapi_ticket:lock:"; + + /// 缓存过期时间(抖音 token/ticket 有效期 7200s, 提前 5 分钟刷新) + private static final Duration CACHE_EXPIRE = Duration.ofSeconds(7200 - 300); + + /// 获取 client_access_token(自动刷新, 支持多副本部署) + public String getClientAccessToken(String clientKey, String clientSecret) { + String cacheKey = CLIENT_TOKEN_KEY + clientKey; + String token = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(token)) { + return token; + } + return refreshClientAccessToken(clientKey, clientSecret); + } + + /// 强制刷新 client_access_token(分布式锁 + 双重检查) + public String refreshClientAccessToken(String clientKey, String clientSecret) { + String lockKey = CLIENT_TOKEN_LOCK_KEY + clientKey; + String cacheKey = CLIENT_TOKEN_KEY + clientKey; + TryLockResult result = lockExecutor.tryExecute(lockKey, + 30_000L, 5_000L, () -> { + // 双重检查 + String cached = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(cached)) { + return cached; + } + log.info("刷新抖音 client_access_token, clientKey: {}", clientKey); + Map body = new HashMap<>(); + body.put("client_key", clientKey); + body.put("client_secret", clientSecret); + body.put("grant_type", "client_credential"); + String token = postJsonAndExtract(CLIENT_TOKEN_URL, body, "access_token"); + redisTemplate.opsForValue().set(cacheKey, token, CACHE_EXPIRE); + log.info("刷新抖音 client_access_token 成功, clientKey: {}", clientKey); + return token; + }); + if (!result.acquired()) { + log.warn("获取抖音 client_access_token 刷新锁失败, clientKey: {}", clientKey); + String cached = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(cached)) { + return cached; + } + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.tokenLockFailed"); + } + return result.value(); + } + + /// 获取 jsapi_ticket(自动刷新, 依赖 client_access_token) + public String getJsapiTicket(String clientKey, String clientSecret) { + String cacheKey = TICKET_KEY + clientKey; + String ticket = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(ticket)) { + return ticket; + } + return refreshJsapiTicket(clientKey, clientSecret); + } + + /// 强制刷新 jsapi_ticket(分布式锁 + 双重检查) + public String refreshJsapiTicket(String clientKey, String clientSecret) { + String lockKey = TICKET_LOCK_KEY + clientKey; + String cacheKey = TICKET_KEY + clientKey; + TryLockResult result = lockExecutor.tryExecute(lockKey, + 30_000L, 5_000L, () -> { + String cached = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(cached)) { + return cached; + } + String accessToken = getClientAccessToken(clientKey, clientSecret); + log.info("刷新抖音 jsapi_ticket, clientKey: {}", clientKey); + // getticket 是 GET + access-token header, 无 body + // 错误用 POST/带 body 会报 28001007 参数不合法 + String ticket = getWithAuth(JSAPI_TICKET_URL, accessToken, "ticket"); + redisTemplate.opsForValue().set(cacheKey, ticket, CACHE_EXPIRE); + log.info("刷新抖音 jsapi_ticket 成功, clientKey: {}", clientKey); + return ticket; + }); + if (!result.acquired()) { + log.warn("获取抖音 jsapi_ticket 刷新锁失败, clientKey: {}", clientKey); + String cached = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isNotBlank(cached)) { + return cached; + } + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.ticketLockFailed"); + } + return result.value(); + } + + /// 生成给前端的 sdk.config 验签包(MD5 signature) + /// + /// 签名拼接(字典序): `jsapi_ticket={}&nonce_str={}×tamp={}&url={}` + /// 详情参考: https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/js/signature + public DouyinJsapiConfigResult buildJsapiConfig(String clientKey, String clientSecret, String url) { + if (StrUtil.hasBlank(clientKey, clientSecret, url)) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.douyin.jsapiConfigParamBlank"); + } + String ticket = getJsapiTicket(clientKey, clientSecret); + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String nonceStr = RandomUtil.randomString(32); + + // 字典序拼接(jsapi_ticket < nonce_str < timestamp < url) + String signStr = String.format("jsapi_ticket=%s&nonce_str=%s×tamp=%s&url=%s", + ticket, nonceStr, timestamp, url); + String signature = cn.hutool.crypto.SecureUtil.md5(signStr); + + return new DouyinJsapiConfigResult() + .setClientKey(clientKey) + .setTimestamp(timestamp) + .setNonceStr(nonceStr) + .setSignature(signature); + } + + /// POST JSON 调抖音接口, 提取 data.{field} + private String postJsonAndExtract(String url, Map body, String field) { + return postJson(url, body, field); + } + + /// GET 调抖音接口(仅带 access-token header, 无 body), 提取 data.{field} + /// + /// 适用于 jsapi_ticket 等纯授权接口, 参考文档: + /// https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/tools-ability/jsb-management/get-jsticket + private String getWithAuth(String url, String accessToken, String field) { + HttpRequest request = HttpUtil.createGet(url) + .header("Content-Type", "application/json") + .header("access-token", accessToken); + return doRequestAndExtract(request, url, field); + } + + /// 统一 POST JSON 调用, 解析 `data.{field}`, 失败抛业务异常 + private String postJson(String url, Map body, String field) { + HttpRequest request = HttpUtil.createPost(url) + .header("Content-Type", "application/json"); + request.body(JSONUtil.toJsonStr(body)); + return doRequestAndExtract(request, url, field); + } + + /// 执行请求并解析 `data.{field}`, 失败抛业务异常 + private String doRequestAndExtract(HttpRequest request, String url, String field) { + String respBody; + try (HttpResponse response = request.execute()) { + respBody = response.body(); + } + log.info("抖音开放平台响应: url={}, body={}", url, respBody); + JSONObject obj = JSONUtil.parseObj(respBody); + JSONObject data = obj.getJSONObject("data"); + if (data == null) { + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.openApiFailed", obj.getStr("message", respBody)); + } + // 抖音错误码: data.error_code 非 0 视为失败 + int errorCode = data.getInt("error_code", -1); + if (errorCode != 0) { + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.openApiFailed", + data.getStr("description", "error_code=" + errorCode)); + } + String value = data.getStr(field); + if (StrUtil.isBlank(value)) { + throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, + "error.channel.douyin.openApiFailed", field + " is blank"); + } + return value; + } +} diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/douyin.json index 3b83aa82a..4d709ae08 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "Payment capability [{0}] is duplicated under this channel merchant", "payFailed": "Douyin payment error: {0}", "closeFailed": "Douyin close order error: {0}", - "notSupportMethod": "Unsupported Douyin payment method: {0}" + "notSupportMethod": "Unsupported Douyin payment method: {0}", + "openApiFailed": "Douyin OpenPlatform API call failed: {0}", + "tokenLockFailed": "Failed to acquire Douyin client_access_token refresh lock, please retry later", + "ticketLockFailed": "Failed to acquire Douyin jsapi_ticket refresh lock, please retry later", + "jsapiConfigParamBlank": "Douyin sdk.config signing parameter (clientKey/clientSecret/url) is blank", + "h5AuthNotConfigured": "Douyin H5 app auth config is incomplete, please configure clientKey/clientSecret in Platform Config first" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/douyin.json index 760635d95..4413a7016 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "Kemampuan pembayaran [{0}] diduplikasi di bawah saluran pedagang ini", "payFailed": "Kesalahan pembayaran Douyin: {0}", "closeFailed": "Kesalahan penutupan pesanan Douyin: {0}", - "notSupportMethod": "Metode pembayaran Douyin yang tidak didukung: {0}" + "notSupportMethod": "Metode pembayaran Douyin yang tidak didukung: {0}", + "openApiFailed": "Panggilan API Douyin OpenPlatform gagal: {0}", + "tokenLockFailed": "Gagal mendapatkan kunci pembaruan client_access_token Douyin, coba lagi nanti", + "ticketLockFailed": "Gagal mendapatkan kunci pembaruan jsapi_ticket Douyin, coba lagi nanti", + "jsapiConfigParamBlank": "Parameter tanda tangan sdk.config Douyin (clientKey/clientSecret/url) kosong", + "h5AuthNotConfigured": "Konfigurasi autentikasi aplikasi H5 Douyin tidak lengkap, mohon konfigurasikan clientKey/clientSecret di Konfigurasi Platform terlebih dahulu" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/douyin.json index ac730b783..cd227a9c7 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "支払い機能 [{0}] はこのチャネル販売者の下で複製されています", "payFailed": "Douyin 支払いエラー: {0}", "closeFailed": "Douyin 成約注文エラー: {0}", - "notSupportMethod": "サポートされていない Douyin 支払い方法: {0}" + "notSupportMethod": "サポートされていない Douyin 支払い方法: {0}", + "openApiFailed": "Douyin オープンプラットフォーム API 呼び出し失敗: {0}", + "tokenLockFailed": "Douyin client_access_token 更新ロックの取得に失敗しました、後でもう一度お試しください", + "ticketLockFailed": "Douyin jsapi_ticket 更新ロックの取得に失敗しました、後でもう一度お試しください", + "jsapiConfigParamBlank": "Douyin sdk.config 署名パラメータ(clientKey/clientSecret/url)が空です", + "h5AuthNotConfigured": "Douyin H5 アプリ認証設定が不完全です。先に「プラットフォーム設定」で clientKey/clientSecret を設定してください" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/douyin.json index 7620f2f8e..2e00abdf2 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "이 채널 판매자에 결제 기능 [{0}]이(가) 중복되었습니다.", "payFailed": "Douyin 결제 오류: {0}", "closeFailed": "Douyin 마감 주문 오류: {0}", - "notSupportMethod": "지원되지 않는 Douyin 결제 수단: {0}" + "notSupportMethod": "지원되지 않는 Douyin 결제 수단: {0}", + "openApiFailed": "Douyin 오픈플랫폼 API 호출 실패: {0}", + "tokenLockFailed": "Douyin client_access_token 갱신 잠금 획득에 실패했습니다, 나중에 다시 시도해 주세요", + "ticketLockFailed": "Douyin jsapi_ticket 갱신 잠금 획득에 실패했습니다, 나중에 다시 시도해 주세요", + "jsapiConfigParamBlank": "Douyin sdk.config 서명 매개변수(clientKey/clientSecret/url)가 비어 있습니다", + "h5AuthNotConfigured": "Douyin H5 앱 인증 구성이 불완전합니다. 먼저 '플랫폼 구성'에서 clientKey/clientSecret을 구성해 주세요" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/douyin.json index 3e943588c..442a87e68 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "Keupayaan pembayaran [{0}] diduakan di bawah pedagang saluran ini", "payFailed": "Ralat pembayaran Douyin: {0}", "closeFailed": "Ralat tutup pesanan Douyin: {0}", - "notSupportMethod": "Kaedah pembayaran Douyin tidak disokong: {0}" + "notSupportMethod": "Kaedah pembayaran Douyin tidak disokong: {0}", + "openApiFailed": "Panggilan API Douyin OpenPlatform gagal: {0}", + "tokenLockFailed": "Gagal mendapatkan kunci pembaharuan client_access_token Douyin, sila cuba lagi kemudian", + "ticketLockFailed": "Gagal mendapatkan kunci pembaharuan jsapi_ticket Douyin, sila cuba lagi kemudian", + "jsapiConfigParamBlank": "Parameter tandatangan sdk.config Douyin (clientKey/clientSecret/url) kosong", + "h5AuthNotConfigured": "Konfigurasi pengesahan aplikasi H5 Douyin tidak lengkap, sila konfigurasikan clientKey/clientSecret dalam Konfigurasi Platform terlebih dahulu" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/douyin.json index 11a2b6331..efb95421e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "ความสามารถในการชำระเงิน [{0}] ซ้ำกันภายใต้ผู้ขายช่องทางนี้", "payFailed": "ข้อผิดพลาดในการชำระเงิน Douyin: {0}", "closeFailed": "ข้อผิดพลาดในการสั่งซื้อปิด Douyin: {0}", - "notSupportMethod": "วิธีการชำระเงิน Douyin ที่ไม่รองรับ: {0}" + "notSupportMethod": "วิธีการชำระเงิน Douyin ที่ไม่รองรับ: {0}", + "openApiFailed": "การเรียก API แพลตฟอร์มเปิด Douyin ล้มเหลว: {0}", + "tokenLockFailed": "ไม่สามารถรับล็อกการรีเฟรช client_access_token ของ Douyin ได้ โปรดลองอีกครั้งในภายหลัง", + "ticketLockFailed": "ไม่สามารถรับล็อกการรีเฟรช jsapi_ticket ของ Douyin ได้ โปรดลองอีกครั้งในภายหลัง", + "jsapiConfigParamBlank": "พารามิเตอร์ลายเซ็น sdk.config ของ Douyin (clientKey/clientSecret/url) ว่างเปล่า", + "h5AuthNotConfigured": "การกำหนดค่าการตรวจสอบสิทธิ์แอป H5 Douyin ไม่สมบูรณ์ โปรดกำหนดค่า clientKey/clientSecret ในการกำหนดค่าแพลตฟอร์มก่อน" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/douyin.json index 4cc51359e..59933c8ab 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "Khả năng thanh toán [{0}] bị trùng lặp trong kênh này của người bán", "payFailed": "Lỗi thanh toán Douyin: {0}", "closeFailed": "Lỗi đóng lệnh Douyin: {0}", - "notSupportMethod": "Phương thức thanh toán Douyin không được hỗ trợ: {0}" + "notSupportMethod": "Phương thức thanh toán Douyin không được hỗ trợ: {0}", + "openApiFailed": "Gọi API Nền tảng mở Douyin không thành công: {0}", + "tokenLockFailed": "Không thể lấy khóa làm mới client_access_token Douyin, vui lòng thử lại sau", + "ticketLockFailed": "Không thể lấy khóa làm mới jsapi_ticket Douyin, vui lòng thử lại sau", + "jsapiConfigParamBlank": "Tham số ký sdk.config Douyin (clientKey/clientSecret/url) bị trống", + "h5AuthNotConfigured": "Cấu hình xác thực ứng dụng H5 Douyin không đầy đủ, vui lòng cấu hình clientKey/clientSecret trong Cấu hình nền tảng trước" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/douyin.json index 9a5a5bb1e..206acb997 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "同一通道商户下支付能力[{0}]重复配置", "payFailed": "抖音支付异常: {0}", "closeFailed": "抖音关闭订单异常: {0}", - "notSupportMethod": "暂不支持的抖音支付方式: {0}" + "notSupportMethod": "暂不支持的抖音支付方式: {0}", + "openApiFailed": "抖音开放平台接口调用失败: {0}", + "tokenLockFailed": "获取抖音 client_access_token 刷新锁失败, 请稍后重试", + "ticketLockFailed": "获取抖音 jsapi_ticket 刷新锁失败, 请稍后重试", + "jsapiConfigParamBlank": "抖音 sdk.config 验签参数(clientKey/clientSecret/url)存在空值", + "h5AuthNotConfigured": "抖音 H5 应用认证配置不完整, 请先在「平台配置」中配置 clientKey/clientSecret" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/douyin.json index 6ec2fa48e..51de5de6e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "同一通道商户下支付能力[{0}]重複配置", "payFailed": "抖音支付異常: {0}", "closeFailed": "抖音關閉訂單異常: {0}", - "notSupportMethod": "暫不支持的抖音支付方式: {0}" + "notSupportMethod": "暫不支持的抖音支付方式: {0}", + "openApiFailed": "抖音開放平台介面呼叫失敗: {0}", + "tokenLockFailed": "取得抖音 client_access_token 更新鎖失敗, 請稍後重試", + "ticketLockFailed": "取得抖音 jsapi_ticket 更新鎖失敗, 請稍後重試", + "jsapiConfigParamBlank": "抖音 sdk.config 驗簽參數(clientKey/clientSecret/url)存在空值", + "h5AuthNotConfigured": "抖音 H5 應用認證配置不完整, 請先在「平台配置」中配置 clientKey/clientSecret" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/douyin.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/douyin.json index e3621f7bf..d0ee40d7e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/douyin.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/douyin.json @@ -6,5 +6,10 @@ "capabilityDuplicate": "同一通道商戶下支付能力[{0}]重複配置", "payFailed": "抖音支付異常: {0}", "closeFailed": "抖音關閉訂單異常: {0}", - "notSupportMethod": "暫不支援的抖音支付方式: {0}" + "notSupportMethod": "暫不支援的抖音支付方式: {0}", + "openApiFailed": "抖音開放平台介面呼叫失敗: {0}", + "tokenLockFailed": "取得抖音 client_access_token 更新鎖失敗, 請稍後重試", + "ticketLockFailed": "取得抖音 jsapi_ticket 更新鎖失敗, 請稍後重試", + "jsapiConfigParamBlank": "抖音 sdk.config 驗簽參數(clientKey/clientSecret/url)存在空值", + "h5AuthNotConfigured": "抖音 H5 應用認證配置不完整, 請先在「平台配置」中配置 clientKey/clientSecret" }