简化履约匹配并支持mock订单测试

This commit is contained in:
yml
2026-05-27 12:55:44 +08:00
parent e8010728a4
commit dd6776c4e4
21 changed files with 1892 additions and 683 deletions
@@ -8,9 +8,12 @@ import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import {
getKuaishouEticketSourceConfig,
listKuaishouEticketShopConfigs,
resolveKuaishouEticketShopConfig,
type KuaishouEticketShopConfig,
} from '../platforms/kuaishou-eticket/source-config-service.js'
import { queryKuaishouEticketConsumeDetail } from '../platforms/kuaishou-eticket/consume-service.js'
import { isKuaishouEticketMockTicketCode } from '../platforms/kuaishou-eticket/mock-ticket-service.js'
import {
dispatchKuaishouCloudFulfillmentTask,
maskCode,
@@ -48,24 +51,28 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
})
}
const shopId = String(flow.consume.shopId || context.order.shop_id || '').trim()
const shopConfig = resolveKuaishouEticketShopConfig({
shopId,
shopName: String(context.order.shop_name || '').trim(),
})
if (!shopConfig || shopConfig.enabled === false || !String(shopConfig.cookie || '').trim()) {
throw createHttpError('这笔订单对应的快手小店还没有配置可用 Cookie,请联系客服处理', {
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_shop_cookie_missing',
})
}
const mockTicketCode = isKuaishouEticketMockTicketCode(ticketCode)
let shopId = String(flow.consume.shopId || context.order.shop_id || (mockTicketCode ? 'mock' : '')).trim()
let shopName = String(flow.consume.shopName || context.order.shop_name || '').trim()
let detailResult
const eticketSource = getKuaishouEticketSourceConfig()
const detailResult = await queryKuaishouEticketConsumeDetail({
baseUrl: eticketSource.baseUrl,
cookie: shopConfig.cookie,
eTicketId: ticketCode,
})
if (mockTicketCode) {
detailResult = await queryKuaishouEticketConsumeDetail({
eTicketId: ticketCode,
goodsTitle: context.orderItem.sku_name || context.orderItem.sku_code,
})
} else {
const matched = await queryKuaishouEticketConsumeDetailWithShopFallback({
ticketCode,
shopId,
shopName,
fallbackShopName: String(context.order.shop_name || '').trim(),
})
detailResult = matched.detailResult
shopId = matched.shopConfig.shopId || shopId
shopName = matched.shopConfig.kshopName || shopName
}
if (!detailResult.ok || detailResult.alreadyConsumed || !detailResult.detail) {
throw createHttpError(detailResult.errorMessage || '核销码校验失败,请确认是否复制完整', {
@@ -130,8 +137,9 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
await createTaskEvent(context.task.id, 'kuaishou_cloud_ticket_verified', {
ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode),
shopId,
shopName: String(context.order.shop_name || '').trim(),
shopName,
goodsTitle: String(detailResult.goods?.itemTitle || '').trim(),
mock: mockTicketCode,
}, now)
return getKuaishouCloudClaimDetail(token)
@@ -190,6 +198,133 @@ function parseTaskContext(task: Partial<TaskRow> | null | undefined): JsonObject
}
}
async function queryKuaishouEticketConsumeDetailWithShopFallback({
ticketCode,
shopId,
shopName,
fallbackShopName = '',
}: {
ticketCode: string
shopId: string
shopName: string
fallbackShopName?: string
}) {
const eticketSource = getKuaishouEticketSourceConfig()
const shopConfigs = resolveKuaishouEticketDetailCandidateShops({
shopId,
shopName,
fallbackShopName,
})
if (shopConfigs.length === 0) {
throw createHttpError('还没有配置可用的快手小店 Cookie,请联系客服处理', {
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_shop_cookie_missing',
})
}
let lastResult: JsonObject | null = null
let lastError: unknown = null
for (const shopConfig of shopConfigs) {
try {
const detailResult = await queryKuaishouEticketConsumeDetail({
baseUrl: eticketSource.baseUrl,
cookie: shopConfig.cookie,
eTicketId: ticketCode,
})
lastResult = detailResult
if (detailResult.ok || detailResult.alreadyConsumed) {
return {
shopConfig,
detailResult,
}
}
} catch (error) {
lastError = error
}
}
if (lastResult) {
const fallbackShopConfig = shopConfigs[0]
if (!fallbackShopConfig) {
throw createHttpError('还没有配置可用的快手小店 Cookie,请联系客服处理', {
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_shop_cookie_missing',
})
}
return {
shopConfig: fallbackShopConfig,
detailResult: lastResult,
}
}
throw createHttpError(lastError instanceof Error ? lastError.message : '核销码校验失败,请确认是否复制完整', {
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_ticket_invalid',
})
}
function resolveKuaishouEticketDetailCandidateShops({
shopId,
shopName,
fallbackShopName = '',
}: {
shopId: string
shopName: string
fallbackShopName?: string
}) {
const candidates: KuaishouEticketShopConfig[] = []
const configuredShop = resolveKuaishouEticketShopConfig({
shopId,
shopName: shopName || fallbackShopName,
})
if (isUsableKuaishouEticketShopConfig(configuredShop)) {
candidates.push(configuredShop)
}
for (const shopConfig of listKuaishouEticketShopConfigs()) {
if (!isUsableKuaishouEticketShopConfig(shopConfig)) {
continue
}
if (candidates.some((item) => isSameKuaishouEticketShopConfig(item, shopConfig))) {
continue
}
candidates.push(shopConfig)
}
return candidates
}
function isUsableKuaishouEticketShopConfig(
shopConfig: KuaishouEticketShopConfig | null | undefined,
): shopConfig is KuaishouEticketShopConfig {
return Boolean(
shopConfig &&
shopConfig.enabled !== false &&
String(shopConfig.cookie || '').trim(),
)
}
function isSameKuaishouEticketShopConfig(
left: KuaishouEticketShopConfig,
right: KuaishouEticketShopConfig,
) {
const leftShopId = String(left.shopId || '').trim()
const rightShopId = String(right.shopId || '').trim()
if (leftShopId && rightShopId) {
return leftShopId === rightShopId
}
return String(left.kshopName || '').trim() === String(right.kshopName || '').trim()
}
export async function confirmKuaishouCloudClaimRole(token: unknown) {
const context = await getClaimContext(token)
const now = nowIso()