182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
/**
|
|
* 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,
|
|
}
|
|
}
|