修正行业凭证发货回调金额参数

This commit is contained in:
yml2213
2026-07-08 12:32:43 +08:00
parent b9d1f57e3d
commit 588cf5f596
3 changed files with 197 additions and 7 deletions
@@ -23,7 +23,6 @@ type SendCallbackInput = {
totalGoodsValue?: number
token: string
eticketType?: string
ext?: string
expressCode?: string
expressNo?: string
}
@@ -77,7 +76,6 @@ export async function sendCallback(input: SendCallbackInput): Promise<{ success:
if (input.totalGoodsValue != null) bizParams.totalGoodsValue = input.totalGoodsValue
if (input.eticketType) bizParams.eticketType = input.eticketType
if (input.ext) bizParams.ext = input.ext
if (input.expressCode) bizParams.expressCode = input.expressCode
if (input.expressNo) bizParams.expressNo = input.expressNo
@@ -0,0 +1,80 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
resolveSendCallbackGoodsValuePlan,
resolveSendCallbackPreferredTotalGoodsValue,
} from './send-code-service.js'
test('resolveSendCallbackPreferredTotalGoodsValue converts 91 maxAmount yuan to fen', () => {
const total = resolveSendCallbackPreferredTotalGoodsValue({
raw_payload_json: JSON.stringify({
body: {
maxAmount: '0.01',
},
}),
total_amount: 100,
} as any)
assert.equal(total, 1)
})
test('resolveSendCallbackPreferredTotalGoodsValue falls back to order total amount', () => {
const total = resolveSendCallbackPreferredTotalGoodsValue({
raw_payload_json: '{}',
total_amount: 66,
} as any)
assert.equal(total, 66)
})
test('resolveSendCallbackGoodsValuePlan prefers 91 amount over ext payment', () => {
const plan = resolveSendCallbackGoodsValuePlan(
[{}],
'{"payment":100,"platformBearAmount":0}',
1,
)
assert.deepEqual(plan.goodsValues, [1])
assert.equal(plan.totalGoodsValue, 1)
assert.equal(plan.source, 'preferred')
})
test('resolveSendCallbackGoodsValuePlan splits preferred amount and keeps sum equal to total', () => {
const plan = resolveSendCallbackGoodsValuePlan(
[{}, {}, {}],
'{"payment":999}',
100,
)
assert.deepEqual(plan.goodsValues, [33, 33, 34])
assert.equal(plan.totalGoodsValue, 100)
assert.equal(plan.source, 'preferred')
})
test('resolveSendCallbackGoodsValuePlan uses ext payment for a single voucher', () => {
const plan = resolveSendCallbackGoodsValuePlan(
[{}],
'{"payment":1,"platformBearAmount":0}',
)
assert.deepEqual(plan.goodsValues, [1])
assert.equal(plan.totalGoodsValue, 1)
})
test('resolveSendCallbackGoodsValuePlan splits ext payment and keeps sum equal to total', () => {
const plan = resolveSendCallbackGoodsValuePlan(
[{}, {}, {}],
'{"payment":100}',
)
assert.deepEqual(plan.goodsValues, [33, 33, 34])
assert.equal(plan.totalGoodsValue, 100)
})
test('resolveSendCallbackGoodsValuePlan falls back to one weight per voucher', () => {
const plan = resolveSendCallbackGoodsValuePlan([{}, {}], '')
assert.deepEqual(plan.goodsValues, [1, 1])
assert.equal(plan.totalGoodsValue, 2)
})
@@ -21,6 +21,8 @@ import {
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'
type JsonObject = Record<string, any>
@@ -100,6 +102,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
token: params.token,
eticketType: params.eticketType,
ext: params.ext,
preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order),
})
return response
@@ -112,6 +115,7 @@ function fireSendCallback(input: {
token: string
eticketType?: string
ext?: string
preferredTotalGoodsValue?: number
}) {
const eticketItems = input.etickets.map((e) => ({
id: String(e.id || ''),
@@ -119,29 +123,38 @@ function fireSendCallback(input: {
num: Number(e.num) || 1,
validStartTime: Number(e.validStartTime) || 0,
validEndTime: Number(e.validEndTime) || 0,
goodsValue: Number(e.goodsValue) || 0,
}))
const goodsValuePlan = resolveSendCallbackGoodsValuePlan(
eticketItems,
input.ext,
input.preferredTotalGoodsValue,
)
const eticketItemsWithGoodsValue = eticketItems.map((item, index) => ({
...item,
goodsValue: goodsValuePlan.goodsValues[index] || 1,
}))
const sendNum = eticketItems.reduce((sum, e) => sum + e.num, 0)
const totalGoodsValue = eticketItems.reduce((sum, e) => sum + e.goodsValue, 0)
const totalGoodsValue = goodsValuePlan.totalGoodsValue
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调已调度', {
oid: input.oid,
sendType: input.sendType,
sendNum,
totalGoodsValue,
etickets: eticketItems,
goodsValueSource: goodsValuePlan.source,
preferredTotalGoodsValue: normalizePositiveInteger(input.preferredTotalGoodsValue),
etickets: eticketItemsWithGoodsValue,
eticketType: input.eticketType || '',
})
sendCallback({
oid: input.oid,
sendType: input.sendType,
etickets: eticketItems,
etickets: eticketItemsWithGoodsValue,
sendNum,
totalGoodsValue,
token: input.token,
...(input.eticketType ? { eticketType: input.eticketType } : {}),
...(input.ext ? { ext: input.ext } : {}),
}).then((result) => {
if (!result.success) {
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行失败', {
@@ -163,3 +176,102 @@ function fireSendCallback(input: {
}, { level: 'warn' })
})
}
export function resolveSendCallbackGoodsValuePlan(
etickets: Array<{ goodsValue?: unknown, [key: string]: unknown }>,
ext = '',
preferredTotalGoodsValue: unknown = null,
) {
const count = Math.max(etickets.length, 1)
const preferredTotal = normalizePositiveInteger(preferredTotalGoodsValue)
if (preferredTotal >= count) {
return buildSplitGoodsValuePlan(preferredTotal, count, 'preferred')
}
const extTotal = resolveGoodsValueTotalFromExt(ext)
if (extTotal >= count) {
return buildSplitGoodsValuePlan(extTotal, count, 'ext')
}
const explicitGoodsValues = etickets
.map((item) => normalizePositiveInteger(item.goodsValue))
.filter((value) => value > 0)
if (explicitGoodsValues.length === etickets.length && explicitGoodsValues.length > 0) {
return {
totalGoodsValue: explicitGoodsValues.reduce((sum, value) => sum + value, 0),
goodsValues: explicitGoodsValues,
source: 'eticket',
}
}
const goodsValues = Array.from({ length: count }, () => 1)
return {
totalGoodsValue: count,
goodsValues,
source: 'fallback',
}
}
export function resolveSendCallbackPreferredTotalGoodsValue(order: OrderRow | null): number {
if (!order) {
return 0
}
const rawPayload = parseJsonObject(order.raw_payload_json)
const body = parseJsonObject(rawPayload.body)
const maxAmountFen = normalizePositiveInteger(parseAmountToFen(body.maxAmount ?? rawPayload.maxAmount))
if (maxAmountFen > 0) {
return maxAmountFen
}
return normalizePositiveInteger(order.total_amount)
}
function buildSplitGoodsValuePlan(totalGoodsValue: number, count: number, source: string) {
const goodsValues = splitGoodsValue(totalGoodsValue, count)
return {
totalGoodsValue: goodsValues.reduce((sum, value) => sum + value, 0),
goodsValues,
source,
}
}
function splitGoodsValue(totalGoodsValue: number, count: number): number[] {
const base = Math.floor(totalGoodsValue / count)
if (base <= 0) {
return Array.from({ length: count }, () => 1)
}
return Array.from({ length: count }, (_item, index) =>
index === count - 1 ? totalGoodsValue - base * (count - 1) : base,
)
}
function resolveGoodsValueTotalFromExt(ext: unknown): number {
const parsed = parseJsonObject(ext)
return normalizePositiveInteger(
parsed.totalGoodsValue
?? parsed.goodsValue
?? parsed.payment
?? parsed.payAmount
?? parsed.amount,
)
}
function normalizePositiveInteger(value: unknown): number {
const parsed = Number(value)
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : 0
}
function parseJsonObject(value: unknown): JsonObject {
if (value && typeof value === 'object' && !Array.isArray(value)) {
return value as JsonObject
}
try {
const parsed = JSON.parse(String(value || '{}'))
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
} catch {
return {}
}
}