支持快手行业电子凭证多店铺授权

This commit is contained in:
yml2213
2026-07-08 14:13:15 +08:00
parent 102330d7af
commit ec23eb6af6
24 changed files with 1110 additions and 219 deletions
@@ -1,9 +1,11 @@
import { maskSecret } from '../../../utils/masking.js'
import { createHttpError } from '../../../utils/http.js'
import {
findKuaishouIndustryShopConfig,
getKuaishouIndustrySourceConfig,
getKuaishouIndustrySourceFilePath,
saveKuaishouIndustrySourceConfig,
type KuaishouIndustryShopConfig,
type KuaishouIndustrySourceConfig,
} from '../../platforms/kuaishou-industry/source-config-service.js'
import {
@@ -17,6 +19,9 @@ const SECRET_FIELDS = [
'appSecret',
'signSecret',
'messageSecret',
] as const
const SHOP_SECRET_FIELDS = [
'accessToken',
'refreshToken',
] as const
@@ -49,8 +54,10 @@ export function updateAdminKuaishouIndustrySourceConfig(payload: JsonObject = {}
shopId: readConfigString(payload, 'shopId', current.shopId),
shopName: readConfigString(payload, 'shopName', current.shopName),
version: readConfigString(payload, 'version', current.version),
accessTokenExpiresAt: readConfigString(payload, 'accessTokenExpiresAt', current.accessTokenExpiresAt, { allowBlank: true }),
refreshTokenExpiresAt: readConfigString(payload, 'refreshTokenExpiresAt', current.refreshTokenExpiresAt, { allowBlank: true }),
shops: normalizeShopConfigPayloads(
Array.isArray(payload.shops) ? payload.shops : current.shops,
current.shops,
),
...resolveSecretPatch(payload, current),
})
@@ -60,12 +67,15 @@ export function updateAdminKuaishouIndustrySourceConfig(payload: JsonObject = {}
}
}
export async function refreshAdminKuaishouIndustryAccessToken() {
const result = await refreshKuaishouIndustryAccessToken()
export async function refreshAdminKuaishouIndustryAccessToken(payload: JsonObject = {}) {
const result = await refreshKuaishouIndustryAccessToken({
sellerId: String(payload.sellerId || '').trim(),
})
return {
filePath: getKuaishouIndustrySourceFilePath(),
refreshed: result.refreshed,
shop: mapAdminKuaishouIndustryShopConfig(result.shop, result.config),
source: mapAdminKuaishouIndustrySourceConfig(result.config),
}
}
@@ -79,17 +89,23 @@ export async function exchangeAdminKuaishouIndustryAuthorizationCode(payload: Js
})
}
const result = await exchangeKuaishouIndustryAuthorizationCode(code)
const result = await exchangeKuaishouIndustryAuthorizationCode(code, {
sellerId: String(payload.sellerId || '').trim(),
shopName: String(payload.shopName || '').trim(),
customShopName: String(payload.customShopName || '').trim(),
})
return {
filePath: getKuaishouIndustrySourceFilePath(),
refreshed: result.refreshed,
shop: mapAdminKuaishouIndustryShopConfig(result.shop, result.config),
source: mapAdminKuaishouIndustrySourceConfig(result.config),
}
}
function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConfig) {
const accessTokenStatus = resolveAccessTokenStatus(config)
const primaryShop = config.shops[0] || null
const accessTokenStatus = resolveAccessTokenStatus(primaryShop || config)
return {
enabled: config.enabled !== false,
@@ -127,12 +143,47 @@ function mapAdminKuaishouIndustrySourceConfig(config: KuaishouIndustrySourceConf
shopId: config.shopId,
shopName: config.shopName,
version: config.version,
shops: config.shops.map((shop) => mapAdminKuaishouIndustryShopConfig(shop, config)),
lastRefreshedAt: config.lastRefreshedAt,
lastRefreshError: config.lastRefreshError,
}
}
function buildKuaishouIndustryAuthorizationUrl(config: KuaishouIndustrySourceConfig): string {
function mapAdminKuaishouIndustryShopConfig(
shop: KuaishouIndustryShopConfig,
config: KuaishouIndustrySourceConfig,
) {
const accessTokenStatus = resolveAccessTokenStatus(shop)
return {
enabled: shop.enabled !== false,
sellerId: shop.sellerId,
shopId: shop.shopId,
shopName: shop.shopName,
customShopName: shop.customShopName,
authState: shop.authState,
authorizationUrl: buildKuaishouIndustryAuthorizationUrl(config, shop),
accessToken: '',
accessTokenMasked: maskSecret(shop.accessToken),
hasAccessToken: Boolean(shop.accessToken),
refreshToken: '',
refreshTokenMasked: maskSecret(shop.refreshToken),
hasRefreshToken: Boolean(shop.refreshToken),
accessTokenExpiresAt: shop.accessTokenExpiresAt,
refreshTokenExpiresAt: shop.refreshTokenExpiresAt,
accessTokenStatus: accessTokenStatus.status,
accessTokenExpiresInSeconds: accessTokenStatus.expiresInSeconds,
openId: shop.openId,
grantedScopes: shop.grantedScopes,
lastRefreshedAt: shop.lastRefreshedAt,
lastRefreshError: shop.lastRefreshError,
}
}
function buildKuaishouIndustryAuthorizationUrl(
config: KuaishouIndustrySourceConfig,
shop?: Pick<KuaishouIndustryShopConfig, 'sellerId' | 'authState'> | null,
): string {
if (!config.authBaseUrl || !config.appKey || !config.redirectUri || !config.scopes) {
return ''
}
@@ -143,8 +194,9 @@ function buildKuaishouIndustryAuthorizationUrl(config: KuaishouIndustrySourceCon
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)
const state = String(shop?.authState || config.authState || shop?.sellerId || '').trim()
if (state) {
url.searchParams.set('state', state)
}
return url.toString()
@@ -174,7 +226,96 @@ function resolveSecretPatch(
return patch
}
function resolveAccessTokenStatus(config: KuaishouIndustrySourceConfig) {
function normalizeShopConfigPayloads(
rawShops: unknown[],
currentShops: KuaishouIndustryShopConfig[] = [],
): KuaishouIndustryShopConfig[] {
return rawShops
.map((rawShop, index) => normalizeShopConfigPayload(rawShop, index, currentShops))
.filter((shop): shop is KuaishouIndustryShopConfig => Boolean(shop))
}
function normalizeShopConfigPayload(
rawShop: unknown,
index: number,
currentShops: KuaishouIndustryShopConfig[],
): KuaishouIndustryShopConfig | null {
if (!rawShop || typeof rawShop !== 'object' || Array.isArray(rawShop)) {
return null
}
const payload = rawShop as JsonObject
const current = resolveCurrentShopConfig(payload, index, currentShops)
const sellerId = readConfigString(payload, 'sellerId', current?.sellerId || '', { allowBlank: true })
const shopId = readConfigString(payload, 'shopId', current?.shopId || sellerId, { allowBlank: true }) || sellerId
const shopName = readConfigString(payload, 'shopName', current?.shopName || '', { allowBlank: true })
const customShopName = readConfigString(payload, 'customShopName', current?.customShopName || '', { allowBlank: true })
const accessToken = readSecretString(payload, 'accessToken', current?.accessToken || '')
const refreshToken = readSecretString(payload, 'refreshToken', current?.refreshToken || '')
const openId = readConfigString(payload, 'openId', current?.openId || '', { allowBlank: true })
if (!sellerId && !shopId && !shopName && !customShopName && !accessToken && !refreshToken && !openId) {
return null
}
return {
enabled: hasPayloadField(payload, 'enabled') ? payload.enabled !== false : current?.enabled !== false,
sellerId,
shopId,
shopName,
customShopName,
authState: readConfigString(payload, 'authState', current?.authState || '', { allowBlank: true }),
accessToken,
refreshToken,
accessTokenExpiresAt: readConfigString(payload, 'accessTokenExpiresAt', current?.accessTokenExpiresAt || '', { allowBlank: true }),
refreshTokenExpiresAt: readConfigString(payload, 'refreshTokenExpiresAt', current?.refreshTokenExpiresAt || '', { allowBlank: true }),
openId,
grantedScopes: normalizeScopeText(readConfigString(payload, 'grantedScopes', current?.grantedScopes || '', { allowBlank: true })),
lastRefreshedAt: readConfigString(payload, 'lastRefreshedAt', current?.lastRefreshedAt || '', { allowBlank: true }),
lastRefreshError: readConfigString(payload, 'lastRefreshError', current?.lastRefreshError || '', { allowBlank: true }),
...resolveShopSecretPatch(payload, current),
}
}
function resolveCurrentShopConfig(
payload: JsonObject,
index: number,
currentShops: KuaishouIndustryShopConfig[],
): KuaishouIndustryShopConfig | null {
const sellerId = String(payload.sellerId || payload.shopId || '').trim()
if (sellerId) {
const sourceLike = { shops: currentShops } as KuaishouIndustrySourceConfig
const matched = findKuaishouIndustryShopConfig(sellerId, sourceLike)
if (matched) {
return matched
}
}
return currentShops[index] || null
}
function resolveShopSecretPatch(
payload: JsonObject,
current: KuaishouIndustryShopConfig | null,
): Partial<KuaishouIndustryShopConfig> {
const patch: Partial<KuaishouIndustryShopConfig> = {}
for (const field of SHOP_SECRET_FIELDS) {
patch[field] = readSecretString(payload, field, current?.[field] || '')
}
return patch
}
function readSecretString(
payload: JsonObject,
field: string,
fallback: string,
): string {
const text = String(payload[field] || '').trim()
return text || fallback
}
function resolveAccessTokenStatus(config: Pick<KuaishouIndustrySourceConfig, 'accessToken' | 'accessTokenExpiresAt'>) {
if (!config.accessToken) {
return {
status: 'missing',