核销时间提前到 91 返回链接
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user