新增 affiliate_dash 统一领取页流程(阶段 4)
- 后端:claim 详情分流 affiliate_dash 分支 + 详情轮询刷新 bind-result - 输 UID 触发 bind(拿 bindUuid/二维码, task→waiting_binding) - 新增 POST /claim/:token/affiliate-dash/submit(submit→redeeming + 事件) - 前端:ClaimAffiliateDashSteps(二维码/绑定状态/mismatch警告/提交发货) - claim-snapshot/claim-poll 按 flowType 分流(step2 绑定中/step3 已绑定待提交/step4 结果) - 前后端 typecheck + 后端 212 测试通过
This commit is contained in:
@@ -16,6 +16,15 @@ import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
} from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import { syncKuaishouFeifeiTaskStatus } from '../fulfillment/kuaishou-feifei/index.js'
|
||||
import {
|
||||
normalizeAffiliateDashFlow,
|
||||
syncAffiliateDashTaskStatus,
|
||||
} from '../fulfillment/affiliate-dash/index.js'
|
||||
import {
|
||||
bindAffiliateDashDelivery,
|
||||
getAffiliateDashBindResult,
|
||||
submitAffiliateDashDelivery,
|
||||
} from '../platforms/affiliate-dash/order-service.js'
|
||||
import {
|
||||
assertValidClaimUid,
|
||||
canUpdateClaimUid,
|
||||
@@ -221,6 +230,11 @@ export async function getKuaishouCloudClaimDetail(token: unknown): Promise<Claim
|
||||
task = (await syncKuaishouFeifeiTaskStatus(task)) || task
|
||||
}
|
||||
|
||||
if (executorKey === 'affiliate_dash') {
|
||||
task = (await syncAffiliateDashTaskStatus(task)) || task
|
||||
task = (await refreshAffiliateDashBindState(task)) || task
|
||||
}
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
// 已核销也要走 verify:内部会补 prepare 绑定资源
|
||||
@@ -357,9 +371,173 @@ export async function submitClaimUid(
|
||||
}
|
||||
}
|
||||
|
||||
if (executorKey === 'affiliate_dash') {
|
||||
updatedTask = (await bindAffiliateDashClaimForTask(updatedTask, expectedUid)) || updatedTask
|
||||
}
|
||||
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
/**
|
||||
* affiliate_dash 领取 Step2:提交发货(绑定完成后)。
|
||||
*/
|
||||
export async function submitAffiliateDashClaim(
|
||||
token: unknown,
|
||||
payload: { gameAccount?: unknown; bindUuid?: unknown } = {},
|
||||
): Promise<ClaimDetailPayload> {
|
||||
const context = await getClaimContext(token)
|
||||
const task = context.task
|
||||
const executorKey = String(task.executor_key || '').trim()
|
||||
if (executorKey !== 'affiliate_dash') {
|
||||
throw createHttpError('当前领取链接不是 affiliate-dash 领取流程', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_not_affiliate_dash',
|
||||
})
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo) {
|
||||
throw createHttpError('affiliate-dash 订单尚未创建', {
|
||||
statusCode: 409,
|
||||
errorCode: 'affiliate_dash_order_missing',
|
||||
})
|
||||
}
|
||||
|
||||
const gameAccount = String(payload.gameAccount || flow.gameAccount || '').trim()
|
||||
const bindUuid = String(payload.bindUuid || flow.bindUuid || '').trim()
|
||||
if (!gameAccount || !bindUuid) {
|
||||
throw createHttpError('缺少绑定账号或 bindUuid', {
|
||||
statusCode: 400,
|
||||
errorCode: 'affiliate_dash_submit_missing',
|
||||
})
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const result = await submitAffiliateDashDelivery({
|
||||
orderNo: flow.orderNo,
|
||||
gameAccount,
|
||||
bindUuid,
|
||||
})
|
||||
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
gameAccount,
|
||||
bindUuid,
|
||||
submitStatus: result.status,
|
||||
orderStatus: result.status || flow.orderStatus,
|
||||
}
|
||||
|
||||
await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.REDEEMING,
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
result_code: 'affiliate_dash_submitted',
|
||||
result_message: result.message || 'affiliate-dash 已提交发货',
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'affiliate_dash_submitted',
|
||||
{
|
||||
orderNo: flow.orderNo,
|
||||
submitStatus: result.status,
|
||||
providerOrderNo: result.providerOrderNo,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交 UID 后触发 affiliate-dash 绑定(bind),把 bind_uuid / 二维码写回上下文,
|
||||
* task → waiting_binding(link_generated → waiting_binding 合法)。
|
||||
*/
|
||||
async function bindAffiliateDashClaimForTask(task: TaskRow, gameAccount: string) {
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo || !gameAccount) {
|
||||
return task
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const bindResult = await bindAffiliateDashDelivery({
|
||||
orderNo: flow.orderNo,
|
||||
gameAccount,
|
||||
})
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
bindUuid: bindResult.bindUuid,
|
||||
bindUrl: bindResult.bindUrl,
|
||||
qrUrl: bindResult.qrUrl,
|
||||
gameAccount,
|
||||
}
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'affiliate_dash_bind_created',
|
||||
{
|
||||
orderNo: flow.orderNo,
|
||||
bindUuid: bindResult.bindUuid,
|
||||
gameAccount,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return updatedTask || task
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询绑定状态:详情查询时若有 bind_uuid,拉取 affiliate-dash bind-result 实时刷新
|
||||
* bound / 绑定账号 / mismatch 到上下文(可重入,拉取失败不阻塞详情)。
|
||||
*/
|
||||
async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | null> {
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo || !flow.bindUuid) {
|
||||
return task
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await getAffiliateDashBindResult({
|
||||
orderNo: flow.orderNo,
|
||||
bindUuid: flow.bindUuid,
|
||||
})
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
bound: result.bound,
|
||||
boundAccount: result.gameAccount || flow.boundAccount,
|
||||
gameChannel: result.gameChannel || flow.gameChannel,
|
||||
bindMismatch: result.mismatch,
|
||||
}
|
||||
const now = nowIso()
|
||||
|
||||
return (
|
||||
(await updateTask(task.id, {
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
updated_at: now,
|
||||
})) || task
|
||||
)
|
||||
} catch {
|
||||
// 绑定状态拉取失败:保持现状,前端可继续轮询
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
export async function rebindKuaishouCloudClaimRole(token: unknown) {
|
||||
const context = await getClaimContext(token)
|
||||
assertLewanClaimTask(context.task)
|
||||
|
||||
Reference in New Issue
Block a user