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

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
@@ -13,7 +13,10 @@ import {
buildIndustryErrorResponse,
buildIndustryQueryCodeData,
} from './response.js'
import { buildKuaishouIndustryEticketFromVoucher } from './voucher-service.js'
import {
buildKuaishouIndustryEticketFromVoucher,
isKuaishouIndustryVoucherSendCallbackSuccess,
} from './voucher-service.js'
type JsonObject = Record<string, any>
@@ -33,6 +36,9 @@ export async function handleQueryCode(rawBody: JsonObject = {}) {
if (!matched) {
return buildIndustryErrorResponse(4012005, `卡券不存在: ${params.eticketId}`)
}
if (!isKuaishouIndustryVoucherSendCallbackSuccess(matched)) {
return buildIndustryErrorResponse(4012005, `卡券尚未完成发码: ${params.eticketId}`)
}
const eticket = buildKuaishouIndustryEticketFromVoucher(matched, params.eticketType)
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) {
return buildIndustryErrorResponse(4012002, `订单不存在: ${normalizedOid}`)
}
@@ -1,6 +1,9 @@
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-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 { normalizeTimestampIso } from '../../../utils/time.js'
import { logIntegration } from '../../../utils/logger.js'
@@ -11,6 +14,7 @@ import {
import { normalizeSendCodePayload, assertSendCodePayload } from './payload.js'
import { assertKuaishouIndustrySignature } from './crypto.js'
import {
buildIndustryErrorResponse,
buildIndustrySuccessResponse,
buildIndustryEticketItem,
buildIndustrySendCodeData,
@@ -18,11 +22,12 @@ import {
import { sendCallback } from './send-callback-service.js'
import {
buildKuaishouIndustryEticketFromVoucher,
KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS,
resolveKuaishouIndustryVoucherValidity,
} from './voucher-service.js'
import { bindKuaishouIndustryVouchersToOrderTasks } from './voucher-binding-service.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>
@@ -47,7 +52,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
const targetTaskCount = params.num > 0 ? params.num : 1
const tasks = order ? await listTasksByOrderId(order.id) : []
const validity = resolveKuaishouIndustryVoucherValidity(params, nowMs)
const vouchers = []
const vouchers: KuaishouIndustryVoucherRow[] = []
for (let index = 0; index < targetTaskCount; index += 1) {
const unitIndex = index + 1
@@ -60,6 +65,10 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
orderId: order?.id || null,
taskId: task?.id || null,
status: 'UNUSED',
sendCallbackStatus: KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS.PENDING,
sendCallbackLastError: '',
sendCallbackResponseJson: {},
sendCallbackSentAt: null,
validStartTime: validity.validStartTime,
validEndTime: validity.validEndTime,
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) =>
buildKuaishouIndustryEticketFromVoucher(voucher, params.eticketType),
)
const response = buildIndustrySuccessResponse(
buildIndustrySendCodeData({
oid: normalizedOid,
sendType: params.sendType,
sendNum: etickets.length,
etickets,
}),
)
fireSendCallback({
const callbackResult = await sendAndRecordCallback({
oid: normalizedOid,
sendType: params.sendType,
etickets,
vouchers,
sellerId: params.sellerId,
token: params.token,
eticketType: params.eticketType,
@@ -107,13 +101,35 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
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
sendType: string
etickets: ReturnType<typeof buildIndustryEticketItem>[]
vouchers: KuaishouIndustryVoucherRow[]
sellerId: string
token: string
eticketType?: string
@@ -139,7 +155,7 @@ function fireSendCallback(input: {
const sendNum = eticketItems.reduce((sum, e) => sum + e.num, 0)
const totalGoodsValue = goodsValuePlan.totalGoodsValue
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调已调度', {
logIntegration('[kuaishou-industry/send-code]', '准备发起电子凭证发货回调', {
oid: input.oid,
sellerId: input.sellerId,
sendType: input.sendType,
@@ -151,35 +167,74 @@ function fireSendCallback(input: {
eticketType: input.eticketType || '',
})
sendCallback({
oid: input.oid,
sendType: input.sendType,
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]', '电子凭证发货回调异步执行完成', {
let result: Awaited<ReturnType<typeof sendCallback>>
try {
result = await sendCallback({
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) => {
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行异常', {
} catch (error) {
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,
error: err instanceof Error ? err.message : String(err),
error: result.error || '',
response: result.response || null,
}, { 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(
@@ -6,6 +6,11 @@ import {
import { normalizeTimestampIso } from '../../../utils/time.js'
import { parseTaskContext } from '../../../utils/task-json.js'
import { normalizeKuaishouCloudFlow } from '../../fulfillment/kuaishou-cloud/domain.js'
import {
isKuaishouIndustryVoucherSendCallbackSuccess,
normalizeKuaishouIndustrySendCallbackStatus,
resolveKuaishouIndustryVoucherSendCallbackMessage,
} from './voucher-service.js'
import type {
KuaishouIndustryVoucherRow,
OrderRow,
@@ -41,7 +46,7 @@ export async function bindKuaishouIndustryVouchersToOrderTasks(
const currentVoucher = nextVoucher || voucher
bound.push(currentVoucher)
if (task) {
if (task && isKuaishouIndustryVoucherSendCallbackSuccess(currentVoucher)) {
await attachKuaishouIndustryVoucherToTask(task, currentVoucher, {
source: options.source || 'voucher_bind',
now,
@@ -67,12 +72,16 @@ export async function attachKuaishouIndustryVoucherToTask(
context.kuaishouIndustryVoucher,
now,
)
const sendCallbackConfirmed = isKuaishouIndustryVoucherSendCallbackSuccess(voucher)
const nextContext: JsonObject = {
...context,
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 consumedAt = voucher.consumed_at || nextVoucherContext.consumedAt || null
const status = String(voucher.status || 'UNUSED').trim().toUpperCase()
@@ -143,6 +152,10 @@ export function buildKuaishouIndustryVoucherContext(
voucherCode: String(voucher.voucher_code || '').trim(),
unitIndex: Number(voucher.unit_index || 0) || 0,
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,
validEndTime: Number(voucher.valid_end_time || 0) || 0,
verifiedAt: existing.verifiedAt || now,
@@ -12,6 +12,11 @@ import type { KuaishouIndustryVoucherRow, TaskRow } from '../../../types/reposit
type JsonObject = Record<string, any>
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(
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(
task: TaskRow,
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 consumeTime = Number(input.consumeTime || Date.now()) || Date.now()
const consumeType = String(input.consumeType || 'delivery').trim() || 'delivery'