优化发码回调,失败停止整个流程
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user