修复核销提前导致的链接不初始化错误
This commit is contained in:
@@ -51,6 +51,34 @@ async function requireExecutorAction<T>(
|
||||
return value
|
||||
}
|
||||
|
||||
function isKuaishouCloudBindingReady(flow: ReturnType<typeof normalizeKuaishouCloudFlow>) {
|
||||
return flow.binding.prepareStatus === 'ready' && Boolean(String(flow.binding.bindUrl || '').trim())
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 Cloud 绑定资源已准备(取号 + 绑链)。
|
||||
* 提前核销后仍可能未 prepare,不能因 consume.success 跳过。
|
||||
*/
|
||||
async function ensureKuaishouCloudBindingPrepared(
|
||||
task: TaskRow,
|
||||
options: { source: string; actorSource?: string } = { source: 'system_auto_prepare_binding' },
|
||||
): Promise<TaskRow> {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
if (isKuaishouCloudBindingReady(flow)) {
|
||||
return task
|
||||
}
|
||||
|
||||
try {
|
||||
const prepared = await prepareFulfillmentBinding(task, {
|
||||
source: options.source,
|
||||
actor: { source: options.actorSource || options.source },
|
||||
})
|
||||
return prepared?.task || task
|
||||
} catch {
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyIndustryVoucherTicket(
|
||||
context: Awaited<ReturnType<typeof getClaimContext>>,
|
||||
now: string,
|
||||
@@ -58,8 +86,19 @@ async function verifyIndustryVoucherTicket(
|
||||
const taskContext = parseTaskContext(context.task)
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||
|
||||
// 提前核销后 consume 已是 success,但仍可能未准备绑定资源;禁止整段短路。
|
||||
// 注意:不要再调 getKuaishouCloudClaimDetail,避免 prepare 失败时递归。
|
||||
if (flow.consume.status === 'success') {
|
||||
return getKuaishouCloudClaimDetail(context.claimToken.token)
|
||||
const task = await ensureKuaishouCloudBindingPrepared(context.task, {
|
||||
source: 'claim_after_early_consume_prepare',
|
||||
actorSource: 'system',
|
||||
})
|
||||
return buildClaimDetailPayload({
|
||||
claimToken: context.claimToken,
|
||||
task,
|
||||
order: context.order,
|
||||
orderItem: context.orderItem,
|
||||
})
|
||||
}
|
||||
|
||||
const industryContext = typeof taskContext === 'object' ? taskContext : {}
|
||||
@@ -144,10 +183,11 @@ async function verifyIndustryVoucherTicket(
|
||||
}
|
||||
|
||||
const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment)
|
||||
if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) {
|
||||
await prepareFulfillmentBinding(taskWithTicket, {
|
||||
let taskAfterPrepare = taskWithTicket
|
||||
if (!isKuaishouCloudBindingReady(preparedFlow)) {
|
||||
taskAfterPrepare = await ensureKuaishouCloudBindingPrepared(taskWithTicket, {
|
||||
source: 'send_code_callback',
|
||||
actor: { source: 'send_code_callback' },
|
||||
actorSource: 'send_code_callback',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -163,7 +203,12 @@ async function verifyIndustryVoucherTicket(
|
||||
now,
|
||||
)
|
||||
|
||||
return getKuaishouCloudClaimDetail(context.claimToken.token)
|
||||
return buildClaimDetailPayload({
|
||||
claimToken: context.claimToken,
|
||||
task: taskAfterPrepare,
|
||||
order: context.order,
|
||||
orderItem: context.orderItem,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getKuaishouCloudClaimDetail(token: unknown): Promise<ClaimDetailPayload> {
|
||||
@@ -178,9 +223,14 @@ export async function getKuaishouCloudClaimDetail(token: unknown): Promise<Claim
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
if (flow.consume.status !== 'success') {
|
||||
// 已核销也要走 verify:内部会补 prepare 绑定资源
|
||||
if (flow.consume.status !== 'success' || !isKuaishouCloudBindingReady(flow)) {
|
||||
const now = nowIso()
|
||||
await verifyIndustryVoucherTicket(context, now).catch(() => null)
|
||||
const prepared: ClaimDetailPayload | null = await verifyIndustryVoucherTicket(context, now)
|
||||
.catch(() => null)
|
||||
if (prepared) {
|
||||
return prepared
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +241,7 @@ export async function getKuaishouCloudClaimDetail(token: unknown): Promise<Claim
|
||||
hasUsableIndustryVoucher(taskContext) &&
|
||||
(
|
||||
flow.ticket.status !== 'verified' ||
|
||||
(flow.binding.prepareStatus !== 'ready' && !flow.binding.bindUrl)
|
||||
!isKuaishouCloudBindingReady(flow)
|
||||
)
|
||||
|
||||
if (needsIndustryVoucherPrepare) {
|
||||
@@ -202,6 +252,20 @@ export async function getKuaishouCloudClaimDetail(token: unknown): Promise<Claim
|
||||
return prepared
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:不依赖 UID;凭证已确认但绑定未就绪时自动补 prepare(覆盖提前核销场景)
|
||||
if (!isKuaishouCloudMockTask(task)) {
|
||||
const latestFlow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
if (
|
||||
(latestFlow.ticket.status === 'verified' || hasUsableIndustryVoucher(parseTaskContext(task))) &&
|
||||
!isKuaishouCloudBindingReady(latestFlow)
|
||||
) {
|
||||
task = await ensureKuaishouCloudBindingPrepared(task, {
|
||||
source: 'claim_page_auto_binding_prepare',
|
||||
actorSource: 'system',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 已提交 UID 的 lewan 任务:详情查询时顺带刷新角色,便于前端轮询匹配状态
|
||||
|
||||
@@ -10,7 +10,12 @@ export async function syncKuaishouCloudRoleInfo(task: TaskRow) {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
|
||||
if (!flow.binding.vnId || !flow.binding.vnKey || !flow.binding.vnPhone) {
|
||||
if (flow.ticket.status === 'verified' && flow.binding.prepareStatus !== 'ready') {
|
||||
// ticket verified 或绑定尚未 ready:补 prepare(覆盖提前核销后未取号场景)
|
||||
if (
|
||||
flow.ticket.status === 'verified' ||
|
||||
flow.binding.prepareStatus !== 'ready' ||
|
||||
!flow.binding.bindUrl
|
||||
) {
|
||||
try {
|
||||
const prepared = await prepareFulfillmentBinding(task, {
|
||||
source: 'claim_page_retry_binding_prepare',
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* 把「核销时间」提前到链接交付时刻,降低「拿链即退」误退窗口。
|
||||
*
|
||||
* 语义:核销 = 领取链接已交付(售出闭环),≠ 游戏内道具已到账。
|
||||
*
|
||||
* 交付后会 best-effort 自动准备 Cloud 绑定资源(取号/绑链),避免
|
||||
* 「已核销却停在待准备资源」只能靠后台手动点「准备资源」。
|
||||
*/
|
||||
import {
|
||||
attachKuaishouIndustryVoucherToTask,
|
||||
@@ -11,8 +14,11 @@ import {
|
||||
import {
|
||||
consumeKuaishouIndustryVoucher,
|
||||
} from '../platforms/kuaishou-industry/voucher-service.js'
|
||||
import { prepareFulfillmentBinding } from '../fulfillment/executors/registry.js'
|
||||
import { normalizeKuaishouCloudFlow } from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import { listKuaishouIndustryVouchersByOid } from '../../repositories/kuaishou-industry-voucher-repo.js'
|
||||
import { logIntegration, logWarn } from '../../utils/logger.js'
|
||||
import { parseTaskContext } from '../../utils/task-json.js'
|
||||
import type {
|
||||
KuaishouIndustryVoucherRow,
|
||||
OrderRow,
|
||||
@@ -130,8 +136,18 @@ export async function consumeIndustryVouchersBeforeOpen91Deliver(options: {
|
||||
continue
|
||||
}
|
||||
|
||||
await attachKuaishouIndustryVoucherToTask(task, result.voucher, {
|
||||
const attachedTask =
|
||||
(await attachKuaishouIndustryVoucherToTask(task, result.voucher, {
|
||||
source,
|
||||
})) || task
|
||||
|
||||
// 核销后立刻补准备绑定资源;失败不阻断 cards(领取页仍会再兜底 prepare)
|
||||
await ensureBindingPreparedAfterDeliver({
|
||||
task: attachedTask,
|
||||
source,
|
||||
requestId: options.requestId || '',
|
||||
orderId: options.order.id,
|
||||
orderNo: oid,
|
||||
})
|
||||
|
||||
if (alreadyConsumed) {
|
||||
@@ -179,3 +195,50 @@ export async function consumeIndustryVouchersBeforeOpen91Deliver(options: {
|
||||
alreadyConsumedTaskIds,
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureBindingPreparedAfterDeliver(options: {
|
||||
task: TaskRow
|
||||
source: string
|
||||
requestId: string
|
||||
orderId: number
|
||||
orderNo: string
|
||||
}) {
|
||||
const executorKey = String(options.task.executor_key || '').trim()
|
||||
// lewan / 行业凭证任务才需要 Cloud 绑定资源
|
||||
if (
|
||||
executorKey !== 'kuaishou_ct_assisted' &&
|
||||
executorKey !== 'kuaishou-industry' &&
|
||||
!parseTaskContext(options.task).kuaishouCloudFulfillment
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const flow = normalizeKuaishouCloudFlow(
|
||||
parseTaskContext(options.task).kuaishouCloudFulfillment,
|
||||
)
|
||||
if (flow.binding.prepareStatus === 'ready' && String(flow.binding.bindUrl || '').trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await prepareFulfillmentBinding(options.task, {
|
||||
source: `${options.source}_prepare_binding`,
|
||||
actor: { source: options.source },
|
||||
})
|
||||
logIntegration('[open-91/query]', '91交付后自动准备绑定资源成功', {
|
||||
requestId: options.requestId,
|
||||
orderId: options.orderId,
|
||||
orderNo: options.orderNo,
|
||||
taskId: options.task.id,
|
||||
source: options.source,
|
||||
})
|
||||
} catch (error) {
|
||||
logWarn('[open-91/query]', '91交付后自动准备绑定资源失败(不阻断 cards,领取页将再试)', {
|
||||
requestId: options.requestId,
|
||||
orderId: options.orderId,
|
||||
orderNo: options.orderNo,
|
||||
taskId: options.task.id,
|
||||
error: error instanceof Error ? error.message : String(error || ''),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user