571959900b
Enable urlencoded body parser, return XFX {code,message} without API envelope, and harden callback payload parsing.
Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { PrismaService } from '../common/prisma/prisma.module';
|
|
import { CourierService } from '../integrations/courier/courier.service';
|
|
import { XiaofeixiaProvider } from '../integrations/courier/xiaofeixia/xiaofeixia.provider';
|
|
import { logCourierCall } from '../integrations/courier/courier-log.util';
|
|
import { TradeService } from '../modules/trade/trade.service';
|
|
|
|
const XFX_PROVIDER_ALIASES = new Set(['xfx', 'xiaofeixia']);
|
|
|
|
@Injectable()
|
|
export class DeliveryCallbackService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly courier: CourierService,
|
|
private readonly xiaofeixiaProvider: XiaofeixiaProvider,
|
|
private readonly tradeService: TradeService,
|
|
) {}
|
|
|
|
async handleTrackCallback(providerKey: string, body: unknown, requestUrl: string) {
|
|
const normalized = providerKey.trim().toLowerCase();
|
|
const baseLog = {
|
|
scene: 'TRACK_CALLBACK',
|
|
requestUrl,
|
|
requestBody: (body && typeof body === 'object' ? body : { value: body }) as Record<
|
|
string,
|
|
unknown
|
|
>,
|
|
};
|
|
|
|
if (!XFX_PROVIDER_ALIASES.has(normalized) && normalized !== 'logistics') {
|
|
await logCourierCall(this.prisma, {
|
|
...baseLog,
|
|
status: 'FAILED',
|
|
errorMessage: `不支持的承运商: ${providerKey}`,
|
|
});
|
|
return this.courier.buildTrackCallbackResponse(false);
|
|
}
|
|
|
|
if (normalized === 'logistics') {
|
|
await logCourierCall(this.prisma, {
|
|
...baseLog,
|
|
status: 'SUCCESS',
|
|
errorMessage: '跨城物流回调暂未接入,已记录',
|
|
});
|
|
return this.courier.buildTrackCallbackResponse(true);
|
|
}
|
|
|
|
const payload = this.xiaofeixiaProvider.parseTrackCallback(body);
|
|
if (!payload) {
|
|
const response = this.courier.buildTrackCallbackResponse(false);
|
|
await logCourierCall(this.prisma, {
|
|
...baseLog,
|
|
responseBody: response,
|
|
status: 'FAILED',
|
|
errorMessage: '回调体解析失败',
|
|
});
|
|
return response;
|
|
}
|
|
|
|
const order = payload.outNumber
|
|
? await this.prisma.order.findUnique({ where: { orderNo: payload.outNumber } })
|
|
: payload.trackingNumber
|
|
? await this.prisma.order.findFirst({
|
|
where: { delivery: { trackingNo: payload.trackingNumber } },
|
|
})
|
|
: null;
|
|
|
|
if (!order) {
|
|
const response = this.courier.buildTrackCallbackResponse(false);
|
|
await logCourierCall(this.prisma, {
|
|
...baseLog,
|
|
responseBody: response,
|
|
status: 'FAILED',
|
|
errorMessage: '订单不存在',
|
|
externalNo: payload.outNumber || payload.trackingNumber,
|
|
});
|
|
return response;
|
|
}
|
|
|
|
const targetStatus = this.xiaofeixiaProvider.mapTrackStatus(
|
|
payload.status,
|
|
payload.statusName,
|
|
);
|
|
let applied = false;
|
|
if (targetStatus && targetStatus !== order.status) {
|
|
await this.tradeService.applyStatusTransition(
|
|
order.id,
|
|
order.status,
|
|
targetStatus,
|
|
'COURIER_TRACK_CALLBACK',
|
|
payload.trackInfo || payload.statusName,
|
|
);
|
|
applied = true;
|
|
}
|
|
|
|
const response = this.courier.buildTrackCallbackResponse(true);
|
|
await logCourierCall(this.prisma, {
|
|
...baseLog,
|
|
responseBody: response,
|
|
status: 'SUCCESS',
|
|
externalNo: order.orderNo,
|
|
ref: { refType: 'ORDER', refId: order.id },
|
|
errorMessage: applied ? undefined : '状态未变更',
|
|
});
|
|
|
|
return response;
|
|
}
|
|
}
|