修正行业凭证发货回调金额参数
This commit is contained in:
@@ -23,7 +23,6 @@ type SendCallbackInput = {
|
|||||||
totalGoodsValue?: number
|
totalGoodsValue?: number
|
||||||
token: string
|
token: string
|
||||||
eticketType?: string
|
eticketType?: string
|
||||||
ext?: string
|
|
||||||
expressCode?: string
|
expressCode?: string
|
||||||
expressNo?: 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.totalGoodsValue != null) bizParams.totalGoodsValue = input.totalGoodsValue
|
||||||
if (input.eticketType) bizParams.eticketType = input.eticketType
|
if (input.eticketType) bizParams.eticketType = input.eticketType
|
||||||
if (input.ext) bizParams.ext = input.ext
|
|
||||||
if (input.expressCode) bizParams.expressCode = input.expressCode
|
if (input.expressCode) bizParams.expressCode = input.expressCode
|
||||||
if (input.expressNo) bizParams.expressNo = input.expressNo
|
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,
|
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 type { OrderRow } from '../../../types/repository/rows.js'
|
||||||
|
|
||||||
type JsonObject = Record<string, any>
|
type JsonObject = Record<string, any>
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
|
|||||||
token: params.token,
|
token: params.token,
|
||||||
eticketType: params.eticketType,
|
eticketType: params.eticketType,
|
||||||
ext: params.ext,
|
ext: params.ext,
|
||||||
|
preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order),
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -112,6 +115,7 @@ function fireSendCallback(input: {
|
|||||||
token: string
|
token: string
|
||||||
eticketType?: string
|
eticketType?: string
|
||||||
ext?: string
|
ext?: string
|
||||||
|
preferredTotalGoodsValue?: number
|
||||||
}) {
|
}) {
|
||||||
const eticketItems = input.etickets.map((e) => ({
|
const eticketItems = input.etickets.map((e) => ({
|
||||||
id: String(e.id || ''),
|
id: String(e.id || ''),
|
||||||
@@ -119,29 +123,38 @@ function fireSendCallback(input: {
|
|||||||
num: Number(e.num) || 1,
|
num: Number(e.num) || 1,
|
||||||
validStartTime: Number(e.validStartTime) || 0,
|
validStartTime: Number(e.validStartTime) || 0,
|
||||||
validEndTime: Number(e.validEndTime) || 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 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]', '电子凭证发货回调已调度', {
|
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调已调度', {
|
||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
sendType: input.sendType,
|
sendType: input.sendType,
|
||||||
sendNum,
|
sendNum,
|
||||||
totalGoodsValue,
|
totalGoodsValue,
|
||||||
etickets: eticketItems,
|
goodsValueSource: goodsValuePlan.source,
|
||||||
|
preferredTotalGoodsValue: normalizePositiveInteger(input.preferredTotalGoodsValue),
|
||||||
|
etickets: eticketItemsWithGoodsValue,
|
||||||
eticketType: input.eticketType || '',
|
eticketType: input.eticketType || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
sendCallback({
|
sendCallback({
|
||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
sendType: input.sendType,
|
sendType: input.sendType,
|
||||||
etickets: eticketItems,
|
etickets: eticketItemsWithGoodsValue,
|
||||||
sendNum,
|
sendNum,
|
||||||
totalGoodsValue,
|
totalGoodsValue,
|
||||||
token: input.token,
|
token: input.token,
|
||||||
...(input.eticketType ? { eticketType: input.eticketType } : {}),
|
...(input.eticketType ? { eticketType: input.eticketType } : {}),
|
||||||
...(input.ext ? { ext: input.ext } : {}),
|
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行失败', {
|
logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调异步执行失败', {
|
||||||
@@ -163,3 +176,102 @@ function fireSendCallback(input: {
|
|||||||
}, { level: 'warn' })
|
}, { 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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user