import { createTaskEvent } from '../../../repositories/task-event-repo.js' import { getTaskById, updateTask, updateTaskStatusIfCurrent, } from '../../../repositories/task-repo.js' import { TASK_STATUS, canRedeemKuaishouCloudClaimStatus, isKuaishouCloudRedeemSettledStatus, normalizeTaskStatus, } from '../../../domain/task-status.js' import { createHttpError } from '../../../utils/http.js' import { nowIso } from '../../../utils/time.js' import type { TaskRow } from '../../../types/repository/rows.js' import { assertBoundUidMatchesExpected, assertClaimExpectedUidReady, getClaimIdentityFromTask, } from '../../claim/claim-identity.js' import { isKuaishouCloudTask, normalizeKuaishouCloudFlow, type JsonObject } from './domain.js' import { dispatchKuaishouCloudFulfillmentTask } from './dispatch-fulfillment.js' import { completeMockKuaishouCloudTask, isKuaishouCloudMockTask, } from './mock-helpers.js' import { normalizeActor, parseTaskContext } from './task-context.js' export type RedeemKuaishouCloudTaskResult = { task: TaskRow /** 是否已处于终态/已兑换,调用方无需再 dispatch */ alreadySettled: boolean /** 并发抢锁失败(他人已推进) */ concurrentSkipped: boolean } /** * lewan 一键兑换用例(履约引擎入口): * waiting_binding + UID 匹配 → role_confirmed → redeeming → dispatch(+autoFinalize) * * claim / admin 应只调此函数,不要直接拼状态机。 */ export async function redeemKuaishouCloudTask( task: TaskRow, options: JsonObject = {}, ): Promise { if (!isKuaishouCloudTask(task)) { throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', { statusCode: 409, errorCode: 'kuaishou_cloud_task_invalid', }) } const now = String(options.now || '').trim() || nowIso() const source = String(options.source || 'system_redeem').trim() || 'system_redeem' const actor = normalizeActor(options.actor) || { source } const autoFinalize = options.autoFinalize !== false const errorCodePrefix = String(options.errorCodePrefix || 'kuaishou_cloud_redeem').trim() || 'kuaishou_cloud_redeem' let workingTask = task const currentStatus = normalizeTaskStatus(workingTask.task_status) if (isKuaishouCloudRedeemSettledStatus(currentStatus)) { return { task: workingTask, alreadySettled: true, concurrentSkipped: false } } if (!canRedeemKuaishouCloudClaimStatus(currentStatus)) { throw createHttpError('当前状态不可兑换,请先完成绑定并匹配 UID', { statusCode: 409, errorCode: `${errorCodePrefix}_not_ready`, }) } // WAITING_BINDING + UID 匹配:自动升为 ROLE_CONFIRMED,实现一键兑换 if (currentStatus === TASK_STATUS.WAITING_BINDING) { const flow = normalizeKuaishouCloudFlow( parseTaskContext(workingTask).kuaishouCloudFulfillment, ) if (!isKuaishouCloudMockTask(workingTask)) { assertBoundUidMatchesExpected(workingTask, flow, { errorCodePrefix, }) } else { assertClaimExpectedUidReady(workingTask) } const confirmed = await updateTask(workingTask.id, { task_status: TASK_STATUS.ROLE_CONFIRMED, role_id: flow.binding.roleId || workingTask.role_id || '', role_name: flow.binding.roleName || workingTask.role_name || '', user_action_status: TASK_STATUS.ROLE_CONFIRMED, role_confirmed_at: now, last_error: '', updated_at: now, }) if (confirmed) { workingTask = confirmed } await createTaskEvent( workingTask.id, 'kuaishou_cloud_role_confirmed', { expectedUid: getClaimIdentityFromTask(workingTask).expectedUid, vnPhone: flow.binding.vnPhone, roleName: flow.binding.roleName, roleId: flow.binding.roleId, source: `${source}_one_click`, }, now, ) } const flowBeforeRedeem = normalizeKuaishouCloudFlow( parseTaskContext(workingTask).kuaishouCloudFulfillment, ) if (!isKuaishouCloudMockTask(workingTask)) { assertBoundUidMatchesExpected(workingTask, flowBeforeRedeem, { errorCodePrefix, }) } else { assertClaimExpectedUidReady(workingTask) } const lockedTask = await updateTaskStatusIfCurrent( workingTask.id, TASK_STATUS.ROLE_CONFIRMED, { task_status: TASK_STATUS.REDEEMING, user_action_status: 'not_required', last_error: '', updated_at: now, }, ) if (!lockedTask) { const latest = (await getTaskById(workingTask.id)) || workingTask return { task: latest, alreadySettled: isKuaishouCloudRedeemSettledStatus(latest.task_status), concurrentSkipped: true, } } if (isKuaishouCloudMockTask(lockedTask)) { await completeMockKuaishouCloudTask(lockedTask, now) const completed = (await getTaskById(lockedTask.id)) || lockedTask return { task: completed, alreadySettled: false, concurrentSkipped: false } } try { const result = await dispatchKuaishouCloudFulfillmentTask(lockedTask, { source, actor, autoFinalize, }) return { task: result.task || lockedTask, alreadySettled: false, concurrentSkipped: false, } } 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 { task: latestTask, alreadySettled: true, concurrentSkipped: false } } 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, errorMessage: message, }, nowIso(), ) throw error } }