移除快手小店以及 api
This commit is contained in:
@@ -11,8 +11,6 @@ import { buildClaimUrl } from './claim-service.js'
|
||||
import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
export const CLAIM_TERMINAL_STATUSES = new Set([TASK_STATUS.EXPIRED, TASK_STATUS.CLOSED])
|
||||
export const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type ClaimContext = {
|
||||
claimToken: ClaimTokenRow
|
||||
@@ -355,11 +353,7 @@ export function mapClaimKuaishouCloudFulfillment(task: TaskRow, order: OrderRow)
|
||||
flowType: 'kuaishou_cloud',
|
||||
shopId: String(order?.shop_id || '').trim(),
|
||||
shopName: String(order?.shop_name || '').trim(),
|
||||
guideImages: [
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/1.png`,
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/2.png`,
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/3.png`,
|
||||
],
|
||||
guideImages: [],
|
||||
ticket: {
|
||||
code: String(ticket.code || '').trim(),
|
||||
status: String(ticket.status || 'pending').trim() || 'pending',
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { PROJECT_ROOT } from '../../config/runtime.js'
|
||||
import { createTaskEvent } from '../../repositories/task-event-repo.js'
|
||||
import { getTaskById, updateTask, updateTaskStatusIfCurrent } from '../../repositories/task-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
@@ -13,22 +9,10 @@ import {
|
||||
isKuaishouCloudRoleConfirmSettledStatus,
|
||||
normalizeTaskStatus,
|
||||
} from '../../domain/task-status.js'
|
||||
import {
|
||||
getKuaishouEticketSourceConfig,
|
||||
listKuaishouEticketShopConfigs,
|
||||
resolveKuaishouEticketShopConfig,
|
||||
type KuaishouEticketShopConfig,
|
||||
} from '../platforms/kuaishou-eticket/source-config-service.js'
|
||||
import {
|
||||
consumeKuaishouEticket,
|
||||
queryKuaishouEticketConsumeDetail,
|
||||
} from '../platforms/kuaishou-eticket/consume-service.js'
|
||||
import { isKuaishouEticketMockTicketCode } from '../platforms/kuaishou-eticket/mock-ticket-service.js'
|
||||
import {
|
||||
dispatchKuaishouCloudFulfillmentTask,
|
||||
hasKuaishouCloudCustomerRole,
|
||||
hasKuaishouCloudDefaultRoleSnapshot,
|
||||
maskCode,
|
||||
normalizeKuaishouCloudFlow,
|
||||
prepareKuaishouCloudFulfillmentTask,
|
||||
rebindKuaishouCloudTaskRole,
|
||||
@@ -39,215 +23,9 @@ import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim
|
||||
import { syncKuaishouCloudRoleInfo } from './kuaishou-cloud-sync-service.js'
|
||||
import type { TaskRow } from '../../types/repository/rows.js'
|
||||
|
||||
const KUAISHOU_CLOUD_GUIDE_DIR = path.resolve(PROJECT_ROOT, '../../tems/imgs')
|
||||
const ALLOWED_GUIDE_FILES = new Set(['1.png', '2.png', '3.png'])
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
type ClaimDetailPayload = ReturnType<typeof buildClaimDetailPayload>
|
||||
|
||||
export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: JsonObject = {}) {
|
||||
const context = await getClaimContext(token)
|
||||
const now = nowIso()
|
||||
|
||||
const executorKey = String(context.task.executor_key || '').trim()
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
return verifyIndustryVoucherTicket(context, now)
|
||||
}
|
||||
|
||||
if (executorKey !== 'kuaishou_ct_assisted') {
|
||||
throw createHttpError('当前领取链接不是 kuaishou-lewan 客户领取流程', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_not_kuaishou_cloud',
|
||||
})
|
||||
}
|
||||
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(context.task).kuaishouCloudFulfillment)
|
||||
if (hasUsableIndustryVoucher(parseTaskContext(context.task))) {
|
||||
return verifyIndustryVoucherTicket(context, now)
|
||||
}
|
||||
|
||||
const ticketCode = String(payload.ticketCode || payload.eTicketId || '').trim()
|
||||
if (!ticketCode) {
|
||||
throw createHttpError('请先粘贴快手小店核销码', {
|
||||
statusCode: 400,
|
||||
errorCode: 'claim_kuaishou_cloud_missing_ticket_code',
|
||||
})
|
||||
}
|
||||
|
||||
const persistedTicketCode = String(flow.ticket.code || '').trim()
|
||||
if (flow.consume.status === 'success') {
|
||||
if (!persistedTicketCode || persistedTicketCode === ticketCode) {
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
throw createHttpError('当前领取链接已完成核销,不能更换核销码', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_kuaishou_cloud_ticket_already_consumed',
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
let shopCookie = ''
|
||||
let consumeBaseUrl = ''
|
||||
|
||||
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
|
||||
shopCookie = matched.shopConfig.cookie
|
||||
consumeBaseUrl = getKuaishouEticketSourceConfig().baseUrl
|
||||
}
|
||||
|
||||
if (!detailResult.ok || detailResult.alreadyConsumed || !detailResult.detail) {
|
||||
throw createHttpError(detailResult.errorMessage || '核销码校验失败,请确认是否复制完整', {
|
||||
statusCode: 409,
|
||||
errorCode: detailResult.alreadyConsumed
|
||||
? 'claim_kuaishou_cloud_ticket_already_consumed'
|
||||
: 'claim_kuaishou_cloud_ticket_invalid',
|
||||
})
|
||||
}
|
||||
|
||||
const consumePayload = {
|
||||
baseUrl: consumeBaseUrl,
|
||||
cookie: shopCookie,
|
||||
eTicketId: detailResult.eTicketId || ticketCode,
|
||||
oid: String(detailResult.detail.oid || '').trim(),
|
||||
formToken: String(detailResult.detail.formToken || '').trim(),
|
||||
num: Number(detailResult.detail.leftCount || 0) || 1,
|
||||
}
|
||||
const consumeResult = await consumeKuaishouEticket(consumePayload)
|
||||
if (!consumeResult.consumed) {
|
||||
throw createHttpError(consumeResult.errorMessage || '核销失败,请确认核销码状态后重试', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_kuaishou_cloud_consume_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContext(context.task)
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
ticket: {
|
||||
...flow.ticket,
|
||||
code: detailResult.eTicketId || ticketCode,
|
||||
status: 'verified',
|
||||
capturedAt: flow.ticket.capturedAt || now,
|
||||
capturedBy: flow.ticket.capturedBy || {
|
||||
source: 'claim_page',
|
||||
},
|
||||
verifiedAt: now,
|
||||
oid: String(detailResult.detail.oid || '').trim(),
|
||||
formToken: String(detailResult.detail.formToken || '').trim(),
|
||||
leftCount: Number(detailResult.detail.leftCount || 0) || 0,
|
||||
goodsTitle: String(detailResult.goods?.itemTitle || '').trim(),
|
||||
},
|
||||
consume: {
|
||||
...flow.consume,
|
||||
status: 'success',
|
||||
shopId,
|
||||
shopName,
|
||||
consumedAt: now,
|
||||
errorMessage: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (isKuaishouCloudMockContext(nextContext)) {
|
||||
const mockFlow = buildMockVerifiedKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment, now)
|
||||
await updateTask(context.task.id, {
|
||||
claim_token: context.claimToken.token,
|
||||
claim_expires_at: context.claimToken.expired_at,
|
||||
context_json: JSON.stringify({
|
||||
...nextContext,
|
||||
kuaishouCloudFulfillment: mockFlow,
|
||||
}),
|
||||
claimed_at: context.task.claimed_at || now,
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
user_action_status: 'pending_claim',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
})
|
||||
} else {
|
||||
const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment)
|
||||
const taskWithConsumedTicket = (await updateTask(context.task.id, {
|
||||
claim_token: context.claimToken.token,
|
||||
claim_expires_at: context.claimToken.expired_at,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
claimed_at: context.task.claimed_at || now,
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
user_action_status: 'pending_claim',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
})) || {
|
||||
...context.task,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
claimed_at: context.task.claimed_at || now,
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
user_action_status: 'pending_claim',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
}
|
||||
|
||||
if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) {
|
||||
await prepareKuaishouCloudFulfillmentTask(taskWithConsumedTicket, {
|
||||
source: 'claim_page_ticket_verified',
|
||||
actor: { source: 'claim_page' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await createTaskEvent(
|
||||
context.task.id,
|
||||
'kuaishou_cloud_ticket_verified',
|
||||
{
|
||||
ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode),
|
||||
shopId,
|
||||
shopName,
|
||||
goodsTitle: String(detailResult.goods?.itemTitle || '').trim(),
|
||||
mock: mockTicketCode,
|
||||
consumedAt: now,
|
||||
consumeAlreadyCompleted: consumeResult.alreadyConsumed === true,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
await createTaskEvent(
|
||||
context.task.id,
|
||||
'kuaishou_cloud_consumed',
|
||||
{
|
||||
source: 'claim_page_ticket_submit',
|
||||
ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode),
|
||||
shopId,
|
||||
shopName,
|
||||
consumeStatus: 'success',
|
||||
consumedAt: now,
|
||||
mock: mockTicketCode,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
async function verifyIndustryVoucherTicket(
|
||||
context: Awaited<ReturnType<typeof getClaimContext>>,
|
||||
now: string,
|
||||
@@ -363,26 +141,6 @@ async function verifyIndustryVoucherTicket(
|
||||
return getKuaishouCloudClaimDetail(context.claimToken.token)
|
||||
}
|
||||
|
||||
export async function getKuaishouCloudClaimGuideAssetPath(filename: unknown) {
|
||||
const normalized = String(filename || '').trim()
|
||||
if (!ALLOWED_GUIDE_FILES.has(normalized)) {
|
||||
throw createHttpError('指引图片不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'claim_kuaishou_cloud_asset_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const filePath = path.resolve(KUAISHOU_CLOUD_GUIDE_DIR, normalized)
|
||||
if (!filePath.startsWith(KUAISHOU_CLOUD_GUIDE_DIR) || !fs.existsSync(filePath)) {
|
||||
throw createHttpError('指引图片不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'claim_kuaishou_cloud_asset_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
return filePath
|
||||
}
|
||||
|
||||
export async function getKuaishouCloudClaimDetail(token: unknown): Promise<ClaimDetailPayload> {
|
||||
const context = await getClaimContext(token)
|
||||
let task = context.task
|
||||
@@ -500,134 +258,6 @@ function normalizeIndustryVoucherContext(value: unknown): 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()
|
||||
@@ -656,7 +286,7 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment)
|
||||
|
||||
if (flow.ticket.status !== 'verified') {
|
||||
throw createHttpError('请先验证核销码', {
|
||||
throw createHttpError('请先完成电子凭证确认', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_kuaishou_cloud_ticket_not_verified',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user