diff --git a/apps/backend/src/routes/admin/platform-config/kuaishou-industry.ts b/apps/backend/src/routes/admin/platform-config/kuaishou-industry.ts index d86c5f03..f9e0dc9f 100644 --- a/apps/backend/src/routes/admin/platform-config/kuaishou-industry.ts +++ b/apps/backend/src/routes/admin/platform-config/kuaishou-industry.ts @@ -1,11 +1,13 @@ import { Router } from 'express' import { + exchangeAdminKuaishouIndustryAuthorizationCode, getAdminKuaishouIndustrySourceConfig, refreshAdminKuaishouIndustryAccessToken, updateAdminKuaishouIndustrySourceConfig, } from '../../../services/admin/platform-config/kuaishou-industry-service.js' import type { + AdminKuaishouIndustryAuthorizationCodeRouteBody, AdminKuaishouIndustrySourceConfigRouteBody, } from '../../../types/admin/route-inputs.js' import { createJsonHandler } from '../session.js' @@ -75,4 +77,33 @@ router.post( }), ) +router.post( + '/kuaishou-industry-source/exchange-code', + createJsonHandler( + (req) => + exchangeAdminKuaishouIndustryAuthorizationCode( + req.body as AdminKuaishouIndustryAuthorizationCodeRouteBody, + ), + { + successMessage: '快手行业电子凭证授权 token 已保存', + errorMessage: '换取快手行业电子凭证授权 token 失败', + scope: '[admin/platform-config/kuaishou-industry-source/exchange-code]', + audit: (_req, data) => { + const result = data as JsonRecord + return { + action: 'platform_kuaishou_industry_authorization_code_exchanged', + targetType: 'platform_config', + targetId: 'kuaishou_industry_source', + data: { + refreshed: Boolean(result.refreshed), + accessTokenExpiresAt: String(result.source?.accessTokenExpiresAt || '').trim(), + refreshTokenExpiresAt: String(result.source?.refreshTokenExpiresAt || '').trim(), + accessTokenStatus: String(result.source?.accessTokenStatus || '').trim(), + }, + } + }, + }, + ), +) + export default router diff --git a/apps/backend/src/services/admin/platform-config/kuaishou-industry-service.ts b/apps/backend/src/services/admin/platform-config/kuaishou-industry-service.ts index 1203d538..e5929b34 100644 --- a/apps/backend/src/services/admin/platform-config/kuaishou-industry-service.ts +++ b/apps/backend/src/services/admin/platform-config/kuaishou-industry-service.ts @@ -1,11 +1,15 @@ import { maskSecret } from '../../../utils/masking.js' +import { createHttpError } from '../../../utils/http.js' import { getKuaishouIndustrySourceConfig, getKuaishouIndustrySourceFilePath, saveKuaishouIndustrySourceConfig, type KuaishouIndustrySourceConfig, } from '../../platforms/kuaishou-industry/source-config-service.js' -import { refreshKuaishouIndustryAccessToken } from '../../platforms/kuaishou-industry/token-service.js' +import { + exchangeKuaishouIndustryAuthorizationCode, + refreshKuaishouIndustryAccessToken, +} from '../../platforms/kuaishou-industry/token-service.js' type JsonObject = Record @@ -36,7 +40,12 @@ export function updateAdminKuaishouIndustrySourceConfig(payload: JsonObject = {} : current.sendCallbackEnabled === true, baseUrl: readConfigString(payload, 'baseUrl', current.baseUrl), authBaseUrl: readConfigString(payload, 'authBaseUrl', current.authBaseUrl), + redirectUri: readConfigString(payload, 'redirectUri', current.redirectUri, { allowBlank: true }), + scopes: normalizeScopeText(readConfigString(payload, 'scopes', current.scopes, { allowBlank: true })), + authState: readConfigString(payload, 'authState', current.authState, { allowBlank: true }), appKey: readConfigString(payload, 'appKey', current.appKey, { allowBlank: true }), + openId: readConfigString(payload, 'openId', current.openId, { allowBlank: true }), + grantedScopes: normalizeScopeText(readConfigString(payload, 'grantedScopes', current.grantedScopes, { allowBlank: true })), sellerId: readConfigString(payload, 'sellerId', current.sellerId, { allowBlank: true }), provider: readConfigString(payload, 'provider', current.provider), platform: readConfigString(payload, 'platform', current.platform), @@ -64,6 +73,24 @@ export async function refreshAdminKuaishouIndustryAccessToken() { } } +export async function exchangeAdminKuaishouIndustryAuthorizationCode(payload: JsonObject = {}) { + const code = String(payload.code || '').trim() + if (!code) { + throw createHttpError('授权 code 未填写', { + statusCode: 400, + errorCode: 'kuaishou_industry_missing_authorization_code', + }) + } + + const result = await exchangeKuaishouIndustryAuthorizationCode(code) + + return { + filePath: getKuaishouIndustrySourceFilePath(), + refreshed: result.refreshed, + source: mapAdminKuaishouIndustrySourceConfig(result.config), + } +} + function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConfig) { const accessTokenStatus = resolveAccessTokenStatus(config) @@ -72,6 +99,10 @@ function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConf sendCallbackEnabled: config.sendCallbackEnabled === true, baseUrl: config.baseUrl, authBaseUrl: config.authBaseUrl, + redirectUri: config.redirectUri, + scopes: config.scopes, + authState: config.authState, + authorizationUrl: buildKuaishouIndustryAuthorizationUrl(config), appKey: config.appKey, appSecret: '', appSecretMasked: maskSecret(config.appSecret), @@ -92,6 +123,8 @@ function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConf refreshTokenExpiresAt: config.refreshTokenExpiresAt, accessTokenStatus: accessTokenStatus.status, accessTokenExpiresInSeconds: accessTokenStatus.expiresInSeconds, + openId: config.openId, + grantedScopes: config.grantedScopes, sellerId: config.sellerId, provider: config.provider, platform: config.platform, @@ -103,6 +136,35 @@ function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConf } } +function buildKuaishouIndustryAuthorizationUrl(config: KuaishouIndustrySourceConfig): string { + if (!config.authBaseUrl || !config.appKey || !config.redirectUri || !config.scopes) { + return '' + } + + try { + const url = new URL(`${String(config.authBaseUrl).replace(/\/+$/, '')}/oauth/authorize`) + url.searchParams.set('app_id', config.appKey) + url.searchParams.set('redirect_uri', config.redirectUri) + url.searchParams.set('scope', normalizeScopeText(config.scopes)) + url.searchParams.set('response_type', 'code') + if (config.authState) { + url.searchParams.set('state', config.authState) + } + + return url.toString() + } catch { + return '' + } +} + +function normalizeScopeText(value: unknown): string { + return String(value || '') + .split(/[,\s]+/) + .map((item) => item.trim()) + .filter(Boolean) + .join(',') +} + function resolveSecretPatch( payload: JsonObject, current: KuaishouIndustrySourceConfig, diff --git a/apps/backend/src/services/platforms/kuaishou-industry/config.ts b/apps/backend/src/services/platforms/kuaishou-industry/config.ts index f541c8bf..fbf119be 100644 --- a/apps/backend/src/services/platforms/kuaishou-industry/config.ts +++ b/apps/backend/src/services/platforms/kuaishou-industry/config.ts @@ -17,6 +17,9 @@ export function getKuaishouIndustryConfig(overrides: Partial const ACCESS_TOKEN_REFRESH_MARGIN_MS = 30 * 60 * 1000 const DEFAULT_ACCESS_TOKEN_TTL_MS = 47 * 60 * 60 * 1000 +const DEFAULT_REFRESH_TOKEN_TTL_MS = 179 * 24 * 60 * 60 * 1000 export type KuaishouIndustryAccessTokenResult = { accessToken: string @@ -43,40 +44,23 @@ export async function refreshKuaishouIndustryAccessToken( ): Promise { assertRefreshConfig(config) - const authBaseUrl = String(config.authBaseUrl || '').trim().replace(/\/+$/, '') - const url = new URL(`${authBaseUrl}/oauth2/refresh_token`) - url.searchParams.set('app_id', config.appKey) - url.searchParams.set('app_secret', config.appSecret) - url.searchParams.set('grant_type', 'refresh_token') - url.searchParams.set('refresh_token', config.refreshToken) + const params = new URLSearchParams() + params.set('app_id', config.appKey) + params.set('app_secret', config.appSecret) + params.set('grant_type', 'refresh_token') + params.set('refresh_token', config.refreshToken) try { const startedAt = Date.now() - const response = await fetch(url) - const text = await response.text() - const json = parseJsonObject(text) - const tokenPayload = normalizeTokenRefreshResponse(json, config) - - if (!response.ok || !tokenPayload.accessToken) { - const message = resolveTokenRefreshErrorMessage(json, response.status) - patchKuaishouIndustrySourceConfig({ - lastRefreshError: message, - }) - throw createHttpError(message, { - statusCode: 502, - errorCode: 'kuaishou_industry_access_token_refresh_failed', - }) - } - - const saved = patchKuaishouIndustrySourceConfig({ - accessToken: tokenPayload.accessToken, - refreshToken: tokenPayload.refreshToken || config.refreshToken, - accessTokenExpiresAt: tokenPayload.accessTokenExpiresAt, - refreshTokenExpiresAt: tokenPayload.refreshTokenExpiresAt || config.refreshTokenExpiresAt, - sellerId: tokenPayload.sellerId || config.sellerId, - lastRefreshedAt: new Date().toISOString(), - lastRefreshError: '', + const tokenPayload = await requestKuaishouIndustryToken({ + config, + path: '/oauth2/refresh_token', + params, + method: 'POST', + failureMessage: '快手 accessToken 刷新失败', + errorCode: 'kuaishou_industry_access_token_refresh_failed', }) + const saved = saveTokenPayload(tokenPayload, config) logInfo('[kuaishou-industry/token]', 'accessToken 刷新成功', { durationMs: Date.now() - startedAt, @@ -100,6 +84,52 @@ export async function refreshKuaishouIndustryAccessToken( } } +export async function exchangeKuaishouIndustryAuthorizationCode( + code: string, + config: KuaishouIndustrySourceConfig = getKuaishouIndustrySourceConfig(), +): Promise { + assertAuthorizationCodeConfig(config, code) + + const params = new URLSearchParams() + params.set('app_id', config.appKey) + params.set('app_secret', config.appSecret) + params.set('grant_type', 'code') + params.set('code', code.trim()) + + try { + const startedAt = Date.now() + const tokenPayload = await requestKuaishouIndustryToken({ + config, + path: '/oauth2/access_token', + params, + method: 'GET', + failureMessage: '快手授权码换取 accessToken 失败', + errorCode: 'kuaishou_industry_authorization_code_exchange_failed', + }) + const saved = saveTokenPayload(tokenPayload, config) + + logInfo('[kuaishou-industry/token]', '授权码换取 accessToken 成功', { + durationMs: Date.now() - startedAt, + accessTokenExpiresAt: saved.accessTokenExpiresAt || '', + refreshTokenExpiresAt: saved.refreshTokenExpiresAt || '', + hasRefreshToken: Boolean(saved.refreshToken), + }) + + return { + accessToken: saved.accessToken, + refreshed: true, + config: saved, + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + patchKuaishouIndustrySourceConfig({ + lastRefreshError: message, + }) + logWarn('[kuaishou-industry/token]', '授权码换取 accessToken 失败', resolveRefreshErrorDetail(error)) + throw error + } +} + export function shouldRefreshAccessToken(config: KuaishouIndustrySourceConfig): boolean { if (!String(config.accessToken || '').trim()) { return true @@ -114,10 +144,10 @@ export function shouldRefreshAccessToken(config: KuaishouIndustrySourceConfig): } function assertRefreshConfig(config: KuaishouIndustrySourceConfig) { - if (!config.authBaseUrl) { - throw createHttpError('快手开放平台授权地址未配置', { + if (!config.baseUrl) { + throw createHttpError('快手开放平台 API 地址未配置', { statusCode: 400, - errorCode: 'kuaishou_industry_missing_auth_base_url', + errorCode: 'kuaishou_industry_missing_base_url', }) } @@ -136,7 +166,92 @@ function assertRefreshConfig(config: KuaishouIndustrySourceConfig) { } } -function normalizeTokenRefreshResponse( +function assertAuthorizationCodeConfig(config: KuaishouIndustrySourceConfig, code: string) { + if (!config.baseUrl) { + throw createHttpError('快手开放平台 API 地址未配置', { + statusCode: 400, + errorCode: 'kuaishou_industry_missing_base_url', + }) + } + + if (!config.appKey || !config.appSecret) { + throw createHttpError('快手 appKey/appSecret 未配置,无法换取 accessToken', { + statusCode: 400, + errorCode: 'kuaishou_industry_missing_oauth_credential', + }) + } + + if (!String(code || '').trim()) { + throw createHttpError('授权 code 未填写,无法换取 accessToken', { + statusCode: 400, + errorCode: 'kuaishou_industry_missing_authorization_code', + }) + } +} + +async function requestKuaishouIndustryToken({ + config, + path, + params, + method, + failureMessage, + errorCode, +}: { + config: KuaishouIndustrySourceConfig + path: string + params: URLSearchParams + method: 'GET' | 'POST' + failureMessage: string + errorCode: string +}) { + const baseUrl = String(config.baseUrl || '').trim().replace(/\/+$/, '') + const url = new URL(`${baseUrl}${path}`) + const response = method === 'GET' + ? await fetch(appendSearchParams(url, params)) + : await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + }) + const text = await response.text() + const json = parseJsonObject(text) + const tokenPayload = normalizeTokenResponse(json, config) + + if (!response.ok || !isTokenResponseSuccess(json) || !tokenPayload.accessToken) { + const message = resolveTokenErrorMessage(json, response.status, failureMessage) + patchKuaishouIndustrySourceConfig({ + lastRefreshError: message, + }) + throw createHttpError(message, { + statusCode: 502, + errorCode, + }) + } + + return tokenPayload +} + +function saveTokenPayload( + tokenPayload: ReturnType, + config: KuaishouIndustrySourceConfig, +) { + const nextRefreshToken = tokenPayload.refreshToken || config.refreshToken + + return patchKuaishouIndustrySourceConfig({ + accessToken: tokenPayload.accessToken, + refreshToken: nextRefreshToken, + accessTokenExpiresAt: tokenPayload.accessTokenExpiresAt, + refreshTokenExpiresAt: tokenPayload.refreshTokenExpiresAt + || (tokenPayload.refreshToken ? new Date(Date.now() + DEFAULT_REFRESH_TOKEN_TTL_MS).toISOString() : config.refreshTokenExpiresAt), + sellerId: tokenPayload.sellerId || config.sellerId, + openId: tokenPayload.openId || config.openId, + grantedScopes: tokenPayload.grantedScopes || config.grantedScopes, + lastRefreshedAt: new Date().toISOString(), + lastRefreshError: '', + }) +} + +function normalizeTokenResponse( json: JsonObject, currentConfig: KuaishouIndustrySourceConfig, ) { @@ -152,11 +267,15 @@ function normalizeTokenRefreshResponse( 'refreshTokenValue', ]) const sellerId = pickFirstString(payload, ['seller_id', 'sellerId']) + const openId = pickFirstString(payload, ['open_id', 'openId']) + const grantedScopes = pickScopes(payload) return { accessToken, refreshToken, sellerId, + openId, + grantedScopes, accessTokenExpiresAt: resolveExpiresAt(payload, [ 'access_token_expires_at', 'accessTokenExpiresAt', @@ -176,10 +295,18 @@ function normalizeTokenRefreshResponse( ], [ 'refresh_token_expires_in', 'refreshTokenExpiresIn', - ], 0) || currentConfig.refreshTokenExpiresAt, + ], 0), } } +function appendSearchParams(url: URL, params: URLSearchParams): URL { + for (const [key, value] of params.entries()) { + url.searchParams.set(key, value) + } + + return url +} + function resolveExpiresAt( payload: JsonObject, dateKeys: string[], @@ -218,7 +345,17 @@ function parseTimestampLike(value: unknown): number { return Date.parse(String(value || '').trim()) } -function resolveTokenRefreshErrorMessage(json: JsonObject, status: number): string { +function isTokenResponseSuccess(json: JsonObject): boolean { + const payload = isPlainObject(json.data) ? json.data : json + const result = payload.result ?? json.result + if (result === undefined || result === null || result === '') { + return true + } + + return Number(result) === 1 || result === true || String(result).toLowerCase() === 'true' +} + +function resolveTokenErrorMessage(json: JsonObject, status: number, fallback: string): string { const payload = isPlainObject(json.data) ? json.data : json return pickFirstString(payload, [ 'error_msg', @@ -227,7 +364,7 @@ function resolveTokenRefreshErrorMessage(json: JsonObject, status: number): stri 'msg', 'error_description', 'errorDescription', - ]) || `快手 accessToken 刷新失败,HTTP ${status}` + ]) || `${fallback},HTTP ${status}` } function parseJsonObject(text: string): JsonObject { @@ -250,6 +387,15 @@ function pickFirstString(payload: JsonObject, keys: string[]): string { return '' } +function pickScopes(payload: JsonObject): string { + const value = payload.scopes ?? payload.scope + if (Array.isArray(value)) { + return value.map((item) => String(item || '').trim()).filter(Boolean).join(',') + } + + return String(value || '').trim() +} + function resolveRefreshErrorDetail(error: unknown): JsonObject { const detail: JsonObject = { error: error instanceof Error ? error.message : String(error), diff --git a/apps/backend/src/types/admin/route-inputs.ts b/apps/backend/src/types/admin/route-inputs.ts index 669e5fab..dce6dbae 100644 --- a/apps/backend/src/types/admin/route-inputs.ts +++ b/apps/backend/src/types/admin/route-inputs.ts @@ -12,6 +12,7 @@ import type { AdminCloudtentaclesSkuBuyInput, AdminCloudtentaclesSkuUseInput, AdminCloudtentaclesSourceConfigInput, + AdminKuaishouIndustryAuthorizationCodeInput, AdminKuaishouIndustrySourceConfigInput, AdminCloudtentaclesTestLoginInput, AdminCloudtentaclesValidateSessionInput, @@ -33,6 +34,7 @@ export type AdminRouteAdminSession = AdminViewerSessionInput export type AdminKuaishouEticketSourceConfigRouteBody = AdminKuaishouEticketSourceConfigInput export type AdminKuaishouIndustrySourceConfigRouteBody = AdminKuaishouIndustrySourceConfigInput +export type AdminKuaishouIndustryAuthorizationCodeRouteBody = AdminKuaishouIndustryAuthorizationCodeInput export type AdminNotificationConfigRouteBody = AdminNotificationConfigInput export type AdminNotificationTestRouteBody = AdminNotificationTestInput export type AdminScheduledJobsConfigRouteBody = AdminScheduledJobsConfigInput diff --git a/apps/backend/src/types/admin/write-inputs.ts b/apps/backend/src/types/admin/write-inputs.ts index 5fc19740..59672db4 100644 --- a/apps/backend/src/types/admin/write-inputs.ts +++ b/apps/backend/src/types/admin/write-inputs.ts @@ -17,6 +17,9 @@ export type AdminKuaishouIndustrySourceConfigInput = { sendCallbackEnabled?: boolean baseUrl?: string authBaseUrl?: string + redirectUri?: string + scopes?: string + authState?: string appKey?: string appSecret?: string signSecret?: string @@ -25,6 +28,8 @@ export type AdminKuaishouIndustrySourceConfigInput = { refreshToken?: string accessTokenExpiresAt?: string refreshTokenExpiresAt?: string + openId?: string + grantedScopes?: string sellerId?: string provider?: string platform?: string @@ -33,6 +38,10 @@ export type AdminKuaishouIndustrySourceConfigInput = { version?: string } +export type AdminKuaishouIndustryAuthorizationCodeInput = { + code?: string +} + export type AdminNotificationBarkRecipientInput = { id?: string name?: string diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx index a6737a73..3383f81b 100644 --- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx +++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx @@ -1,6 +1,8 @@ import { CheckCircleOutlined, + CopyOutlined, DeleteOutlined, + LinkOutlined, PlusOutlined, ReloadOutlined, SaveOutlined, @@ -32,6 +34,7 @@ import PageHeader from '@/components/admin/PageHeader' import { isFeedbackDismissed, showError, showPrompt, showSuccess } from '@/lib/feedback' import { deleteAdminCloudtentaclesSource, + exchangeAdminKuaishouIndustryAuthorizationCode, failAdminNinetyoneOrder, fetchAdminCloudtentaclesAsset, fetchAdminCloudtentaclesSkuList, @@ -1069,7 +1072,10 @@ function KuaishouIndustryPanel({ }) { const [saving, setSaving] = useState(false) const [refreshing, setRefreshing] = useState(false) + const [exchanging, setExchanging] = useState(false) + const [authorizationCode, setAuthorizationCode] = useState('') const source = config.source + const authorizationUrl = buildIndustryAuthorizationUrl(source) async function saveConfig() { setSaving(true) @@ -1099,6 +1105,51 @@ function KuaishouIndustryPanel({ } } + async function exchangeAuthorizationCode() { + const code = authorizationCode.trim() + if (!code) { + showError('请输入授权 code') + return + } + + setExchanging(true) + try { + const saved = await saveAdminKuaishouIndustrySourceConfig(source) + onChange(saved.data) + const response = await exchangeAdminKuaishouIndustryAuthorizationCode({ code }) + onChange(response.data) + setAuthorizationCode('') + showSuccess('授权 token 已保存') + } catch (error) { + showError(error instanceof Error ? error.message : '授权 code 换 token 失败') + } finally { + setExchanging(false) + } + } + + async function copyAuthorizationUrl() { + if (!authorizationUrl) { + showError('授权链接未生成') + return + } + + try { + await navigator.clipboard.writeText(authorizationUrl) + showSuccess('授权链接已复制') + } catch (error) { + showError(error instanceof Error ? error.message : '复制授权链接失败') + } + } + + function openAuthorizationUrl() { + if (!authorizationUrl) { + showError('授权链接未生成') + return + } + + window.open(authorizationUrl, '_blank', 'noopener,noreferrer') + } + function updateSource(patch: Partial) { onChange({ ...config, @@ -1170,7 +1221,7 @@ function KuaishouIndustryPanel({ + + + } + > +
+ updateSource({ redirectUri })} + /> + updateSource({ scopes })} + /> + updateSource({ authState })} + /> +
+ 授权链接 + +
+
+ openId + updateSource({ openId: event.target.value })} + /> +
+
+ 已授权 scope + updateSource({ grantedScopes: event.target.value })} + /> +
+
+ + setAuthorizationCode(event.target.value)} + onPressEnter={exchangeAuthorizationCode} + /> + + + +
{renderSecretInput('accessToken', 'accessToken', source.accessTokenMasked)} @@ -1738,6 +1857,35 @@ function formatIndustryTokenCountdown(value: number | null) { return `${minutes} 分钟后过期` } +function buildIndustryAuthorizationUrl(source: AdminKuaishouIndustrySourceConfig) { + if (!source.authBaseUrl || !source.appKey || !source.redirectUri || !source.scopes) { + return source.authorizationUrl || '' + } + + try { + const url = new URL(`${source.authBaseUrl.replace(/\/+$/, '')}/oauth/authorize`) + url.searchParams.set('app_id', source.appKey) + url.searchParams.set('redirect_uri', source.redirectUri) + url.searchParams.set('scope', normalizeIndustryScopeText(source.scopes)) + url.searchParams.set('response_type', 'code') + if (source.authState) { + url.searchParams.set('state', source.authState) + } + + return url.toString() + } catch { + return source.authorizationUrl || '' + } +} + +function normalizeIndustryScopeText(value: string) { + return value + .split(/[,\s]+/) + .map((item) => item.trim()) + .filter(Boolean) + .join(',') +} + function MetricCard({ label, value, detail }: { label: string; value: string; detail?: string }) { return ( diff --git a/apps/frontend/src/services/admin/platform-config/kuaishou-industry.ts b/apps/frontend/src/services/admin/platform-config/kuaishou-industry.ts index af300e2d..52480ba4 100644 --- a/apps/frontend/src/services/admin/platform-config/kuaishou-industry.ts +++ b/apps/frontend/src/services/admin/platform-config/kuaishou-industry.ts @@ -1,6 +1,8 @@ import { apiGet, apiPost } from '@/lib/http' import type { + AdminKuaishouIndustryAuthorizationCodePayload, AdminKuaishouIndustryConfigResponse, + AdminKuaishouIndustryExchangeCodeResponse, AdminKuaishouIndustryRefreshTokenResponse, AdminKuaishouIndustrySourceConfig, } from '@/types/admin' @@ -26,3 +28,12 @@ export function refreshAdminKuaishouIndustryAccessToken() { {}, ) } + +export function exchangeAdminKuaishouIndustryAuthorizationCode( + payload: AdminKuaishouIndustryAuthorizationCodePayload, +) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-industry-source/exchange-code', + payload, + ) +} diff --git a/apps/frontend/src/types/admin/index.ts b/apps/frontend/src/types/admin/index.ts index fec9a362..7197f8f3 100644 --- a/apps/frontend/src/types/admin/index.ts +++ b/apps/frontend/src/types/admin/index.ts @@ -54,6 +54,8 @@ export type { AdminKuaishouIndustrySourceConfig, AdminKuaishouIndustryConfigResponse, AdminKuaishouIndustryRefreshTokenResponse, + AdminKuaishouIndustryAuthorizationCodePayload, + AdminKuaishouIndustryExchangeCodeResponse, AdminKuaishouFeifeiProductRule, AdminKuaishouFeifeiConfig, AdminKuaishouFeifeiEffectiveConfig, diff --git a/apps/frontend/src/types/admin/platform-config/index.ts b/apps/frontend/src/types/admin/platform-config/index.ts index 77033df2..50d6daa6 100644 --- a/apps/frontend/src/types/admin/platform-config/index.ts +++ b/apps/frontend/src/types/admin/platform-config/index.ts @@ -35,6 +35,8 @@ export type { AdminKuaishouIndustrySourceConfig, AdminKuaishouIndustryConfigResponse, AdminKuaishouIndustryRefreshTokenResponse, + AdminKuaishouIndustryAuthorizationCodePayload, + AdminKuaishouIndustryExchangeCodeResponse, } from './kuaishou-industry' export type { diff --git a/apps/frontend/src/types/admin/platform-config/kuaishou-industry.ts b/apps/frontend/src/types/admin/platform-config/kuaishou-industry.ts index 213f3c7d..098b81fe 100644 --- a/apps/frontend/src/types/admin/platform-config/kuaishou-industry.ts +++ b/apps/frontend/src/types/admin/platform-config/kuaishou-industry.ts @@ -10,6 +10,10 @@ export interface AdminKuaishouIndustrySourceConfig { sendCallbackEnabled: boolean baseUrl: string authBaseUrl: string + redirectUri: string + scopes: string + authState: string + authorizationUrl: string appKey: string appSecret: string appSecretMasked: string @@ -30,6 +34,8 @@ export interface AdminKuaishouIndustrySourceConfig { refreshTokenExpiresAt: string accessTokenStatus: AdminKuaishouIndustryAccessTokenStatus accessTokenExpiresInSeconds: number | null + openId: string + grantedScopes: string sellerId: string provider: string platform: string @@ -48,3 +54,9 @@ export interface AdminKuaishouIndustryConfigResponse { export interface AdminKuaishouIndustryRefreshTokenResponse extends AdminKuaishouIndustryConfigResponse { refreshed: boolean } + +export interface AdminKuaishouIndustryAuthorizationCodePayload { + code: string +} + +export type AdminKuaishouIndustryExchangeCodeResponse = AdminKuaishouIndustryRefreshTokenResponse