小飞侠接口对接

This commit is contained in:
2026-06-30 23:44:31 +08:00
parent 6e047dc0a5
commit ed1845c0ba
14 changed files with 653 additions and 2 deletions
@@ -0,0 +1,82 @@
import { Injectable } from '@nestjs/common';
import { CourierApiError } from '../courier.error';
import { CourierConfigService } from '../courier.config';
import { buildXiaofeixiaSign } from './xiaofeixia.sign';
import { XIAOFEIXIA_SUCCESS_CODE } from './xiaofeixia.constants';
import type { XiaofeixiaApiResponse } from './xiaofeixia.types';
type RequestParams = Record<string, string | number | undefined>;
@Injectable()
export class XiaofeixiaClient {
constructor(private readonly courierConfig: CourierConfigService) {}
async request<T>(cmd: string, bizParams: RequestParams): Promise<T> {
const cfg = this.courierConfig.load().xiaofeixia;
if (!cfg.mchId || !cfg.apiKey) {
throw new CourierApiError(
'小飞侠商户配置不完整,请设置 XIAOFEIXIA_MCH_ID 与 XIAOFEIXIA_API_KEY',
'CONFIG_ERROR',
'XIAOFEIXIA',
);
}
const baseParams: RequestParams = {
mchId: cfg.mchId,
cmd,
signType: cfg.signType,
...bizParams,
};
if (cfg.appId) {
baseParams.appId = cfg.appId;
}
const sign = buildXiaofeixiaSign(baseParams, cfg.apiKey, cfg.signType);
const body = new URLSearchParams();
for (const [key, value] of Object.entries({ ...baseParams, sign })) {
if (value !== undefined && value !== null && value !== '') {
body.append(key, String(value));
}
}
let response: Response;
try {
response = await fetch(cfg.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
} catch (error) {
throw new CourierApiError(
'小飞侠接口网络异常',
'200000',
'XIAOFEIXIA',
error,
);
}
if (!response.ok) {
throw new CourierApiError(
`小飞侠 HTTP 请求失败: ${response.status}`,
'200000',
'XIAOFEIXIA',
);
}
const payload = (await response.json()) as XiaofeixiaApiResponse<T>;
if (payload.code !== XIAOFEIXIA_SUCCESS_CODE) {
throw new CourierApiError(
payload.message || '小飞侠接口业务失败',
payload.code,
'XIAOFEIXIA',
payload,
);
}
return payload.data as T;
}
}