小飞侠的日志
This commit is contained in:
@@ -19,6 +19,19 @@ export const ORDER_STATUS_LABELS: Record<string, string> = {
|
|||||||
REFUNDED: '已退款',
|
REFUNDED: '已退款',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 订单状态 Tag 颜色(Ant Design preset) */
|
||||||
|
export const ORDER_STATUS_COLORS: Record<string, string> = {
|
||||||
|
PENDING_PAY: 'red',
|
||||||
|
PENDING_SHIP: 'green',
|
||||||
|
OUT_WAREHOUSE: 'green',
|
||||||
|
SHIPPING: 'processing',
|
||||||
|
PENDING_RECEIVE: 'cyan',
|
||||||
|
COMPLETED: 'green',
|
||||||
|
CANCELLED: 'default',
|
||||||
|
REFUNDING: 'orange',
|
||||||
|
REFUNDED: 'default',
|
||||||
|
};
|
||||||
|
|
||||||
export const STORE_STATUS_LABELS: Record<string, string> = {
|
export const STORE_STATUS_LABELS: Record<string, string> = {
|
||||||
OPEN: '营业中',
|
OPEN: '营业中',
|
||||||
PAUSED: '暂停',
|
PAUSED: '暂停',
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import { request, type AdminOrderRow, type Paginated } from '../lib/api';
|
import { request, type AdminOrderRow, type Paginated } from '../lib/api';
|
||||||
import { DELIVERY_PROVIDER_LABELS, ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
|
import { DELIVERY_PROVIDER_LABELS, ORDER_STATUS_COLORS, ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
|
||||||
|
|
||||||
type ShipDefaults = {
|
type ShipDefaults = {
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -172,7 +172,9 @@ export default function OrdersPage() {
|
|||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag>,
|
render: (s) => (
|
||||||
|
<Tag color={ORDER_STATUS_COLORS[s] || 'default'}>{ORDER_STATUS_LABELS[s] || s}</Tag>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '配送',
|
title: '配送',
|
||||||
@@ -293,7 +295,11 @@ export default function OrdersPage() {
|
|||||||
<>
|
<>
|
||||||
<Descriptions column={1} bordered size="small" title="基本信息">
|
<Descriptions column={1} bordered size="small" title="基本信息">
|
||||||
<Descriptions.Item label="订单号">{detail.orderNo}</Descriptions.Item>
|
<Descriptions.Item label="订单号">{detail.orderNo}</Descriptions.Item>
|
||||||
<Descriptions.Item label="状态">{ORDER_STATUS_LABELS[detail.status] || detail.status}</Descriptions.Item>
|
<Descriptions.Item label="状态">
|
||||||
|
<Tag color={ORDER_STATUS_COLORS[detail.status] || 'default'}>
|
||||||
|
{ORDER_STATUS_LABELS[detail.status] || detail.status}
|
||||||
|
</Tag>
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
|
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
|
||||||
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
|
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
|
||||||
<Descriptions.Item label="实付">¥{detail.payAmount}</Descriptions.Item>
|
<Descriptions.Item label="实付">¥{detail.payAmount}</Descriptions.Item>
|
||||||
@@ -435,7 +441,9 @@ export default function OrdersPage() {
|
|||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag>,
|
render: (s) => (
|
||||||
|
<Tag color={ORDER_STATUS_COLORS[s] || 'default'}>{ORDER_STATUS_LABELS[s] || s}</Tag>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{ title: '实付', dataIndex: 'payAmount', width: 90, render: (v) => `¥${v}` },
|
{ title: '实付', dataIndex: 'payAmount', width: 90, render: (v) => `¥${v}` },
|
||||||
{ title: '收货人', dataIndex: 'receiverName', width: 90 },
|
{ title: '收货人', dataIndex: 'receiverName', width: 90 },
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const PROVIDER_OPTIONS = [
|
|||||||
{ value: 'WECHAT_MAP', label: 'WECHAT_MAP' },
|
{ value: 'WECHAT_MAP', label: 'WECHAT_MAP' },
|
||||||
{ value: 'ALIYUN_SMS', label: 'ALIYUN_SMS' },
|
{ value: 'ALIYUN_SMS', label: 'ALIYUN_SMS' },
|
||||||
{ value: 'MOCK_SMS', label: 'MOCK_SMS' },
|
{ value: 'MOCK_SMS', label: 'MOCK_SMS' },
|
||||||
{ value: 'XIAOFEIXIA', label: 'XIAOFEIXIA' },
|
{ value: 'XFX', label: '小飞侠 (XFX)' },
|
||||||
|
{ value: 'LOGISTICS', label: 'LOGISTICS' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const STATUS_COLOR: Record<string, string> = {
|
const STATUS_COLOR: Record<string, string> = {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Body, Controller, Post } from '@nestjs/common';
|
|||||||
import { TradeService } from '../modules/trade/trade.service';
|
import { TradeService } from '../modules/trade/trade.service';
|
||||||
import { CourierService } from '../integrations/courier/courier.service';
|
import { CourierService } from '../integrations/courier/courier.service';
|
||||||
import { PrismaService } from '../common/prisma/prisma.module';
|
import { PrismaService } from '../common/prisma/prisma.module';
|
||||||
|
import { logCourierCall } from '../integrations/courier/courier-log.util';
|
||||||
|
|
||||||
@Controller('callbacks/delivery')
|
@Controller('callbacks/delivery')
|
||||||
export class DeliveryCallbackController {
|
export class DeliveryCallbackController {
|
||||||
@@ -13,13 +14,34 @@ export class DeliveryCallbackController {
|
|||||||
|
|
||||||
@Post('track')
|
@Post('track')
|
||||||
async track(@Body() body: { orderNo?: string; orderId?: string; status?: string }) {
|
async track(@Body() body: { orderNo?: string; orderId?: string; status?: string }) {
|
||||||
|
const baseLog = {
|
||||||
|
scene: 'TRACK_CALLBACK',
|
||||||
|
requestUrl: '/api/v1/callbacks/delivery/track',
|
||||||
|
requestBody: body as Record<string, unknown>,
|
||||||
|
};
|
||||||
|
|
||||||
if (!body.orderId && !body.orderNo) {
|
if (!body.orderId && !body.orderNo) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
...baseLog,
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: '缺少 orderId / orderNo',
|
||||||
|
});
|
||||||
return this.courier.buildTrackCallbackResponse(false);
|
return this.courier.buildTrackCallbackResponse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = body.orderId
|
const order = body.orderId
|
||||||
? await this.prisma.order.findUnique({ where: { id: BigInt(body.orderId) } })
|
? await this.prisma.order.findUnique({ where: { id: BigInt(body.orderId) } })
|
||||||
: await this.prisma.order.findUnique({ where: { orderNo: body.orderNo! } });
|
: await this.prisma.order.findUnique({ where: { orderNo: body.orderNo! } });
|
||||||
if (!order) return this.courier.buildTrackCallbackResponse(false);
|
|
||||||
|
if (!order) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
...baseLog,
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: '订单不存在',
|
||||||
|
externalNo: body.orderNo,
|
||||||
|
});
|
||||||
|
return this.courier.buildTrackCallbackResponse(false);
|
||||||
|
}
|
||||||
|
|
||||||
const statusMap: Record<string, string> = {
|
const statusMap: Record<string, string> = {
|
||||||
SHIPPED: 'SHIPPING',
|
SHIPPED: 'SHIPPING',
|
||||||
@@ -28,9 +50,22 @@ export class DeliveryCallbackController {
|
|||||||
COMPLETED: 'COMPLETED',
|
COMPLETED: 'COMPLETED',
|
||||||
};
|
};
|
||||||
const target = statusMap[body.status ?? ''] ?? body.status;
|
const target = statusMap[body.status ?? ''] ?? body.status;
|
||||||
|
let applied = false;
|
||||||
if (target && target !== order.status) {
|
if (target && target !== order.status) {
|
||||||
await this.tradeService.applyStatusTransition(order.id, order.status, target, 'DELIVERY_CALLBACK');
|
await this.tradeService.applyStatusTransition(order.id, order.status, target, 'DELIVERY_CALLBACK');
|
||||||
|
applied = true;
|
||||||
}
|
}
|
||||||
return this.courier.buildTrackCallbackResponse(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import type { PrismaService } from '../../common/prisma/prisma.module';
|
||||||
|
import { XIAOFEIXIA_CMD } from './xiaofeixia/xiaofeixia.constants';
|
||||||
|
|
||||||
|
const CMD_SCENE: Record<string, string> = {
|
||||||
|
[XIAOFEIXIA_CMD.CREATE_ORDER]: 'CREATE_SHIPMENT',
|
||||||
|
[XIAOFEIXIA_CMD.TRACK_ROUTE]: 'GET_TRACK',
|
||||||
|
[XIAOFEIXIA_CMD.CANCEL_ORDER]: 'CANCEL_SHIPMENT',
|
||||||
|
[XIAOFEIXIA_CMD.GET_ORDER]: 'GET_SHIPMENT',
|
||||||
|
[XIAOFEIXIA_CMD.ESTIMATE_FREIGHT]: 'ESTIMATE_FREIGHT',
|
||||||
|
[XIAOFEIXIA_CMD.BATCH_GET_ORDER]: 'BATCH_GET_SHIPMENT',
|
||||||
|
[XIAOFEIXIA_CMD.DELIVERY_COVERAGE]: 'CHECK_COVERAGE',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CourierLogRef = {
|
||||||
|
refType?: string;
|
||||||
|
refId?: bigint;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogCourierCallInput = {
|
||||||
|
scene: string;
|
||||||
|
requestUrl: string;
|
||||||
|
requestBody?: Record<string, unknown>;
|
||||||
|
responseBody?: unknown;
|
||||||
|
externalNo?: string;
|
||||||
|
status: 'SUCCESS' | 'FAILED' | 'PENDING';
|
||||||
|
errorMessage?: string;
|
||||||
|
ref?: CourierLogRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
function maskMchId(mchId?: string) {
|
||||||
|
if (!mchId) return mchId;
|
||||||
|
if (mchId.length <= 4) return '****';
|
||||||
|
return `${mchId.slice(0, 4)}****`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 请求体入库前脱敏(去掉 sign,商户号打码) */
|
||||||
|
export function sanitizeXfxRequestBody(body: Record<string, unknown>) {
|
||||||
|
const { sign: _sign, mchId, ...rest } = body;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
...(mchId != null ? { mchId: maskMchId(String(mchId)) } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sceneForXfxCmd(cmd: string) {
|
||||||
|
return CMD_SCENE[cmd] ?? `XFX_CMD_${cmd}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logCourierCall(prisma: PrismaService, input: LogCourierCallInput) {
|
||||||
|
const responseBody =
|
||||||
|
input.responseBody === undefined
|
||||||
|
? undefined
|
||||||
|
: typeof input.responseBody === 'object' && input.responseBody !== null
|
||||||
|
? (input.responseBody as Record<string, unknown>)
|
||||||
|
: { value: input.responseBody };
|
||||||
|
|
||||||
|
const row = await prisma.logThirdParty.create({
|
||||||
|
data: {
|
||||||
|
provider: 'XFX',
|
||||||
|
scene: input.scene,
|
||||||
|
refType: input.ref?.refType,
|
||||||
|
refId: input.ref?.refId,
|
||||||
|
requestUrl: input.requestUrl.slice(0, 512),
|
||||||
|
requestBody: input.requestBody as never,
|
||||||
|
responseBody: responseBody as never,
|
||||||
|
externalNo: input.externalNo?.slice(0, 128),
|
||||||
|
status: input.status,
|
||||||
|
errorMessage: input.errorMessage?.slice(0, 512),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return row.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveOrderRefByOutNumber(
|
||||||
|
prisma: PrismaService,
|
||||||
|
outNumber?: string,
|
||||||
|
): Promise<CourierLogRef | undefined> {
|
||||||
|
if (!outNumber?.trim()) return undefined;
|
||||||
|
const order = await prisma.order.findUnique({
|
||||||
|
where: { orderNo: outNumber.trim() },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!order) return undefined;
|
||||||
|
return { refType: 'ORDER', refId: order.id };
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CourierApiError } from '../courier.error';
|
import { CourierApiError } from '../courier.error';
|
||||||
import { CourierConfigService } from '../courier.config';
|
import { CourierConfigService } from '../courier.config';
|
||||||
|
import { PrismaService } from '../../../common/prisma/prisma.module';
|
||||||
|
import {
|
||||||
|
logCourierCall,
|
||||||
|
resolveOrderRefByOutNumber,
|
||||||
|
sanitizeXfxRequestBody,
|
||||||
|
sceneForXfxCmd,
|
||||||
|
} from '../courier-log.util';
|
||||||
import { buildXiaofeixiaSign } from './xiaofeixia.sign';
|
import { buildXiaofeixiaSign } from './xiaofeixia.sign';
|
||||||
import { XIAOFEIXIA_SUCCESS_CODE } from './xiaofeixia.constants';
|
import { XIAOFEIXIA_SUCCESS_CODE } from './xiaofeixia.constants';
|
||||||
import type { XiaofeixiaApiResponse } from './xiaofeixia.types';
|
import type { XiaofeixiaApiResponse } from './xiaofeixia.types';
|
||||||
@@ -9,12 +16,25 @@ type RequestParams = Record<string, string | number | undefined>;
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class XiaofeixiaClient {
|
export class XiaofeixiaClient {
|
||||||
constructor(private readonly courierConfig: CourierConfigService) {}
|
constructor(
|
||||||
|
private readonly courierConfig: CourierConfigService,
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async request<T>(cmd: string, bizParams: RequestParams): Promise<T> {
|
async request<T>(cmd: string, bizParams: RequestParams): Promise<T> {
|
||||||
const cfg = this.courierConfig.load().xiaofeixia;
|
const cfg = this.courierConfig.load().xiaofeixia;
|
||||||
|
const scene = sceneForXfxCmd(cmd);
|
||||||
|
const externalNo = this.pickExternalNo(bizParams);
|
||||||
|
|
||||||
if (!cfg.mchId || !cfg.apiKey) {
|
if (!cfg.mchId || !cfg.apiKey) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl || '(未配置)',
|
||||||
|
requestBody: sanitizeXfxRequestBody({ cmd, ...bizParams, mchId: cfg.mchId }),
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: '小飞侠商户配置不完整',
|
||||||
|
externalNo,
|
||||||
|
});
|
||||||
throw new CourierApiError(
|
throw new CourierApiError(
|
||||||
'小飞侠商户配置不完整,请设置 XIAOFEIXIA_MCH_ID 与 XIAOFEIXIA_API_KEY',
|
'小飞侠商户配置不完整,请设置 XIAOFEIXIA_MCH_ID 与 XIAOFEIXIA_API_KEY',
|
||||||
'CONFIG_ERROR',
|
'CONFIG_ERROR',
|
||||||
@@ -42,6 +62,12 @@ export class XiaofeixiaClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logRequestBody = sanitizeXfxRequestBody({ ...baseParams, sign: '[REDACTED]' });
|
||||||
|
const orderRef = await resolveOrderRefByOutNumber(
|
||||||
|
this.prisma,
|
||||||
|
typeof bizParams.outNumber === 'string' ? bizParams.outNumber : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(cfg.apiUrl, {
|
response = await fetch(cfg.apiUrl, {
|
||||||
@@ -50,15 +76,32 @@ export class XiaofeixiaClient {
|
|||||||
body: body.toString(),
|
body: body.toString(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new CourierApiError(
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
'小飞侠接口网络异常',
|
await logCourierCall(this.prisma, {
|
||||||
'200000',
|
scene,
|
||||||
'XIAOFEIXIA',
|
requestUrl: cfg.apiUrl,
|
||||||
error,
|
requestBody: logRequestBody,
|
||||||
);
|
status: 'FAILED',
|
||||||
|
errorMessage: `网络异常: ${message}`,
|
||||||
|
externalNo,
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
|
throw new CourierApiError('小飞侠接口网络异常', '200000', 'XIAOFEIXIA', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawText = await response.text();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl,
|
||||||
|
requestBody: logRequestBody,
|
||||||
|
responseBody: { httpStatus: response.status, body: rawText.slice(0, 500) },
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: `HTTP ${response.status}`,
|
||||||
|
externalNo,
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
throw new CourierApiError(
|
throw new CourierApiError(
|
||||||
`小飞侠 HTTP 请求失败: ${response.status}`,
|
`小飞侠 HTTP 请求失败: ${response.status}`,
|
||||||
'200000',
|
'200000',
|
||||||
@@ -66,11 +109,22 @@ export class XiaofeixiaClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawText = await response.text();
|
|
||||||
let payload: XiaofeixiaApiResponse<T>;
|
let payload: XiaofeixiaApiResponse<T>;
|
||||||
try {
|
try {
|
||||||
payload = rawText ? (JSON.parse(rawText) as XiaofeixiaApiResponse<T>) : (null as unknown as XiaofeixiaApiResponse<T>);
|
payload = rawText
|
||||||
|
? (JSON.parse(rawText) as XiaofeixiaApiResponse<T>)
|
||||||
|
: (null as unknown as XiaofeixiaApiResponse<T>);
|
||||||
} catch {
|
} catch {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl,
|
||||||
|
requestBody: logRequestBody,
|
||||||
|
responseBody: { raw: rawText.slice(0, 500) },
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: '响应非 JSON',
|
||||||
|
externalNo,
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
throw new CourierApiError(
|
throw new CourierApiError(
|
||||||
`小飞侠响应非 JSON(HTTP ${response.status}): ${rawText.slice(0, 200) || '(空)'}`,
|
`小飞侠响应非 JSON(HTTP ${response.status}): ${rawText.slice(0, 200) || '(空)'}`,
|
||||||
'200000',
|
'200000',
|
||||||
@@ -80,10 +134,29 @@ export class XiaofeixiaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl,
|
||||||
|
requestBody: logRequestBody,
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: '小飞侠返回空响应',
|
||||||
|
externalNo,
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
throw new CourierApiError('小飞侠返回空响应', '200000', 'XIAOFEIXIA');
|
throw new CourierApiError('小飞侠返回空响应', '200000', 'XIAOFEIXIA');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.code !== XIAOFEIXIA_SUCCESS_CODE) {
|
if (payload.code !== XIAOFEIXIA_SUCCESS_CODE) {
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl,
|
||||||
|
requestBody: logRequestBody,
|
||||||
|
responseBody: payload as unknown as Record<string, unknown>,
|
||||||
|
status: 'FAILED',
|
||||||
|
errorMessage: payload.message || '业务失败',
|
||||||
|
externalNo: externalNo || payload.data?.toString(),
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
throw new CourierApiError(
|
throw new CourierApiError(
|
||||||
payload.message || '小飞侠接口业务失败',
|
payload.message || '小飞侠接口业务失败',
|
||||||
payload.code,
|
payload.code,
|
||||||
@@ -92,6 +165,28 @@ export class XiaofeixiaClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await logCourierCall(this.prisma, {
|
||||||
|
scene,
|
||||||
|
requestUrl: cfg.apiUrl,
|
||||||
|
requestBody: logRequestBody,
|
||||||
|
responseBody: payload as unknown as Record<string, unknown>,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
externalNo: externalNo || this.pickExternalNoFromData(payload.data),
|
||||||
|
ref: orderRef,
|
||||||
|
});
|
||||||
|
|
||||||
return payload.data as T;
|
return payload.data as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private pickExternalNo(params: RequestParams) {
|
||||||
|
const outNumber = params.outNumber != null ? String(params.outNumber) : undefined;
|
||||||
|
const number = params.number != null ? String(params.number) : undefined;
|
||||||
|
return outNumber || number;
|
||||||
|
}
|
||||||
|
|
||||||
|
private pickExternalNoFromData(data: unknown) {
|
||||||
|
if (!data || typeof data !== 'object') return undefined;
|
||||||
|
const row = data as { number?: string; outNumber?: string; id?: string };
|
||||||
|
return row.number || row.outNumber || row.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user