优化发码回调,失败停止整个流程

This commit is contained in:
yml2213
2026-07-08 16:31:19 +08:00
parent 2a5080c6c4
commit 647f038061
11 changed files with 314 additions and 55 deletions
@@ -0,0 +1,9 @@
ALTER TABLE kuaishou_industry_vouchers
ADD COLUMN IF NOT EXISTS send_callback_status TEXT NOT NULL DEFAULT 'success',
ADD COLUMN IF NOT EXISTS send_callback_attempt_count INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS send_callback_last_error TEXT NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS send_callback_response_json JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS send_callback_sent_at TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_send_callback_status
ON kuaishou_industry_vouchers(send_callback_status);
@@ -63,6 +63,11 @@ async function insertKuaishouIndustryVoucher(
consume_details_json, consume_details_json,
consumed_at, consumed_at,
destroyed_at, destroyed_at,
send_callback_status,
send_callback_attempt_count,
send_callback_last_error,
send_callback_response_json,
send_callback_sent_at,
raw_payload_json, raw_payload_json,
created_at, created_at,
updated_at updated_at
@@ -82,9 +87,14 @@ async function insertKuaishouIndustryVoucher(
$12::jsonb, $12::jsonb,
$13, $13,
$14, $14,
$15::jsonb, $15,
$16, $16,
$17 $17,
$18::jsonb,
$19,
$20::jsonb,
$21,
$22
) )
ON CONFLICT (oid, unit_index) DO UPDATE ON CONFLICT (oid, unit_index) DO UPDATE
SET SET
@@ -100,6 +110,10 @@ async function insertKuaishouIndustryVoucher(
task_id = COALESCE(kuaishou_industry_vouchers.task_id, EXCLUDED.task_id), task_id = COALESCE(kuaishou_industry_vouchers.task_id, EXCLUDED.task_id),
valid_start_time = EXCLUDED.valid_start_time, valid_start_time = EXCLUDED.valid_start_time,
valid_end_time = EXCLUDED.valid_end_time, valid_end_time = EXCLUDED.valid_end_time,
send_callback_status = EXCLUDED.send_callback_status,
send_callback_last_error = EXCLUDED.send_callback_last_error,
send_callback_response_json = EXCLUDED.send_callback_response_json,
send_callback_sent_at = EXCLUDED.send_callback_sent_at,
raw_payload_json = EXCLUDED.raw_payload_json, raw_payload_json = EXCLUDED.raw_payload_json,
updated_at = EXCLUDED.updated_at updated_at = EXCLUDED.updated_at
RETURNING * RETURNING *
@@ -119,6 +133,11 @@ async function insertKuaishouIndustryVoucher(
stringifyJson(input.consumeDetailsJson ?? []), stringifyJson(input.consumeDetailsJson ?? []),
input.consumedAt || null, input.consumedAt || null,
input.destroyedAt || null, input.destroyedAt || null,
input.sendCallbackStatus || 'success',
normalizeNonNegativeInteger(input.sendCallbackAttemptCount, 0),
input.sendCallbackLastError || '',
stringifyJson(input.sendCallbackResponseJson ?? {}),
input.sendCallbackSentAt || null,
stringifyJson(input.rawPayloadJson ?? {}), stringifyJson(input.rawPayloadJson ?? {}),
input.createdAt, input.createdAt,
input.updatedAt, input.updatedAt,
@@ -277,6 +296,33 @@ function normalizeVoucherPatchColumns(
columns.push({ column: 'destroyed_at', value: patch.destroyedAt || null }) columns.push({ column: 'destroyed_at', value: patch.destroyedAt || null })
} }
if (patch.sendCallbackStatus !== undefined) {
columns.push({ column: 'send_callback_status', value: patch.sendCallbackStatus || 'success' })
}
if (patch.sendCallbackAttemptCount !== undefined) {
columns.push({
column: 'send_callback_attempt_count',
value: normalizeNonNegativeInteger(patch.sendCallbackAttemptCount, 0),
})
}
if (patch.sendCallbackLastError !== undefined) {
columns.push({ column: 'send_callback_last_error', value: patch.sendCallbackLastError || '' })
}
if (patch.sendCallbackResponseJson !== undefined) {
columns.push({
column: 'send_callback_response_json',
value: stringifyJson(patch.sendCallbackResponseJson ?? {}),
cast: '::jsonb',
})
}
if (patch.sendCallbackSentAt !== undefined) {
columns.push({ column: 'send_callback_sent_at', value: patch.sendCallbackSentAt || null })
}
if (patch.rawPayloadJson !== undefined) { if (patch.rawPayloadJson !== undefined) {
columns.push({ columns.push({
column: 'raw_payload_json', column: 'raw_payload_json',
@@ -337,6 +383,11 @@ function normalizeNullableId(value: unknown): number | null {
return Number.isFinite(parsed) && parsed > 0 ? parsed : null return Number.isFinite(parsed) && parsed > 0 ? parsed : null
} }
function normalizeNonNegativeInteger(value: unknown, fallback: number) {
const parsed = Number(value)
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback
}
function stringifyJson(value: unknown): string { function stringifyJson(value: unknown): string {
if (typeof value === 'string') { if (typeof value === 'string') {
return value return value
@@ -638,7 +638,8 @@ function hasKuaishouIndustryVoucherContext(value: JsonObject): boolean {
: {}; : {};
const voucherCode = String(voucher.voucherCode || voucher.eticketId || "").trim(); const voucherCode = String(voucher.voucherCode || voucher.eticketId || "").trim();
const status = String(voucher.status || "UNUSED").trim().toUpperCase(); const status = String(voucher.status || "UNUSED").trim().toUpperCase();
return Boolean(voucherCode && status !== "DESTROYED"); const sendCallbackStatus = String(voucher.sendCallbackStatus || "success").trim().toLowerCase();
return Boolean(voucherCode && status !== "DESTROYED" && sendCallbackStatus === "success");
} }
export function buildDispatchStockItems( export function buildDispatchStockItems(
@@ -92,7 +92,7 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
outTradeNo: '', outTradeNo: '',
orderStatus: 30, orderStatus: 30,
failCode: OPEN_91_DEFAULT_FAIL_CODE, failCode: OPEN_91_DEFAULT_FAIL_CODE,
failReason: '商品未配置或订单无法履约', failReason: resolveOpen91CreateFailReason(result.ignoreReason),
}) })
} }
@@ -104,3 +104,11 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
cards: '', cards: '',
}) })
} }
function resolveOpen91CreateFailReason(ignoreReason: unknown) {
if (String(ignoreReason || '').trim() === 'kuaishou_industry_send_callback_unconfirmed') {
return '电子凭证发码回调未成功,订单暂不能履约'
}
return '商品未配置或订单无法履约'
}
@@ -4,9 +4,14 @@ import {
updateOrder, updateOrder,
} from '../../repositories/order-repo.js' } from '../../repositories/order-repo.js'
import { replaceOrderItems } from '../../repositories/order-item-repo.js' import { replaceOrderItems } from '../../repositories/order-item-repo.js'
import { listKuaishouIndustryVouchersByOid } from '../../repositories/kuaishou-industry-voucher-repo.js'
import { syncDeliveryTasksForOrder } from './delivery-task-service.js' import { syncDeliveryTasksForOrder } from './delivery-task-service.js'
import { resolveOrderItemForFulfillment } from './product-match-service.js' import { resolveOrderItemForFulfillment } from './product-match-service.js'
import { bindKuaishouIndustryVouchersToOrderTasks } from '../platforms/kuaishou-industry/voucher-binding-service.js' import { bindKuaishouIndustryVouchersToOrderTasks } from '../platforms/kuaishou-industry/voucher-binding-service.js'
import {
isKuaishouIndustryVoucherSendCallbackSuccess,
resolveKuaishouIndustryVoucherSendCallbackMessage,
} from '../platforms/kuaishou-industry/voucher-service.js'
import { nowIso } from '../../utils/time.js' import { nowIso } from '../../utils/time.js'
import { logIntegration } from '../../utils/logger.js' import { logIntegration } from '../../utils/logger.js'
import { createHttpError } from '../../utils/http.js' import { createHttpError } from '../../utils/http.js'
@@ -184,6 +189,25 @@ export async function upsertOrderFromSource(
})), })),
) )
const industryVoucherGate = await resolveKuaishouIndustryVoucherGate(order.platform_order_id)
if (!industryVoucherGate.allowed) {
logIntegration('[order-service]', `${sourceLabel} 订单暂停履约:电子凭证发码回调未确认`, {
orderId: order.id,
provider: order.provider,
platform: order.platform,
platformOrderId: order.platform_order_id,
voucherCount: industryVoucherGate.voucherCount,
reason: industryVoucherGate.reason,
}, { level: 'warn' })
return {
ignoreReason: 'kuaishou_industry_send_callback_unconfirmed',
order,
orderItems,
tasks: [],
}
}
const tasks = await syncDeliveryTasksForOrder(order, orderItems) const tasks = await syncDeliveryTasksForOrder(order, orderItems)
await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, { await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, {
source: `${sourceLabel}_order_upsert`, source: `${sourceLabel}_order_upsert`,
@@ -209,6 +233,36 @@ export async function upsertOrderFromSource(
} }
} }
async function resolveKuaishouIndustryVoucherGate(platformOrderId: unknown) {
const oid = String(platformOrderId || '').trim()
if (!oid) {
return {
allowed: true,
voucherCount: 0,
reason: '',
}
}
const vouchers = await listKuaishouIndustryVouchersByOid(oid)
if (vouchers.length === 0 || vouchers.every(isKuaishouIndustryVoucherSendCallbackSuccess)) {
return {
allowed: true,
voucherCount: vouchers.length,
reason: '',
}
}
const blockedVoucher = vouchers.find((voucher) => !isKuaishouIndustryVoucherSendCallbackSuccess(voucher))
return {
allowed: false,
voucherCount: vouchers.length,
reason: blockedVoucher
? resolveKuaishouIndustryVoucherSendCallbackMessage(blockedVoucher)
: '电子凭证发码回调未确认',
}
}
const ORDER_STATUS_PRIORITY = { const ORDER_STATUS_PRIORITY = {
created: 0, created: 0,
paid: 1, paid: 1,
@@ -13,7 +13,10 @@ import {
buildIndustryErrorResponse, buildIndustryErrorResponse,
buildIndustryQueryCodeData, buildIndustryQueryCodeData,
} from './response.js' } from './response.js'
import { buildKuaishouIndustryEticketFromVoucher } from './voucher-service.js' import {
buildKuaishouIndustryEticketFromVoucher,
isKuaishouIndustryVoucherSendCallbackSuccess,
} from './voucher-service.js'
type JsonObject = Record<string, any> type JsonObject = Record<string, any>
@@ -33,6 +36,9 @@ export async function handleQueryCode(rawBody: JsonObject = {}) {
if (!matched) { if (!matched) {
return buildIndustryErrorResponse(4012005, `卡券不存在: ${params.eticketId}`) return buildIndustryErrorResponse(4012005, `卡券不存在: ${params.eticketId}`)
} }
if (!isKuaishouIndustryVoucherSendCallbackSuccess(matched)) {
return buildIndustryErrorResponse(4012005, `卡券尚未完成发码: ${params.eticketId}`)
}
const eticket = buildKuaishouIndustryEticketFromVoucher(matched, params.eticketType) const eticket = buildKuaishouIndustryEticketFromVoucher(matched, params.eticketType)
return buildIndustrySuccessResponse( return buildIndustrySuccessResponse(
@@ -45,7 +51,8 @@ export async function handleQueryCode(rawBody: JsonObject = {}) {
) )
} }
const vouchers = await listKuaishouIndustryVouchersByOid(normalizedOid) const vouchers = (await listKuaishouIndustryVouchersByOid(normalizedOid))
.filter(isKuaishouIndustryVoucherSendCallbackSuccess)
if (vouchers.length === 0) { if (vouchers.length === 0) {
return buildIndustryErrorResponse(4012002, `订单不存在: ${normalizedOid}`) return buildIndustryErrorResponse(4012002, `订单不存在: ${normalizedOid}`)
} }
@@ -1,6 +1,9 @@
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js' import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
import { listTasksByOrderId } from '../../../repositories/task-repo.js' import { listTasksByOrderId } from '../../../repositories/task-repo.js'
import { upsertKuaishouIndustryVoucher } from '../../../repositories/kuaishou-industry-voucher-repo.js' import {
updateKuaishouIndustryVoucherByCode,
upsertKuaishouIndustryVoucher,
} from '../../../repositories/kuaishou-industry-voucher-repo.js'
import { OPEN_91_PLATFORM, OPEN_91_PROVIDER } from '../../open-91/config.js' import { OPEN_91_PLATFORM, OPEN_91_PROVIDER } from '../../open-91/config.js'
import { normalizeTimestampIso } from '../../../utils/time.js' import { normalizeTimestampIso } from '../../../utils/time.js'
import { logIntegration } from '../../../utils/logger.js' import { logIntegration } from '../../../utils/logger.js'
@@ -11,6 +14,7 @@ import {
import { normalizeSendCodePayload, assertSendCodePayload } from './payload.js' import { normalizeSendCodePayload, assertSendCodePayload } from './payload.js'
import { assertKuaishouIndustrySignature } from './crypto.js' import { assertKuaishouIndustrySignature } from './crypto.js'
import { import {
buildIndustryErrorResponse,
buildIndustrySuccessResponse, buildIndustrySuccessResponse,
buildIndustryEticketItem, buildIndustryEticketItem,
buildIndustrySendCodeData, buildIndustrySendCodeData,
@@ -18,11 +22,12 @@ import {
import { sendCallback } from './send-callback-service.js' import { sendCallback } from './send-callback-service.js'
import { import {
buildKuaishouIndustryEticketFromVoucher, buildKuaishouIndustryEticketFromVoucher,
KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS,
resolveKuaishouIndustryVoucherValidity, resolveKuaishouIndustryVoucherValidity,
} from './voucher-service.js' } from './voucher-service.js'
import { bindKuaishouIndustryVouchersToOrderTasks } from './voucher-binding-service.js' import { bindKuaishouIndustryVouchersToOrderTasks } from './voucher-binding-service.js'
import { parseAmountToFen } from '../../../utils/money.js' import { parseAmountToFen } from '../../../utils/money.js'
import type { OrderRow } from '../../../types/repository/rows.js' import type { KuaishouIndustryVoucherRow, OrderRow } from '../../../types/repository/rows.js'
type JsonObject = Record<string, any> type JsonObject = Record<string, any>
@@ -47,7 +52,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
const targetTaskCount = params.num > 0 ? params.num : 1 const targetTaskCount = params.num > 0 ? params.num : 1
const tasks = order ? await listTasksByOrderId(order.id) : [] const tasks = order ? await listTasksByOrderId(order.id) : []
const validity = resolveKuaishouIndustryVoucherValidity(params, nowMs) const validity = resolveKuaishouIndustryVoucherValidity(params, nowMs)
const vouchers = [] const vouchers: KuaishouIndustryVoucherRow[] = []
for (let index = 0; index < targetTaskCount; index += 1) { for (let index = 0; index < targetTaskCount; index += 1) {
const unitIndex = index + 1 const unitIndex = index + 1
@@ -60,6 +65,10 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
orderId: order?.id || null, orderId: order?.id || null,
taskId: task?.id || null, taskId: task?.id || null,
status: 'UNUSED', status: 'UNUSED',
sendCallbackStatus: KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.PENDING,
sendCallbackLastError: '',
sendCallbackResponseJson: {},
sendCallbackSentAt: null,
validStartTime: validity.validStartTime, validStartTime: validity.validStartTime,
validEndTime: validity.validEndTime, validEndTime: validity.validEndTime,
rawPayloadJson: { rawPayloadJson: {
@@ -76,30 +85,15 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
} }
} }
if (order) {
await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, {
source: 'kuaishou_industry_send_code',
now,
})
}
const etickets = vouchers.map((voucher) => const etickets = vouchers.map((voucher) =>
buildKuaishouIndustryEticketFromVoucher(voucher, params.eticketType), buildKuaishouIndustryEticketFromVoucher(voucher, params.eticketType),
) )
const response = buildIndustrySuccessResponse( const callbackResult = await sendAndRecordCallback({
buildIndustrySendCodeData({
oid: normalizedOid,
sendType: params.sendType,
sendNum: etickets.length,
etickets,
}),
)
fireSendCallback({
oid: normalizedOid, oid: normalizedOid,
sendType: params.sendType, sendType: params.sendType,
etickets, etickets,
vouchers,
sellerId: params.sellerId, sellerId: params.sellerId,
token: params.token, token: params.token,
eticketType: params.eticketType, eticketType: params.eticketType,
@@ -107,13 +101,35 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order), preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order),
}) })
return response if (!callbackResult.success) {
return buildIndustryErrorResponse(
4010003,
resolveSendCallbackFailureMessage(callbackResult),
)
}
if (order) {
await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, {
source: 'kuaishou_industry_send_code',
now,
})
}
return buildIndustrySuccessResponse(
buildIndustrySendCodeData({
oid: normalizedOid,
sendType: params.sendType,
sendNum: etickets.length,
etickets,
}),
)
} }
function fireSendCallback(input: { async function sendAndRecordCallback(input: {
oid: string oid: string
sendType: string sendType: string
etickets: ReturnType<typeof buildIndustryEticketItem>[] etickets: ReturnType<typeof buildIndustryEticketItem>[]
vouchers: KuaishouIndustryVoucherRow[]
sellerId: string sellerId: string
token: string token: string
eticketType?: string eticketType?: string
@@ -139,7 +155,7 @@ function fireSendCallback(input: {
const sendNum = eticketItems.reduce((sum, e) => sum + e.num, 0) const sendNum = eticketItems.reduce((sum, e) => sum + e.num, 0)
const totalGoodsValue = goodsValuePlan.totalGoodsValue const totalGoodsValue = goodsValuePlan.totalGoodsValue
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调已调度', { logIntegration('[kuaishou-industry/send-code]', '准备发起电子凭证发货回调', {
oid: input.oid, oid: input.oid,
sellerId: input.sellerId, sellerId: input.sellerId,
sendType: input.sendType, sendType: input.sendType,
@@ -151,35 +167,74 @@ function fireSendCallback(input: {
eticketType: input.eticketType || '', eticketType: input.eticketType || '',
}) })
sendCallback({ let result: Awaited<ReturnType<typeof sendCallback>>
oid: input.oid, try {
sendType: input.sendType, result = await sendCallback({
etickets: eticketItemsWithGoodsValue,
sellerId: input.sellerId,
sendNum,
totalGoodsValue,
token: input.token,
...(input.eticketType ? { eticketType: input.eticketType } : {}),
}).then((result) => {
if (!result.success) {
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行失败', {
oid: input.oid,
error: result.error || '',
response: result.response || null,
}, { level: 'warn' })
return
}
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行完成', {
oid: input.oid, oid: input.oid,
response: result.response || null, sendType: input.sendType,
etickets: eticketItemsWithGoodsValue,
sellerId: input.sellerId,
sendNum,
totalGoodsValue,
token: input.token,
...(input.eticketType ? { eticketType: input.eticketType } : {}),
}) })
}).catch((err) => { } catch (error) {
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行异常', { result = {
success: false,
error: error instanceof Error ? error.message : String(error),
}
}
const sentAt = normalizeTimestampIso(new Date().toISOString())
await Promise.all(input.vouchers.map((voucher) =>
updateKuaishouIndustryVoucherByCode(voucher.voucher_code, {
sendCallbackStatus: result.success
? KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.SUCCESS
: KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.FAILED,
sendCallbackAttemptCount: normalizePositiveInteger(voucher.send_callback_attempt_count) + 1,
sendCallbackLastError: result.success ? '' : resolveSendCallbackFailureMessage(result),
sendCallbackResponseJson: result.response || {},
sendCallbackSentAt: sentAt,
updatedAt: sentAt,
}),
))
if (!result.success) {
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,发码请求已拒绝继续', {
oid: input.oid, oid: input.oid,
error: err instanceof Error ? err.message : String(err), error: result.error || '',
response: result.response || null,
}, { level: 'warn' }) }, { level: 'warn' })
return result
}
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行完成', {
oid: input.oid,
response: result.response || null,
}) })
return result
}
function resolveSendCallbackFailureMessage(result: Awaited<ReturnType<typeof sendCallback>>) {
const error = String(result.error || '').trim()
if (error) {
return error
}
const response = parseJsonObject(result.response)
const errorMsg = String(response.error_msg || response.message || response.msg || '').trim()
if (errorMsg) {
return errorMsg
}
const resultCode = String(response.result || response.code || '').trim()
if (resultCode) {
return `电子凭证发货回调失败,平台返回码:${resultCode}`
}
return '电子凭证发货回调失败'
} }
export function resolveSendCallbackGoodsValuePlan( export function resolveSendCallbackGoodsValuePlan(
@@ -6,6 +6,11 @@ import {
import { normalizeTimestampIso } from '../../../utils/time.js' import { normalizeTimestampIso } from '../../../utils/time.js'
import { parseTaskContext } from '../../../utils/task-json.js' import { parseTaskContext } from '../../../utils/task-json.js'
import { normalizeKuaishouCloudFlow } from '../../fulfillment/kuaishou-cloud/domain.js' import { normalizeKuaishouCloudFlow } from '../../fulfillment/kuaishou-cloud/domain.js'
import {
isKuaishouIndustryVoucherSendCallbackSuccess,
normalizeKuaishouIndustrySendCallbackStatus,
resolveKuaishouIndustryVoucherSendCallbackMessage,
} from './voucher-service.js'
import type { import type {
KuaishouIndustryVoucherRow, KuaishouIndustryVoucherRow,
OrderRow, OrderRow,
@@ -41,7 +46,7 @@ export async function bindKuaishouIndustryVouchersToOrderTasks(
const currentVoucher = nextVoucher || voucher const currentVoucher = nextVoucher || voucher
bound.push(currentVoucher) bound.push(currentVoucher)
if (task) { if (task && isKuaishouIndustryVoucherSendCallbackSuccess(currentVoucher)) {
await attachKuaishouIndustryVoucherToTask(task, currentVoucher, { await attachKuaishouIndustryVoucherToTask(task, currentVoucher, {
source: options.source || 'voucher_bind', source: options.source || 'voucher_bind',
now, now,
@@ -67,12 +72,16 @@ export async function attachKuaishouIndustryVoucherToTask(
context.kuaishouIndustryVoucher, context.kuaishouIndustryVoucher,
now, now,
) )
const sendCallbackConfirmed = isKuaishouIndustryVoucherSendCallbackSuccess(voucher)
const nextContext: JsonObject = { const nextContext: JsonObject = {
...context, ...context,
kuaishouIndustryVoucher: nextVoucherContext, kuaishouIndustryVoucher: nextVoucherContext,
} }
if (String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' || context.kuaishouCloudFulfillment) { if (
sendCallbackConfirmed &&
(String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' || context.kuaishouCloudFulfillment)
) {
const flow = normalizeKuaishouCloudFlow(context.kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(context.kuaishouCloudFulfillment)
const consumedAt = voucher.consumed_at || nextVoucherContext.consumedAt || null const consumedAt = voucher.consumed_at || nextVoucherContext.consumedAt || null
const status = String(voucher.status || 'UNUSED').trim().toUpperCase() const status = String(voucher.status || 'UNUSED').trim().toUpperCase()
@@ -143,6 +152,10 @@ export function buildKuaishouIndustryVoucherContext(
voucherCode: String(voucher.voucher_code || '').trim(), voucherCode: String(voucher.voucher_code || '').trim(),
unitIndex: Number(voucher.unit_index || 0) || 0, unitIndex: Number(voucher.unit_index || 0) || 0,
status, status,
sendCallbackStatus: normalizeKuaishouIndustrySendCallbackStatus(voucher.send_callback_status),
sendCallbackLastError: resolveKuaishouIndustryVoucherSendCallbackMessage(voucher),
sendCallbackAttemptCount: Number(voucher.send_callback_attempt_count || 0) || 0,
sendCallbackSentAt: voucher.send_callback_sent_at || existing.sendCallbackSentAt || null,
validStartTime: Number(voucher.valid_start_time || 0) || 0, validStartTime: Number(voucher.valid_start_time || 0) || 0,
validEndTime: Number(voucher.valid_end_time || 0) || 0, validEndTime: Number(voucher.valid_end_time || 0) || 0,
verifiedAt: existing.verifiedAt || now, verifiedAt: existing.verifiedAt || now,
@@ -12,6 +12,11 @@ import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/reposit
type JsonObject = Record<string, any> type JsonObject = Record<string, any>
export const KUAISHOU_INDUSTRY_DEFAULT_VALID_DAYS = 3 export const KUAISHOU_INDUSTRY_DEFAULT_VALID_DAYS = 3
export const KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS = {
PENDING: 'pending',
SUCCESS: 'success',
FAILED: 'failed',
} as const
export function resolveKuaishouIndustryVoucherValidity( export function resolveKuaishouIndustryVoucherValidity(
input: { input: {
@@ -61,6 +66,43 @@ export function buildKuaishouIndustryEticketFromVoucher(
}) })
} }
export function normalizeKuaishouIndustrySendCallbackStatus(value: unknown) {
const normalized = String(value || '').trim().toLowerCase()
if (
normalized === KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.PENDING ||
normalized === KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.FAILED
) {
return normalized
}
return KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.SUCCESS
}
export function isKuaishouIndustryVoucherSendCallbackSuccess(
voucher: Pick<KuaishouIndustryVoucherRow, 'send_callback_status'>,
) {
return normalizeKuaishouIndustrySendCallbackStatus(voucher.send_callback_status) ===
KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.SUCCESS
}
export function resolveKuaishouIndustryVoucherSendCallbackMessage(
voucher: Pick<KuaishouIndustryVoucherRow, 'send_callback_status' | 'send_callback_last_error'>,
) {
const status = normalizeKuaishouIndustrySendCallbackStatus(voucher.send_callback_status)
if (status === KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.SUCCESS) {
return ''
}
const errorMessage = String(voucher.send_callback_last_error || '').trim()
if (errorMessage) {
return errorMessage
}
return status === KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.PENDING
? '电子凭证发码回调尚未成功,暂不能继续履约'
: '电子凭证发码回调失败,暂不能继续履约'
}
export async function consumeKuaishouIndustryVouchersForTask( export async function consumeKuaishouIndustryVouchersForTask(
task: TaskRow, task: TaskRow,
input: { input: {
@@ -149,6 +191,15 @@ export async function consumeKuaishouIndustryVoucher(
} }
} }
if (!isKuaishouIndustryVoucherSendCallbackSuccess(voucher)) {
return {
ok: false,
voucher,
callbackSuccess: false,
errorMessage: resolveKuaishouIndustryVoucherSendCallbackMessage(voucher),
}
}
const now = new Date().toISOString() const now = new Date().toISOString()
const consumeTime = Number(input.consumeTime || Date.now()) || Date.now() const consumeTime = Number(input.consumeTime || Date.now()) || Date.now()
const consumeType = String(input.consumeType || 'delivery').trim() || 'delivery' const consumeType = String(input.consumeType || 'delivery').trim() || 'delivery'
@@ -142,6 +142,11 @@ export type KuaishouIndustryVoucherUpsertInput = {
consumeDetailsJson?: string | Record<string, unknown> | unknown[] consumeDetailsJson?: string | Record<string, unknown> | unknown[]
consumedAt?: string | null consumedAt?: string | null
destroyedAt?: string | null destroyedAt?: string | null
sendCallbackStatus?: string
sendCallbackAttemptCount?: number
sendCallbackLastError?: string
sendCallbackResponseJson?: string | Record<string, unknown>
sendCallbackSentAt?: string | null
rawPayloadJson?: string | Record<string, unknown> rawPayloadJson?: string | Record<string, unknown>
createdAt: string createdAt: string
updatedAt: string updatedAt: string
@@ -120,6 +120,11 @@ export type KuaishouIndustryVoucherRow = {
consume_details_json: string | Record<string, unknown> | unknown[] consume_details_json: string | Record<string, unknown> | unknown[]
consumed_at: string | null consumed_at: string | null
destroyed_at: string | null destroyed_at: string | null
send_callback_status: string
send_callback_attempt_count: number
send_callback_last_error: string
send_callback_response_json: string | Record<string, unknown>
send_callback_sent_at: string | null
raw_payload_json: string | Record<string, unknown> raw_payload_json: string | Record<string, unknown>
created_at: string created_at: string
updated_at: string updated_at: string