核销时间提前到 91 返回链接

This commit is contained in:
yml2213
2026-07-11 21:09:25 +08:00
parent b0c77e5842
commit bae8715f5d
4 changed files with 276 additions and 3 deletions
@@ -0,0 +1,181 @@
/**
* 91 卡券交付前核销:
* 在查询接口即将返回 orderStatus=20 + cards 前,同步核销行业电子凭证,
* 把「核销时间」提前到链接交付时刻,降低「拿链即退」误退窗口。
*
* 语义:核销 = 领取链接已交付(售出闭环),≠ 游戏内道具已到账。
*/
import {
attachKuaishouIndustryVoucherToTask,
} from '../platforms/kuaishou-industry/voucher-binding-service.js'
import {
consumeKuaishouIndustryVoucher,
} from '../platforms/kuaishou-industry/voucher-service.js'
import { listKuaishouIndustryVouchersByOid } from '../../repositories/kuaishou-industry-voucher-repo.js'
import { logIntegration, logWarn } from '../../utils/logger.js'
import type {
KuaishouIndustryVoucherRow,
OrderRow,
TaskRow,
} from '../../types/repository/rows.js'
export const OPEN_91_DELIVER_CONSUME_SOURCE = 'open91_query_deliver'
export type Open91DeliverConsumeResult =
| {
ok: true
consumedTaskIds: number[]
alreadyConsumedTaskIds: number[]
}
| {
ok: false
errorMessage: string
failedTaskIds: number[]
consumedTaskIds: number[]
alreadyConsumedTaskIds: number[]
}
/**
* 按 task_id / unit_index 解析任务对应的行业电子凭证(与 91 查询就绪判定一致)。
*/
export function resolveIndustryVoucherForTask(
task: Pick<TaskRow, 'id' | 'unit_index'>,
vouchers: KuaishouIndustryVoucherRow[],
): KuaishouIndustryVoucherRow | null {
const taskId = Number(task.id || 0)
const unitIndex = Number(task.unit_index || 0)
if (taskId > 0) {
const byTaskId = vouchers.find((row) => Number(row.task_id || 0) === taskId)
if (byTaskId) {
return byTaskId
}
}
if (unitIndex > 0) {
const byUnit = vouchers.find((row) => Number(row.unit_index || 0) === unitIndex)
if (byUnit) {
return byUnit
}
}
return null
}
function isVoucherConsumed(voucher: Pick<KuaishouIndustryVoucherRow, 'status'>): boolean {
return String(voucher.status || '').trim().toUpperCase() === 'CONSUMED'
}
/**
* 对即将交付给 91 的 ready 任务同步核销行业凭证(幂等)。
* 失败时调用方应返回 orderStatus=10,避免「未核销却已发链」。
*/
export async function consumeIndustryVouchersBeforeOpen91Deliver(options: {
order: OrderRow
tasks: TaskRow[]
readyTaskIds: number[]
requestId?: string
source?: string
}): Promise<Open91DeliverConsumeResult> {
const source = String(options.source || OPEN_91_DELIVER_CONSUME_SOURCE).trim()
|| OPEN_91_DELIVER_CONSUME_SOURCE
const readySet = new Set(
(Array.isArray(options.readyTaskIds) ? options.readyTaskIds : [])
.map((id) => Number(id))
.filter((id) => id > 0),
)
const readyTasks = (Array.isArray(options.tasks) ? options.tasks : []).filter((task) =>
readySet.has(Number(task.id || 0)),
)
if (readyTasks.length === 0) {
return {
ok: true,
consumedTaskIds: [],
alreadyConsumedTaskIds: [],
}
}
const oid = String(options.order.platform_order_id || '').trim()
const vouchers = oid ? await listKuaishouIndustryVouchersByOid(oid) : []
const consumedTaskIds: number[] = []
const alreadyConsumedTaskIds: number[] = []
const failed: Array<{ taskId: number; errorMessage: string }> = []
for (const task of readyTasks) {
const taskId = Number(task.id || 0)
const voucher = resolveIndustryVoucherForTask(task, vouchers)
if (!voucher) {
failed.push({
taskId,
errorMessage: '交付前核销失败:未找到对应电子凭证',
})
continue
}
const alreadyConsumed = isVoucherConsumed(voucher)
const result = await consumeKuaishouIndustryVoucher(voucher, {
source,
task,
token: String(voucher.token || '').trim(),
consumeTime: Date.now(),
})
if (!result.ok || !result.voucher) {
failed.push({
taskId,
errorMessage: result.errorMessage || '电子凭证核销回调失败',
})
continue
}
await attachKuaishouIndustryVoucherToTask(task, result.voucher, {
source,
})
if (alreadyConsumed) {
alreadyConsumedTaskIds.push(taskId)
} else {
consumedTaskIds.push(taskId)
}
}
if (failed.length > 0) {
const errorMessage = failed[0]?.errorMessage || '电子凭证核销失败,暂缓交付领取链接'
logWarn('[open-91/query]', '91交付前核销失败,暂不返回 cards', {
requestId: options.requestId || '',
orderId: options.order.id,
orderNo: oid,
failedTaskIds: failed.map((item) => item.taskId),
failed,
consumedTaskIds,
alreadyConsumedTaskIds,
errorMessage,
})
return {
ok: false,
errorMessage,
failedTaskIds: failed.map((item) => item.taskId),
consumedTaskIds,
alreadyConsumedTaskIds,
}
}
logIntegration('[open-91/query]', '91交付前核销完成', {
requestId: options.requestId || '',
orderId: options.order.id,
orderNo: oid,
readyTaskCount: readyTasks.length,
consumedTaskIds,
alreadyConsumedTaskIds,
source,
})
return {
ok: true,
consumedTaskIds,
alreadyConsumedTaskIds,
}
}