fix(courier): parse XFX multipart/form-data track callbacks
Xiaofeixia posts multipart form fields; previously mis-read as urlencoded empty payload. Parse multipart text parts and map into order status transitions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -139,20 +139,15 @@ export class XiaofeixiaProvider implements ICourierProvider {
|
||||
parseTrackCallback(body: unknown): TrackCallbackPayload | null {
|
||||
if (!body || typeof body !== 'object') return null;
|
||||
const root = body as Record<string, unknown>;
|
||||
// 兼容顶层字段或 data/payload 包裹
|
||||
const nested =
|
||||
root.data && typeof root.data === 'object'
|
||||
? (root.data as Record<string, unknown>)
|
||||
: root.payload && typeof root.payload === 'object'
|
||||
? (root.payload as Record<string, unknown>)
|
||||
: null;
|
||||
const raw = nested ? { ...nested, ...root } : root;
|
||||
const outNumber = String(raw.outNumber ?? '').trim();
|
||||
const trackingNumber = String(raw.number ?? raw.trackingNumber ?? '').trim();
|
||||
const status = String(raw.status ?? '').trim();
|
||||
const statusName = String(raw.statusName ?? '').trim();
|
||||
const trackInfo = String(raw.trackInfo ?? '').trim();
|
||||
const createTime = String(raw.createTime ?? '').trim();
|
||||
// 兼容顶层字段或 data/payload 包裹;data 若为 JSON 字符串也解开
|
||||
const nestedObj = this.unwrapNestedObject(root.data) ?? this.unwrapNestedObject(root.payload);
|
||||
const raw = nestedObj ? { ...nestedObj, ...root } : root;
|
||||
const outNumber = this.firstString(raw, ['outNumber', 'out_number', 'outNo']);
|
||||
const trackingNumber = this.firstString(raw, ['number', 'trackingNumber', 'trackingNo']);
|
||||
const status = this.firstString(raw, ['status']);
|
||||
const statusName = this.firstString(raw, ['statusName', 'status_name']);
|
||||
const trackInfo = this.firstString(raw, ['trackInfo', 'track_info']);
|
||||
const createTime = this.firstString(raw, ['createTime', 'create_time']);
|
||||
if (!outNumber && !trackingNumber) return null;
|
||||
if (!status && !trackInfo) return null;
|
||||
return {
|
||||
@@ -165,6 +160,38 @@ export class XiaofeixiaProvider implements ICourierProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private unwrapNestedObject(value: unknown): Record<string, unknown> | null {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
if (typeof value === 'string' && value.trim().startsWith('{')) {
|
||||
try {
|
||||
const parsed = JSON.parse(value) as unknown;
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private firstString(raw: Record<string, unknown>, keys: string[]): string {
|
||||
for (const key of keys) {
|
||||
const value = raw[key];
|
||||
if (value == null) continue;
|
||||
if (Array.isArray(value)) {
|
||||
const last = value[value.length - 1];
|
||||
if (last != null && String(last).trim()) return String(last).trim();
|
||||
continue;
|
||||
}
|
||||
const text = String(value).trim();
|
||||
if (text) return text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
mapTrackStatus(status: string, statusName?: string): CourierMappedOrderStatus | null {
|
||||
const name = (statusName || '').trim();
|
||||
if (status === '5' || /签收/.test(name)) {
|
||||
|
||||
Reference in New Issue
Block a user