diff --git a/apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.js b/apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.ts similarity index 86% rename from apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.js rename to apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.ts index 0e9d3b71..3905e842 100644 --- a/apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.js +++ b/apps/backend/src/services/platforms/agiso/xianyu/order-detail-service.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - import crypto from 'node:crypto' import { runtimeConfig } from '../../../../config/runtime.js' @@ -11,11 +9,113 @@ import { parseJsonObject } from '../../../../utils/json.js' const DEFAULT_DETAIL_TIMEOUT_MS = 5000 -export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {}) { +type JsonObject = Record + +type AgisoParsedTrade = { + provider?: string + platform?: string + shopId?: string + shopName?: string + platformOrderId?: string + totalAmount?: number + paidAt?: string | null + buyerId?: string + buyerName?: string + receiverContact?: string + rawPayload?: JsonObject + items?: AgisoOrderItem[] + [key: string]: unknown +} + +type AgisoOrderItem = { + skuCode: string + skuName: string + externalItemId: string + externalSkuCode: string + externalSkuName: string + quantity: number + spec: JsonObject + snapshot: { + externalItemId: string + externalSkuCode: string + externalSkuName: string + } +} + +type AgisoDetailQueryInput = { + shopId?: string + platformOrderId?: string + requestId?: string +} + +type FetchResponseLike = { + status: number + text: () => Promise +} + +type FetchWithTimeout = ( + url: string, + options: RequestInit, + timeoutMs: number, +) => Promise + +type AgisoDetailDeps = { + fetchWithTimeout?: FetchWithTimeout + queryAgisoXianyuOrderDetail?: ( + input: Required, + ) => Promise +} + +type AgisoTradeDetailConfig = { + endpoint: string + apiVersion: string + accessToken: string + appSecret: string + timeoutMs: number +} + +type AgisoShopConfig = { + accessToken?: string + appSecret?: string + tradeDetailEndpoint?: string + tradeDetailApiVersion?: string + tradeDetailTimeoutMs?: string | number +} + +type DeliveryState = { + shipped: boolean + shipTime: number + orderStatus: number +} + +type AgisoDetailResult = { + success: boolean + reason: string + errorMessage: string + responseStatus: number + payload: JsonObject + detailPayload: JsonObject + totalAmountFen: number + shipped: boolean + shipTime: number + orderStatus: number +} + +type ExternalSkuDescriptor = { + raw: string + externalSkuCode: string + externalSkuName: string +} + +export async function enrichAgisoXianyuTradeOrder(parsed: AgisoParsedTrade, { requestId = '' } = {}) { return enrichAgisoXianyuTradeOrderWithDeps(parsed, { requestId }) } -export async function enrichAgisoXianyuTradeOrderWithDeps(parsed, { requestId = '' } = {}, deps = {}) { +export async function enrichAgisoXianyuTradeOrderWithDeps( + parsed: AgisoParsedTrade, + { requestId = '' } = {}, + deps: AgisoDetailDeps = {}, +) { const queryOrderDetail = deps.queryAgisoXianyuOrderDetail || queryAgisoXianyuOrderDetail if (!shouldHydrateAgisoXianyuTradeOrder(parsed)) { @@ -69,8 +169,8 @@ export async function queryAgisoXianyuOrderDetail({ shopId = '', platformOrderId export async function queryAgisoXianyuOrderDetailWithDeps( { shopId = '', platformOrderId = '', requestId = '' } = {}, - deps = {}, -) { + deps: AgisoDetailDeps = {}, +): Promise { const requestWithTimeout = deps.fetchWithTimeout || fetchWithTimeout const normalizedShopId = String(shopId || '').trim() const normalizedPlatformOrderId = String(platformOrderId || '').trim() @@ -114,7 +214,7 @@ export async function queryAgisoXianyuOrderDetailWithDeps( body: new URLSearchParams(requestBody).toString(), }, config.timeoutMs) const rawText = await response.text() - const payload = parseJsonObject(rawText, { preserveLargeIntegers: true }) + const payload = parseJsonObject(rawText, { preserveLargeIntegers: true }) as JsonObject const detailPayload = extractAgisoDetailPayload(payload) const delivery = resolveAgisoXianyuOrderDeliveryState(detailPayload) const totalAmountFen = resolveTotalAmountFen(detailPayload) @@ -216,16 +316,16 @@ export async function queryAgisoXianyuOrderDetailWithDeps( } } -function shouldHydrateAgisoXianyuTradeOrder(parsed) { +function shouldHydrateAgisoXianyuTradeOrder(parsed: AgisoParsedTrade | null | undefined): boolean { return parsed?.provider === 'agiso' && parsed?.platform === 'xianyu' && String(parsed?.platformOrderId || '').trim() && Number(parsed?.totalAmount || 0) <= 0 } -function resolveAgisoXianyuTradeDetailConfig(shopId) { - const baseConfig = runtimeConfig.platforms?.agiso?.tradeDetail || {} - const shopConfig = getAgisoShopConfig(shopId) || {} +function resolveAgisoXianyuTradeDetailConfig(shopId: string): AgisoTradeDetailConfig { + const baseConfig = runtimeConfig.platforms.agiso.tradeDetail + const shopConfig = (getAgisoShopConfig(shopId) || {}) as AgisoShopConfig return { endpoint: String(shopConfig.tradeDetailEndpoint || baseConfig.endpoint || '').trim(), @@ -236,7 +336,10 @@ function resolveAgisoXianyuTradeDetailConfig(shopId) { } } -function buildRequestHeaders({ accessToken, apiVersion }) { +function buildRequestHeaders({ + accessToken, + apiVersion, +}: { accessToken: string; apiVersion: string }): Record { return { Authorization: `Bearer ${accessToken}`, ApiVersion: apiVersion, @@ -244,9 +347,12 @@ function buildRequestHeaders({ accessToken, apiVersion }) { } } -function buildRequestBody({ platformOrderId, appSecret }) { +function buildRequestBody({ + platformOrderId, + appSecret, +}: { platformOrderId: string; appSecret: string }): Record { const timestamp = String(Math.floor(Date.now() / 1000)) - const payload = { + const payload: Record = { tid: String(platformOrderId || '').trim(), timestamp, } @@ -255,7 +361,7 @@ function buildRequestBody({ platformOrderId, appSecret }) { return payload } -function generateSign(params, appSecret) { +function generateSign(params: Record, appSecret: string): string { const sortedEntries = Object.entries(params).sort(([left], [right]) => left.localeCompare(right)) let raw = appSecret diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 53569525..07394dfa 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -349,6 +349,14 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 136 个用例通过 +57. Agiso 订单详情服务迁移到 `.ts`: + - `src/services/platforms/agiso/xianyu/order-detail-service.ts` +58. 订单详情补查入参、依赖注入、HTTP 响应、详情查询结果、发货状态、店铺配置、外部 SKU 解析结果已显式类型化 +59. Docker 内验证通过: + - `src/services/platforms/agiso/xianyu/order-detail-service.test.js` 共 5 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 136 个用例通过 ## 下一步建议