修复电子凭证销毁与飞飞订单金额
This commit is contained in:
@@ -4,6 +4,7 @@ import assert from 'node:assert/strict'
|
|||||||
import {
|
import {
|
||||||
buildFeifeiPlatformOrderNo,
|
buildFeifeiPlatformOrderNo,
|
||||||
resolveKuaishouFeifeiClaimUrl,
|
resolveKuaishouFeifeiClaimUrl,
|
||||||
|
resolveKuaishouFeifeiPlatformAmount,
|
||||||
} from './index.js'
|
} from './index.js'
|
||||||
import type { TaskRow } from '../../../types/repository/rows.js'
|
import type { TaskRow } from '../../../types/repository/rows.js'
|
||||||
|
|
||||||
@@ -66,6 +67,27 @@ test('buildFeifeiPlatformOrderNo falls back to task number', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('resolveKuaishouFeifeiPlatformAmount converts order fen amount to yuan', () => {
|
||||||
|
assert.equal(resolveKuaishouFeifeiPlatformAmount({
|
||||||
|
totalAmountFen: 3800,
|
||||||
|
quantity: 1,
|
||||||
|
}), 38)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolveKuaishouFeifeiPlatformAmount splits amount by item quantity', () => {
|
||||||
|
assert.equal(resolveKuaishouFeifeiPlatformAmount({
|
||||||
|
totalAmountFen: 7600,
|
||||||
|
quantity: 2,
|
||||||
|
}), 38)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolveKuaishouFeifeiPlatformAmount skips empty amount', () => {
|
||||||
|
assert.equal(resolveKuaishouFeifeiPlatformAmount({
|
||||||
|
totalAmountFen: 0,
|
||||||
|
quantity: 1,
|
||||||
|
}), 0)
|
||||||
|
})
|
||||||
|
|
||||||
function createTask(patch: Partial<TaskRow> = {}): TaskRow {
|
function createTask(patch: Partial<TaskRow> = {}): TaskRow {
|
||||||
return {
|
return {
|
||||||
id: 123,
|
id: 123,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { getOrderItemById } from '../../../repositories/order-item-repo.js'
|
||||||
|
import { getOrderById } from '../../../repositories/order-repo.js'
|
||||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||||
import { updateTask } from '../../../repositories/task-repo.js'
|
import { updateTask } from '../../../repositories/task-repo.js'
|
||||||
import { createHttpError } from '../../../utils/http.js'
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
@@ -59,11 +61,17 @@ export async function prepareKuaishouFeifeiTask(task: TaskRow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const platformOrderNo = flow.platformOrderNo || buildFeifeiPlatformOrderNo(task)
|
const platformOrderNo = flow.platformOrderNo || buildFeifeiPlatformOrderNo(task)
|
||||||
const order = await createKuaishouFeifeiOrder({
|
const platformAmount = await resolveKuaishouFeifeiTaskPlatformAmount(task)
|
||||||
|
const orderInput: Parameters<typeof createKuaishouFeifeiOrder>[0] = {
|
||||||
platformOrderNo,
|
platformOrderNo,
|
||||||
productCode: flow.productCode,
|
productCode: flow.productCode,
|
||||||
platformBuyNum: 1,
|
platformBuyNum: 1,
|
||||||
})
|
}
|
||||||
|
if (platformAmount > 0) {
|
||||||
|
orderInput.platformAmount = platformAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await createKuaishouFeifeiOrder(orderInput)
|
||||||
const feifeiClaimUrl = resolveKuaishouFeifeiOrderClaimUrl(order)
|
const feifeiClaimUrl = resolveKuaishouFeifeiOrderClaimUrl(order)
|
||||||
if (!feifeiClaimUrl) {
|
if (!feifeiClaimUrl) {
|
||||||
throw createHttpError('kuaishou-feifei 未返回领取链接', {
|
throw createHttpError('kuaishou-feifei 未返回领取链接', {
|
||||||
@@ -96,6 +104,7 @@ export async function prepareKuaishouFeifeiTask(task: TaskRow) {
|
|||||||
platformOrderNo,
|
platformOrderNo,
|
||||||
orderNo: order.orderNo,
|
orderNo: order.orderNo,
|
||||||
productCode: flow.productCode,
|
productCode: flow.productCode,
|
||||||
|
platformAmount,
|
||||||
rechargeStatus: order.rechargeStatus,
|
rechargeStatus: order.rechargeStatus,
|
||||||
rechargeStatusLabel: order.rechargeStatusLabel,
|
rechargeStatusLabel: order.rechargeStatusLabel,
|
||||||
rechargeUrl: order.h5.rechargeUrl,
|
rechargeUrl: order.h5.rechargeUrl,
|
||||||
@@ -356,3 +365,36 @@ export function buildFeifeiPlatformOrderNo(task: TaskRow) {
|
|||||||
|
|
||||||
return `OS-FEIFEI-${task.id}`
|
return `OS-FEIFEI-${task.id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resolveKuaishouFeifeiTaskPlatformAmount(task: TaskRow) {
|
||||||
|
const [order, orderItem] = await Promise.all([
|
||||||
|
getOrderById(task.order_id),
|
||||||
|
getOrderItemById(task.order_item_id),
|
||||||
|
])
|
||||||
|
|
||||||
|
return resolveKuaishouFeifeiPlatformAmount({
|
||||||
|
totalAmountFen: order?.total_amount,
|
||||||
|
quantity: orderItem?.quantity,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveKuaishouFeifeiPlatformAmount({
|
||||||
|
totalAmountFen,
|
||||||
|
quantity,
|
||||||
|
}: {
|
||||||
|
totalAmountFen?: unknown
|
||||||
|
quantity?: unknown
|
||||||
|
} = {}) {
|
||||||
|
const totalFen = normalizePositiveFen(totalAmountFen)
|
||||||
|
if (totalFen <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedQuantity = Math.max(1, Math.trunc(Number(quantity || 1)) || 1)
|
||||||
|
return Number((totalFen / normalizedQuantity / 100).toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePositiveFen(value: unknown) {
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export async function destroyCallback(input: DestroyCallbackInput): Promise<{ su
|
|||||||
oid: input.oid,
|
oid: input.oid,
|
||||||
sellerId: input.sellerId || '',
|
sellerId: input.sellerId || '',
|
||||||
reason: input.reason,
|
reason: input.reason,
|
||||||
|
hasToken: Boolean(input.token),
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import { normalizeDestroyCodePayload } from './payload.js'
|
||||||
|
import { resolveDestroyCallbackToken } from './destroy-code-service.js'
|
||||||
|
|
||||||
|
test('normalizeDestroyCodePayload keeps destroy callback token when provided', () => {
|
||||||
|
const payload = normalizeDestroyCodePayload({
|
||||||
|
appkey: 'ks-test',
|
||||||
|
param: {
|
||||||
|
oid: '2618901686368642',
|
||||||
|
reason: 'USER_APPLY_REFUND',
|
||||||
|
token: 'destroy-token',
|
||||||
|
etickets: [
|
||||||
|
{
|
||||||
|
id: 'KSVW9JTD4FZM4VDHKBV',
|
||||||
|
num: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
sign: 'test-sign',
|
||||||
|
signMethod: 'MD5',
|
||||||
|
timestamp: 1783499780936,
|
||||||
|
version: '1',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(payload.token, 'destroy-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolveDestroyCallbackToken falls back to stored voucher token', () => {
|
||||||
|
const token = resolveDestroyCallbackToken([
|
||||||
|
{ token: '' },
|
||||||
|
{ token: ' stored-token ' },
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.equal(token, 'stored-token')
|
||||||
|
})
|
||||||
@@ -39,6 +39,7 @@ export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
|||||||
: vouchers
|
: vouchers
|
||||||
const callbackSellerId = params.sellerId
|
const callbackSellerId = params.sellerId
|
||||||
|| String(targetVouchers.find((voucher) => String(voucher.seller_id || '').trim())?.seller_id || '').trim()
|
|| String(targetVouchers.find((voucher) => String(voucher.seller_id || '').trim())?.seller_id || '').trim()
|
||||||
|
const callbackToken = params.token || resolveDestroyCallbackToken(targetVouchers)
|
||||||
|
|
||||||
for (const voucher of targetVouchers) {
|
for (const voucher of targetVouchers) {
|
||||||
const status = String(voucher.status || '').trim().toUpperCase()
|
const status = String(voucher.status || '').trim().toUpperCase()
|
||||||
@@ -80,11 +81,16 @@ export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
|||||||
goodsValue: e.goodsValue,
|
goodsValue: e.goodsValue,
|
||||||
})),
|
})),
|
||||||
reason: params.reason,
|
reason: params.reason,
|
||||||
|
token: callbackToken,
|
||||||
})
|
})
|
||||||
|
|
||||||
return buildIndustrySuccessResponse({ oid: normalizedOid })
|
return buildIndustrySuccessResponse({ oid: normalizedOid })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveDestroyCallbackToken(vouchers: Array<{ token?: unknown }> = []) {
|
||||||
|
return String(vouchers.find((voucher) => String(voucher.token || '').trim())?.token || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
function fireDestroyCallback(input: Parameters<typeof destroyCallback>[0]) {
|
function fireDestroyCallback(input: Parameters<typeof destroyCallback>[0]) {
|
||||||
destroyCallback(input).catch((err) => {
|
destroyCallback(input).catch((err) => {
|
||||||
logWarn('[kuaishou-industry/destroy-code]', '销毁回调异步执行异常', {
|
logWarn('[kuaishou-industry/destroy-code]', '销毁回调异步执行异常', {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function normalizeDestroyCodePayload(raw: JsonObject = {}) {
|
|||||||
oid: normalizeIndustryString(param.oid),
|
oid: normalizeIndustryString(param.oid),
|
||||||
sellerId: normalizeIndustryString(param.sellerId),
|
sellerId: normalizeIndustryString(param.sellerId),
|
||||||
reason: normalizeIndustryString(param.reason),
|
reason: normalizeIndustryString(param.reason),
|
||||||
|
token: normalizeIndustryString(param.token),
|
||||||
etickets: normalizeDestroyEtickets(param.etickets),
|
etickets: normalizeDestroyEtickets(param.etickets),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user