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' import { addHours, nowIso } from '../../utils/time.js' import { TASK_STATUS, canRedeemKuaishouCloudClaimStatus, isKuaishouCloudRedeemSettledStatus, 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, refreshKuaishouCloudTaskRoleInfo, } from '../fulfillment/kuaishou-cloud/index.js' import { syncKuaishouFeifeiTaskStatus } from '../fulfillment/kuaishou-feifei/index.js' import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim-context.js' 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 type ClaimDetailPayload = ReturnType 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('当前领取链接不是快手 Cloud 客户领取流程', { 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>, now: string, ): Promise { const taskContext = parseTaskContext(context.task) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) if (flow.consume.status === 'success') { return getKuaishouCloudClaimDetail(context.claimToken.token) } const industryContext = typeof taskContext === 'object' ? taskContext : {} const voucherContext = normalizeIndustryVoucherContext(industryContext.kuaishouIndustryVoucher) if (voucherContext.status === 'DESTROYED') { throw createHttpError('当前电子凭证已销毁,无法继续领取', { statusCode: 409, errorCode: 'claim_kuaishou_industry_voucher_destroyed', }) } const token = String(voucherContext.token || industryContext.token || '').trim() const voucherCode = String(voucherContext.voucherCode || voucherContext.eticketId || '').trim() const certExpireType = Number(industryContext.certExpireType || 0) const certActualStartTime = Number( voucherContext.validStartTime || industryContext.certActualStartTime || 0, ) const certActualEndTime = Number( voucherContext.validEndTime || industryContext.certActualEndTime || 0, ) const nextContext = { ...taskContext, kuaishouIndustryVoucher: { ...voucherContext, oid: voucherContext.oid || context.order.platform_order_id, token, eticketId: voucherCode, voucherCode, status: voucherContext.status || 'UNUSED', verifiedAt: voucherContext.verifiedAt || now, }, kuaishouCloudFulfillment: { ...flow, ticket: { ...flow.ticket, code: voucherCode, status: 'verified', capturedAt: flow.ticket.capturedAt || now, capturedBy: flow.ticket.capturedBy || { source: 'send_code_callback' }, verifiedAt: now, oid: voucherContext.oid || context.order.platform_order_id, formToken: token, leftCount: voucherContext.status === 'CONSUMED' ? 0 : 1, goodsTitle: context.orderItem.sku_name || context.orderItem.sku_code || '', }, consume: { ...flow.consume, status: voucherContext.status === 'CONSUMED' ? 'success' : 'pending', shopId: context.order.shop_id, shopName: context.order.shop_name, autoConsumeEnabled: true, consumedAt: voucherContext.status === 'CONSUMED' ? voucherContext.consumedAt || flow.consume.consumedAt || now : flow.consume.consumedAt, }, certInfo: { certExpireType, certActualStartTime, certActualEndTime, }, }, } const taskWithTicket = (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, } const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment) if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) { await prepareKuaishouCloudFulfillmentTask(taskWithTicket, { source: 'send_code_callback', actor: { source: 'send_code_callback' }, }) } await createTaskEvent( context.task.id, 'kuaishou_cloud_ticket_verified', { ticketCodeMasked: '', source: 'send_code_callback', industryVoucher: true, verifiedAt: now, }, now, ) 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 { const context = await getClaimContext(token) let task = context.task const executorKey = String(task.executor_key || '').trim() if (executorKey === 'kuaishou_feifei') { task = (await syncKuaishouFeifeiTaskStatus(task)) || task } if (executorKey === 'kuaishou-industry') { const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment) if (flow.consume.status !== 'success') { const now = nowIso() await verifyIndustryVoucherTicket(context, now).catch(() => null) } } if (executorKey === 'kuaishou_ct_assisted') { const taskContext = parseTaskContext(task) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const needsIndustryVoucherPrepare = hasUsableIndustryVoucher(taskContext) && ( flow.ticket.status !== 'verified' || (flow.binding.prepareStatus !== 'ready' && !flow.binding.bindUrl) ) if (needsIndustryVoucherPrepare) { const now = nowIso() const prepared: ClaimDetailPayload | null = await verifyIndustryVoucherTicket(context, now) .catch(() => null) if (prepared) { return prepared } } } if ( executorKey === 'kuaishou_ct_assisted' && !isKuaishouCloudMockTask(task) ) { task = (await syncKuaishouCloudRoleInfo(task)) || task } return buildClaimDetailPayload({ claimToken: context.claimToken, task, order: context.order, orderItem: context.orderItem, }) } export async function rebindKuaishouCloudClaimRole(token: unknown) { const context = await getClaimContext(token) if (String(context.task.executor_key || '').trim() !== 'kuaishou_ct_assisted') { throw createHttpError('当前领取链接不是快手 Cloud 客户领取流程', { statusCode: 409, errorCode: 'claim_not_kuaishou_cloud', }) } await rebindKuaishouCloudTaskRole(context.task, { source: 'claim_page_rebind_role', actor: { source: 'claim_page' }, }) return getKuaishouCloudClaimDetail(token) } function parseTaskContext(task: Partial | null | undefined): JsonObject { const rawValue = task?.context_json if (!rawValue) { return {} } if (typeof rawValue === 'object') { return rawValue } try { return JSON.parse(String(rawValue || '{}')) } catch { return {} } } function hasUsableIndustryVoucher(context: JsonObject = {}) { const voucher = normalizeIndustryVoucherContext(context.kuaishouIndustryVoucher) if (!voucher.voucherCode && !voucher.eticketId) { return false } return voucher.status !== 'DESTROYED' } function normalizeIndustryVoucherContext(value: unknown): JsonObject { const source = value && typeof value === 'object' && !Array.isArray(value) ? value as JsonObject : {} const status = String(source.status || 'UNUSED').trim().toUpperCase() return { ...source, oid: String(source.oid || '').trim(), token: String(source.token || '').trim(), eticketId: String(source.eticketId || source.voucherCode || '').trim(), voucherCode: String(source.voucherCode || source.eticketId || '').trim(), status: status === 'CONSUMED' || status === 'DESTROYED' ? status : 'UNUSED', validStartTime: Number(source.validStartTime || 0) || 0, validEndTime: Number(source.validEndTime || 0) || 0, verifiedAt: source.verifiedAt || null, consumedAt: source.consumedAt || null, } } 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() if (String(context.task.executor_key || '').trim() !== 'kuaishou_ct_assisted') { throw createHttpError('当前领取链接不是快手 Cloud 客户领取流程', { statusCode: 409, errorCode: 'claim_not_kuaishou_cloud', }) } if (isKuaishouCloudRoleConfirmSettledStatus(context.task.task_status)) { return getKuaishouCloudClaimDetail(token) } const contextSource = parseTaskContext(context.task) const mockMode = isKuaishouCloudMockContext(contextSource) const refreshed = mockMode ? { task: context.task } : await refreshKuaishouCloudTaskRoleInfo(context.task, { source: 'claim_page_role_confirm', actor: { source: 'claim_page' }, recordEvent: false, forceProbe: true, }) const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment) if (flow.ticket.status !== 'verified') { throw createHttpError('请先验证核销码', { statusCode: 409, errorCode: 'claim_kuaishou_cloud_ticket_not_verified', }) } if (!flow.binding.vnPhone || !flow.binding.roleName || !flow.binding.roleId) { throw createHttpError('角色信息还未刷新到系统,请完成绑定后稍等片刻再试', { statusCode: 409, errorCode: 'claim_kuaishou_cloud_role_not_ready', }) } const hasDefaultRoleSnapshot = hasKuaishouCloudDefaultRoleSnapshot(flow) if (!mockMode && !hasKuaishouCloudCustomerRole(flow)) { const message = hasDefaultRoleSnapshot ? '当前仍是虚拟机默认角色,请先重新绑定自己的角色信息' : '系统还未获取到虚拟机默认角色信息,请稍后刷新角色信息后再试' throw createHttpError(message, { statusCode: 409, errorCode: hasDefaultRoleSnapshot ? 'claim_kuaishou_cloud_default_role_not_rebound' : 'claim_kuaishou_cloud_default_role_not_ready', }) } await updateTask(context.task.id, { task_status: TASK_STATUS.ROLE_CONFIRMED, role_id: flow.binding.roleId, role_name: flow.binding.roleName, user_action_status: TASK_STATUS.ROLE_CONFIRMED, role_confirmed_at: now, last_error: '', updated_at: now, }) await createTaskEvent( context.task.id, 'kuaishou_cloud_role_confirmed', { vnPhone: flow.binding.vnPhone, roleName: flow.binding.roleName, roleId: flow.binding.roleId, }, now, ) return getKuaishouCloudClaimDetail(token) } export async function redeemKuaishouCloudClaim(token: unknown) { const context = await getClaimContext(token) const now = nowIso() if (String(context.task.executor_key || '').trim() !== 'kuaishou_ct_assisted') { throw createHttpError('当前领取链接不是快手 Cloud 客户领取流程', { statusCode: 409, errorCode: 'claim_not_kuaishou_cloud', }) } const currentStatus = normalizeTaskStatus(context.task.task_status) if (isKuaishouCloudRedeemSettledStatus(currentStatus)) { return getKuaishouCloudClaimDetail(token) } if (!canRedeemKuaishouCloudClaimStatus(currentStatus)) { throw createHttpError('请先确认角色信息', { statusCode: 409, errorCode: 'claim_kuaishou_cloud_role_not_confirmed', }) } const lockedTask = await updateTaskStatusIfCurrent(context.task.id, TASK_STATUS.ROLE_CONFIRMED, { task_status: TASK_STATUS.REDEEMING, user_action_status: 'not_required', last_error: '', updated_at: now, }) if (!lockedTask) { return getKuaishouCloudClaimDetail(token) } if (isKuaishouCloudMockTask(lockedTask)) { await completeMockKuaishouCloudClaimTask(lockedTask, now) return getKuaishouCloudClaimDetail(token) } try { await dispatchKuaishouCloudFulfillmentTask(lockedTask, { source: 'claim_page_redeem', actor: { source: 'claim_page' }, autoFinalize: true, }) } catch (error) { const latestTask = await getTaskById(lockedTask.id) const latestStatus = latestTask ? normalizeTaskStatus(latestTask.task_status) : '' if (latestTask && latestStatus !== TASK_STATUS.REDEEMING) { if ( latestStatus === TASK_STATUS.DISPATCHED_PENDING_RETURN || latestStatus === TASK_STATUS.COMPLETED || latestStatus === TASK_STATUS.REDEEMED ) { return getKuaishouCloudClaimDetail(token) } throw error } const message = error instanceof Error ? error.message : '兑换请求提交失败,请联系客服处理' await updateTask(lockedTask.id, { task_status: TASK_STATUS.MANUAL_REVIEW, user_action_status: 'not_required', last_error: message, result_code: 'kuaishou_cloud_redeem_failed', result_message: message, updated_at: nowIso(), }) await createTaskEvent( lockedTask.id, 'kuaishou_cloud_redeem_failed', { source: 'claim_page_redeem', errorMessage: message, }, nowIso(), ) throw error } return getKuaishouCloudClaimDetail(token) } function isKuaishouCloudMockTask(task: Partial | null | undefined) { return isKuaishouCloudMockContext(parseTaskContext(task)) } function isKuaishouCloudMockContext(context: JsonObject = {}) { const flow = context.kuaishouCloudFulfillment && typeof context.kuaishouCloudFulfillment === 'object' ? context.kuaishouCloudFulfillment : {} const mock = flow.mock && typeof flow.mock === 'object' ? flow.mock : context.mock return Boolean(mock && typeof mock === 'object' && mock.enabled === true) } function buildMockVerifiedKuaishouCloudFlow(value: unknown, timestamp: string) { const flow = normalizeKuaishouCloudFlow(value) const source = flow as JsonObject const bindUrl = flow.binding.bindUrl || `https://example.com/mock-kuaishou-cloud-bind?task=mock&ts=${encodeURIComponent(timestamp)}` const vnPhone = flow.binding.vnPhone || '13800000000' const roleName = flow.binding.roleName || flow.role.name || '测试角色' const roleId = flow.binding.roleId || flow.role.rid || '10001' return { ...flow, mock: { ...(source.mock && typeof source.mock === 'object' ? source.mock : {}), enabled: true, }, binding: { ...flow.binding, prepareStatus: 'ready', vnId: flow.binding.vnId || 900001, vnPhone, bindUrl, bindPreparedAt: flow.binding.bindPreparedAt || timestamp, bindExpiresAt: flow.binding.bindExpiresAt || addHours(timestamp, 24), roleName, roleId, }, role: { ...flow.role, status: 'ready', name: roleName, rid: roleId, refreshedAt: flow.role.refreshedAt || timestamp, errorMessage: '', rawInfo: flow.role.rawInfo || { mock: true, }, }, } } async function completeMockKuaishouCloudClaimTask(task: TaskRow, timestamp: string) { const taskContext = parseTaskContext(task) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const source = flow as JsonObject const nextFlow = { ...flow, mock: { ...(source.mock && typeof source.mock === 'object' ? source.mock : {}), enabled: true, }, dispatch: { ...flow.dispatch, status: 'success', dispatchAt: timestamp, dispatchBy: { source: 'claim_page_mock', }, note: '开发 mock 已模拟发货成功', items: flow.deliveryItems, }, returnNumber: { ...flow.returnNumber, status: 'success', returnedAt: timestamp, returnedBy: { source: 'claim_page_mock', }, }, consume: { ...flow.consume, status: 'success', consumedAt: timestamp, errorMessage: '', }, } await updateTask(task.id, { task_status: TASK_STATUS.COMPLETED, delivery_status: 'success', result_code: 'mock_success', result_message: '开发 mock 已模拟兑换成功', user_action_status: 'not_required', last_error: '', context_json: JSON.stringify({ ...taskContext, kuaishouCloudFulfillment: nextFlow, }), redeemed_at: timestamp, updated_at: timestamp, }) await createTaskEvent( task.id, 'kuaishou_cloud_mock_redeemed', { source: 'claim_page_mock', deliveryItems: flow.deliveryItems, }, timestamp, ) }