fix(open91): 履约未就绪时保持轮询等待
This commit is contained in:
@@ -3,14 +3,11 @@ import { logIntegration } from '../../utils/logger.js'
|
||||
import { parseAmountToFen } from '../../utils/money.js'
|
||||
import {
|
||||
buildOpen91SourceEvent,
|
||||
markOpen91OrderPendingConfig,
|
||||
upsertOpen91PendingOrder,
|
||||
} from '../platforms/ninetyone/order-service.js'
|
||||
import { notifyOpen91PendingConfig } from '../notification/domain-notifications.js'
|
||||
import {
|
||||
assertOpen91Config,
|
||||
OPEN_91_COST_EXCEED_FAIL_CODE,
|
||||
OPEN_91_DEFAULT_FAIL_CODE,
|
||||
} from './config.js'
|
||||
import { assertOpen91Config, OPEN_91_COST_EXCEED_FAIL_CODE } from './config.js'
|
||||
import {
|
||||
assertOpen91CreatePayload,
|
||||
assertOpen91Timestamp,
|
||||
@@ -23,6 +20,11 @@ import {
|
||||
buildOpen91SuccessResponse,
|
||||
} from './response.js'
|
||||
|
||||
/** 内部订单已经落库但履约任务尚未生成时,91 必须拿 10 + outTradeNo 继续轮询。 */
|
||||
export function shouldWaitForOpen91Fulfillment(tasks: unknown): boolean {
|
||||
return !Array.isArray(tasks) || tasks.length === 0
|
||||
}
|
||||
|
||||
export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
const config = assertOpen91Config()
|
||||
const normalized = normalizeOpen91CreatePayload(payload)
|
||||
@@ -86,26 +88,26 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
if (isWaitingKuaishouIndustrySendCallback(result.ignoreReason) && result.order) {
|
||||
if (shouldWaitForOpen91Fulfillment(result.tasks)) {
|
||||
const pendingOrder = await markOpen91OrderPendingConfig(
|
||||
result.order,
|
||||
result.ignoreReason || 'fulfillment_not_ready',
|
||||
)
|
||||
logIntegration('[open-91/create]', '91卡券订单等待履约就绪,已进入轮询', {
|
||||
requestId,
|
||||
orderNo: normalized.orderNo,
|
||||
orderId: result.order.id,
|
||||
reason: result.ignoreReason || 'fulfillment_not_ready',
|
||||
})
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: buildOpen91OutTradeNo(result.order),
|
||||
outTradeNo: buildOpen91OutTradeNo(pendingOrder || result.order),
|
||||
orderStatus: 10,
|
||||
orderCost: buildOpen91OrderCost(0),
|
||||
cards: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (!result.order || !Array.isArray(result.tasks) || result.tasks.length === 0) {
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: '',
|
||||
orderStatus: 30,
|
||||
failCode: OPEN_91_DEFAULT_FAIL_CODE,
|
||||
failReason: resolveOpen91CreateFailReason(result.ignoreReason),
|
||||
})
|
||||
}
|
||||
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: buildOpen91OutTradeNo(result.order),
|
||||
@@ -114,15 +116,3 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
cards: '',
|
||||
})
|
||||
}
|
||||
|
||||
function resolveOpen91CreateFailReason(ignoreReason: unknown) {
|
||||
if (isWaitingKuaishouIndustrySendCallback(ignoreReason)) {
|
||||
return '电子凭证发码回调未成功,订单暂不能履约'
|
||||
}
|
||||
|
||||
return '商品未配置或订单无法履约'
|
||||
}
|
||||
|
||||
function isWaitingKuaishouIndustrySendCallback(ignoreReason: unknown) {
|
||||
return String(ignoreReason || '').trim() === 'kuaishou_industry_send_callback_unconfirmed'
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
verifyOpen91Signature,
|
||||
} from './crypto.js'
|
||||
import { buildOpen91CardItem, formatOpen91ExpireTime, resolveOpen91QueryState } from './response.js'
|
||||
import { shouldWaitForOpen91Fulfillment } from './order-create-service.js'
|
||||
|
||||
test('signOpen91Payload signs only request body fields', () => {
|
||||
const config = {
|
||||
@@ -114,6 +115,12 @@ test('resolveOpen91QueryState returns success only when every task is ready', ()
|
||||
)
|
||||
})
|
||||
|
||||
test('内部订单已落库但履约任务未生成时,91 下单响应必须进入轮询', () => {
|
||||
assert.equal(shouldWaitForOpen91Fulfillment([]), true)
|
||||
assert.equal(shouldWaitForOpen91Fulfillment(undefined), true)
|
||||
assert.equal(shouldWaitForOpen91Fulfillment([{ id: 1 }]), false)
|
||||
})
|
||||
|
||||
test('buildOpen91CardItem mirrors claim link into jumpLink and leaves password empty', () => {
|
||||
assert.deepEqual(
|
||||
buildOpen91CardItem({
|
||||
|
||||
@@ -25,6 +25,32 @@ import type { OrderItemRow, OrderRow } from '../../../types/repository/rows.js'
|
||||
export const OPEN_91_PENDING_CONFIG_STATUS = 'pending_config'
|
||||
export const OPEN_91_MANUAL_FAILED_STATUS = 'manual_failed'
|
||||
|
||||
export async function markOpen91OrderPendingConfig(order: OrderRow, reason = '') {
|
||||
const now = nowIso()
|
||||
const rawPayload = parseJsonObject(order.raw_payload_json)
|
||||
return updateOrder(order.id, {
|
||||
provider: order.provider,
|
||||
platform: order.platform,
|
||||
shopId: order.shop_id,
|
||||
shopName: order.shop_name,
|
||||
platformOrderId: order.platform_order_id,
|
||||
orderStatus: OPEN_91_PENDING_CONFIG_STATUS,
|
||||
payStatus: order.pay_status,
|
||||
buyerId: order.buyer_id,
|
||||
buyerName: order.buyer_name,
|
||||
receiverContact: order.receiver_contact,
|
||||
totalAmount: Number(order.total_amount) || 0,
|
||||
currency: order.currency,
|
||||
rawPayloadJson: JSON.stringify({
|
||||
...rawPayload,
|
||||
pendingReason: String(reason || '').trim() || 'fulfillment_not_ready',
|
||||
pendingAt: now,
|
||||
}),
|
||||
paidAt: order.paid_at,
|
||||
updatedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
export function buildOpen91SourceEvent(payload: JsonObject = {}, config: JsonObject = {}) {
|
||||
const normalized = normalizeOpen91CreatePayload(payload)
|
||||
const productInfo = parseOpen91ProductNo(normalized.productNo)
|
||||
|
||||
Reference in New Issue
Block a user