修正快手行业电子凭证核销参数

This commit is contained in:
yml2213
2026-07-09 19:46:40 +08:00
parent 179f34408b
commit 01171c1761
9 changed files with 77 additions and 35 deletions
@@ -104,11 +104,12 @@ export async function reverseAdminKuaishouIndustryVoucher(input: JsonObject = {}
export async function consumeAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) { export async function consumeAdminKuaishouIndustryVoucherByCode(input: JsonObject = {}) {
const voucher = await resolveRequiredVoucher(input) const voucher = await resolveRequiredVoucher(input)
const task = voucher.task_id ? await getTaskById(voucher.task_id) : null const task = voucher.task_id ? await getTaskById(voucher.task_id) : null
const consumeType = String(input.consumeType || '').trim()
const consumeInput: Parameters<typeof consumeKuaishouIndustryVoucher>[1] = { const consumeInput: Parameters<typeof consumeKuaishouIndustryVoucher>[1] = {
source: 'admin_tool_manual_consume', source: 'admin_tool_manual_consume',
token: String(input.token || voucher.token || '').trim(), token: String(input.token || voucher.token || '').trim(),
consumeType: String(input.consumeType || 'delivery').trim() || 'delivery',
consumeTime: Date.now(), consumeTime: Date.now(),
...(consumeType ? { consumeType } : {}),
...(input.serialNum ? { serialNum: String(input.serialNum).trim() } : {}), ...(input.serialNum ? { serialNum: String(input.serialNum).trim() } : {}),
...(input.eticketType ? { eticketType: String(input.eticketType).trim() } : {}), ...(input.eticketType ? { eticketType: String(input.eticketType).trim() } : {}),
...(input.storeName ? { storeName: String(input.storeName).trim() } : {}), ...(input.storeName ? { storeName: String(input.storeName).trim() } : {}),
@@ -857,12 +857,13 @@ export async function consumeAdminTaskKuaishouIndustryVoucher(
} }
const voucher = await getRequiredIndustryVoucherForTask(task) const voucher = await getRequiredIndustryVoucherForTask(task)
const consumeType = String(payload.consumeType || '').trim()
const result = await consumeKuaishouIndustryVoucher(voucher, { const result = await consumeKuaishouIndustryVoucher(voucher, {
source: 'admin_manual_consume', source: 'admin_manual_consume',
task, task,
token: voucher.token, token: voucher.token,
consumeType: String(payload.consumeType || 'delivery').trim() || 'delivery',
consumeTime: Date.now(), consumeTime: Date.now(),
...(consumeType ? { consumeType } : {}),
...(payload.storeName ? { storeName: payload.storeName } : {}), ...(payload.storeName ? { storeName: payload.storeName } : {}),
...(payload.storeAddress ? { storeAddress: payload.storeAddress } : {}), ...(payload.storeAddress ? { storeAddress: payload.storeAddress } : {}),
...(payload.expressCode ? { expressCode: payload.expressCode } : {}), ...(payload.expressCode ? { expressCode: payload.expressCode } : {}),
@@ -817,7 +817,6 @@ export async function returnKuaishouCloudFulfillmentTask(
? await consumeKuaishouIndustryVouchersForTask(task, { ? await consumeKuaishouIndustryVouchersForTask(task, {
source: String(options.source || "system_auto_finalize").trim() || "system_auto_finalize", source: String(options.source || "system_auto_finalize").trim() || "system_auto_finalize",
token: String(taskContext.kuaishouIndustryVoucher?.token || "").trim(), token: String(taskContext.kuaishouIndustryVoucher?.token || "").trim(),
consumeType: "delivery",
consumeTime: Date.now(), consumeTime: Date.now(),
}) })
: { ok: true, consumed: [], failed: [] }; : { ok: true, consumed: [], failed: [] };
@@ -146,7 +146,6 @@ export async function syncKuaishouFeifeiTaskStatus(task: TaskRow) {
if (order.rechargeStatus === 30) { if (order.rechargeStatus === 30) {
const consumeResult = await consumeKuaishouIndustryVouchersForTask(task, { const consumeResult = await consumeKuaishouIndustryVouchersForTask(task, {
source: 'kuaishou_feifei_completed', source: 'kuaishou_feifei_completed',
consumeType: 'delivery',
consumeTime: Date.now(), consumeTime: Date.now(),
}) })
const consumedVoucher = consumeResult.consumed[0] || null const consumedVoucher = consumeResult.consumed[0] || null
@@ -0,0 +1,26 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { buildConsumeCallbackBizParams } from './consume-callback-service.js'
test('buildConsumeCallbackBizParams omits eticket status', () => {
const bizParams = buildConsumeCallbackBizParams({
oid: '2619000086068982',
etickets: [{
id: 'KSVASXT6Q955WT4SS36',
num: 1,
status: 'CONSUMED',
} as any],
status: 'CONSUMED',
consumeType: 'consume',
consumeTime: 1783596649024,
token: 'ticket-token',
seriallNum: 'CONSUME-KSVASXT6Q955WT4SS36',
})
assert.deepEqual(bizParams.etickets, [{
id: 'KSVASXT6Q955WT4SS36',
num: 1,
}])
assert.equal(bizParams.consumeType, 'consume')
})
@@ -1,7 +1,7 @@
import { logInfo, logWarn } from '../../../utils/logger.js' import { logInfo, logWarn } from '../../../utils/logger.js'
import { requestKuaishouIndustryOpenApi, type JsonObject } from './openapi-client.js' import { requestKuaishouIndustryOpenApi, type JsonObject } from './openapi-client.js'
type ConsumeCallbackInput = { export type ConsumeCallbackInput = {
oid: string oid: string
sellerId?: string sellerId?: string
etickets: Array<{ etickets: Array<{
@@ -9,7 +9,6 @@ type ConsumeCallbackInput = {
code?: string code?: string
num: number num: number
goodsValue?: number goodsValue?: number
status?: string
}> }>
status: string status: string
consumeType: string consumeType: string
@@ -29,30 +28,7 @@ type ConsumeCallbackInput = {
export async function consumeCallback( export async function consumeCallback(
input: ConsumeCallbackInput, input: ConsumeCallbackInput,
): Promise<{ success: boolean; response?: JsonObject; error?: string }> { ): Promise<{ success: boolean; response?: JsonObject; error?: string }> {
const bizParams: JsonObject = { const bizParams = buildConsumeCallbackBizParams(input)
oid: input.oid,
etickets: input.etickets.map((e) => {
const item: JsonObject = { id: e.id, num: e.num }
if (e.code) item.code = e.code
if (e.goodsValue != null) item.goodsValue = e.goodsValue
if (e.status) item.status = e.status
return item
}),
status: input.status,
consumeType: input.consumeType,
token: input.token,
}
if (input.consumeTime != null) bizParams.consumeTime = input.consumeTime
if (input.storeName) bizParams.storeName = input.storeName
if (input.storeAddress) bizParams.storeAddress = input.storeAddress
if (input.expressNo) bizParams.expressNo = input.expressNo
if (input.expressCode) bizParams.expressCode = input.expressCode
if (input.appointmentTime) bizParams.appointmentTime = input.appointmentTime
if (input.eticketType) bizParams.eticketType = input.eticketType
if (input.ext) bizParams.ext = input.ext
if (input.seriallNum) bizParams.seriallNum = input.seriallNum
if (input.consumePoiId != null) bizParams.consumePoiId = input.consumePoiId
const result = await requestKuaishouIndustryOpenApi({ const result = await requestKuaishouIndustryOpenApi({
apiMethod: 'integration.callback.virtual.eticket.consume', apiMethod: 'integration.callback.virtual.eticket.consume',
@@ -115,3 +91,31 @@ export async function consumeCallback(
...(result.error ? { error: result.error } : {}), ...(result.error ? { error: result.error } : {}),
} }
} }
export function buildConsumeCallbackBizParams(input: ConsumeCallbackInput): JsonObject {
const bizParams: JsonObject = {
oid: input.oid,
etickets: input.etickets.map((e) => {
const item: JsonObject = { id: e.id, num: e.num }
if (e.code) item.code = e.code
if (e.goodsValue != null) item.goodsValue = e.goodsValue
return item
}),
status: input.status,
consumeType: input.consumeType,
token: input.token,
}
if (input.consumeTime != null) bizParams.consumeTime = input.consumeTime
if (input.storeName) bizParams.storeName = input.storeName
if (input.storeAddress) bizParams.storeAddress = input.storeAddress
if (input.expressNo) bizParams.expressNo = input.expressNo
if (input.expressCode) bizParams.expressCode = input.expressCode
if (input.appointmentTime) bizParams.appointmentTime = input.appointmentTime
if (input.eticketType) bizParams.eticketType = input.eticketType
if (input.ext) bizParams.ext = input.ext
if (input.seriallNum) bizParams.seriallNum = input.seriallNum
if (input.consumePoiId != null) bizParams.consumePoiId = input.consumePoiId
return bizParams
}
@@ -5,6 +5,7 @@ import { generateKuaishouIndustryVoucherCode } from '../../../repositories/kuais
import { buildKuaishouIndustryVoucherContext } from './voucher-binding-service.js' import { buildKuaishouIndustryVoucherContext } from './voucher-binding-service.js'
import { import {
buildKuaishouIndustryEticketFromVoucher, buildKuaishouIndustryEticketFromVoucher,
normalizeKuaishouIndustryConsumeType,
resolveKuaishouIndustryVoucherValidity, resolveKuaishouIndustryVoucherValidity,
} from './voucher-service.js' } from './voucher-service.js'
@@ -62,6 +63,12 @@ test('resolveKuaishouIndustryVoucherValidity prefers actual certificate time ran
assert.equal(validity.validEndTime, 5000) assert.equal(validity.validEndTime, 5000)
}) })
test('normalizeKuaishouIndustryConsumeType defaults to consume', () => {
assert.equal(normalizeKuaishouIndustryConsumeType(undefined), 'consume')
assert.equal(normalizeKuaishouIndustryConsumeType(''), 'consume')
assert.equal(normalizeKuaishouIndustryConsumeType(' pickup '), 'pickup')
})
test('buildKuaishouIndustryEticketFromVoucher mirrors voucher code into id and code', () => { test('buildKuaishouIndustryEticketFromVoucher mirrors voucher code into id and code', () => {
const eticket = buildKuaishouIndustryEticketFromVoucher({ const eticket = buildKuaishouIndustryEticketFromVoucher({
voucher_code: 'KSV15K821FDHPMTX1ST', voucher_code: 'KSV15K821FDHPMTX1ST',
@@ -17,6 +17,7 @@ export const KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS = {
SUCCESS: 'success', SUCCESS: 'success',
FAILED: 'failed', FAILED: 'failed',
} as const } as const
export const KUAISHOU_INDUSTRY_DEFAULT_CONSUME_TYPE = 'consume'
export function resolveKuaishouIndustryVoucherValidity( export function resolveKuaishouIndustryVoucherValidity(
input: { input: {
@@ -124,7 +125,7 @@ export async function consumeKuaishouIndustryVouchersForTask(
for (const voucher of vouchers) { for (const voucher of vouchers) {
const consumeInput: Parameters<typeof consumeKuaishouIndustryVoucher>[1] = { const consumeInput: Parameters<typeof consumeKuaishouIndustryVoucher>[1] = {
source: input.source || 'fulfillment_completed', source: input.source || 'fulfillment_completed',
consumeType: input.consumeType || 'delivery', consumeType: normalizeKuaishouIndustryConsumeType(input.consumeType),
consumeTime: input.consumeTime || Date.now(), consumeTime: input.consumeTime || Date.now(),
task, task,
} }
@@ -202,7 +203,7 @@ export async function consumeKuaishouIndustryVoucher(
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 = normalizeKuaishouIndustryConsumeType(input.consumeType)
const serialNum = String(input.serialNum || voucher.consume_serial_num || `CONSUME-${voucher.voucher_code}`).trim() const serialNum = String(input.serialNum || voucher.consume_serial_num || `CONSUME-${voucher.voucher_code}`).trim()
const token = String(input.token || voucher.token || '').trim() const token = String(input.token || voucher.token || '').trim()
const consumeDetail = { const consumeDetail = {
@@ -224,7 +225,6 @@ export async function consumeKuaishouIndustryVoucher(
etickets: [{ etickets: [{
id: voucher.voucher_code, id: voucher.voucher_code,
num: 1, num: 1,
status: 'CONSUMED',
}], }],
status: 'CONSUMED', status: 'CONSUMED',
consumeType, consumeType,
@@ -317,7 +317,7 @@ function resolveVoucherConsumeDetails(voucher: KuaishouIndustryVoucherRow): Json
return [{ return [{
serialNum: voucher.consume_serial_num, serialNum: voucher.consume_serial_num,
consumeType: 'delivery', consumeType: KUAISHOU_INDUSTRY_DEFAULT_CONSUME_TYPE,
consumeTime: voucher.consumed_at ? Date.parse(voucher.consumed_at) : 0, consumeTime: voucher.consumed_at ? Date.parse(voucher.consumed_at) : 0,
}] }]
} }
@@ -340,6 +340,11 @@ function normalizeVoucherStatus(value: unknown): string {
return 'UNUSED' return 'UNUSED'
} }
export function normalizeKuaishouIndustryConsumeType(value: unknown): string {
return String(value || KUAISHOU_INDUSTRY_DEFAULT_CONSUME_TYPE).trim() ||
KUAISHOU_INDUSTRY_DEFAULT_CONSUME_TYPE
}
function normalizePositiveTimestamp(value: unknown): number { function normalizePositiveTimestamp(value: unknown): number {
const parsed = Number(value) const parsed = Number(value)
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 0 return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 0
@@ -91,7 +91,7 @@ export default function AdminKuaishouIndustryPage() {
}) })
const [toolForm, setToolForm] = useState<AdminKuaishouIndustryVoucherToolPayload>({ const [toolForm, setToolForm] = useState<AdminKuaishouIndustryVoucherToolPayload>({
eticketType: DEFAULT_ETICKET_TYPE, eticketType: DEFAULT_ETICKET_TYPE,
consumeType: 'delivery', consumeType: 'consume',
}) })
const [refundDateRange, setRefundDateRange] = useState<DateRangeValue>(() => [ const [refundDateRange, setRefundDateRange] = useState<DateRangeValue>(() => [
dayjs().subtract(1, 'day'), dayjs().subtract(1, 'day'),