API/src/service/freightwaves.service.ts

486 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Inject, Provide } from '@midwayjs/core';
import axios from 'axios';
import * as crypto from 'crypto';
import dayjs = require('dayjs');
import utc = require('dayjs/plugin/utc');
import timezone = require('dayjs/plugin/timezone');
// 扩展dayjs功能
dayjs.extend(utc);
dayjs.extend(timezone);
// 全局参数配置接口
interface FreightwavesConfig {
appSecret: string;
apiBaseUrl: string;
partner: string;
}
// 地址信息接口
interface Address {
name: string;
phone: string;
company: string;
countryCode: string;
city: string;
state: string;
address1: string;
address2: string;
postCode: string;
zoneCode?: string;
countryName: string;
cityName: string;
stateName: string;
companyName: string;
}
// 包裹尺寸接口
interface Dimensions {
length: number;
width: number;
height: number;
lengthUnit: 'IN' | 'CM';
weight: number;
weightUnit: 'LB' | 'KG';
}
// 包裹信息接口
interface Package {
dimensions: Dimensions;
currency: string;
description: string;
}
// 申报信息接口
interface Declaration {
boxNo: string;
sku: string;
cnname: string;
enname: string;
declaredPrice: number;
declaredQty: number;
material: string;
intendedUse: string;
cweight: number;
hsCode: string;
battery: string;
}
// 费用试算请求接口
interface RateTryRequest {
shipCompany: string;
partnerOrderNumber: string;
warehouseId?: string;
shipper: Address;
reciver: Address;
packages: Package[];
partner: string;
signService?: 0 | 1;
}
// 创建订单请求接口
interface CreateOrderRequest extends RateTryRequest {
declaration: Declaration;
}
// 查询订单请求接口
interface QueryOrderRequest {
partnerOrderNumber?: string;
shipOrderId?: string;
partner: string;
}
// 修改订单请求接口
interface ModifyOrderRequest extends CreateOrderRequest {
shipOrderId: string;
}
// 订单退款请求接口
interface RefundOrderRequest {
shipOrderId: string;
partner: string;
}
// 通用响应接口
interface FreightwavesResponse<T> {
code: string;
msg: string;
data: T;
}
// 费用试算响应数据接口
interface RateTryResponseData {
shipCompany: string;
channelCode: string;
totalAmount: number;
currency: string;
}
// 创建订单响应数据接口
interface CreateOrderResponseData {
partnerOrderNumber: string;
shipOrderId: string;
}
// 查询订单响应数据接口
interface QueryOrderResponseData {
thirdOrderId: string;
shipCompany: string;
expressFinish: 0 | 1 | 2;
expressFailMsg: string;
expressOrder: {
mainTrackingNumber: string;
labelPath: string[];
totalAmount: number;
currency: string;
balance: number;
};
partnerOrderNumber: string;
shipOrderId: string;
}
// 修改订单响应数据接口
interface ModifyOrderResponseData extends CreateOrderResponseData {}
// 订单退款响应数据接口
interface RefundOrderResponseData {}
@Provide()
export class FreightwavesService {
@Inject() logger;
// 默认配置
private config: FreightwavesConfig = {
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'https://tms.freightwaves.ca',
partner: '25072621035200000060',
};
// 初始化配置
setConfig(config: Partial<FreightwavesConfig>): void {
this.config = { ...this.config, ...config };
}
// 生成签名
private generateSignature(body: any, date: string): string {
const bodyString = JSON.stringify(body);
const signatureStr = `${bodyString}${this.config.appSecret}${date}`;
return crypto.createHash('md5').update(signatureStr).digest('hex');
}
// 发送请求
private async sendRequest<T>(url: string, data: any): Promise<FreightwavesResponse<T>> {
try {
// 设置请求头 - 使用太平洋时间 (America/Los_Angeles)
const date = dayjs().tz('America/Los_Angeles').format('YYYY-mm-dd HH:mm:ss');
const headers = {
'Content-Type': 'application/json',
'requestDate': date,
'signature': this.generateSignature(data, date),
};
// 记录请求前的详细信息
this.log(`Sending request to: ${this.config.apiBaseUrl}${url}`, {
headers,
data
});
// 发送请求 - 临时禁用SSL证书验证以解决UNABLE_TO_VERIFY_LEAF_SIGNATURE错误
const response = await axios.post<FreightwavesResponse<T>>(
`${this.config.apiBaseUrl}${url}`,
data,
{
headers,
httpsAgent: new (require('https').Agent)({
rejectUnauthorized: false
})
}
);
// 记录响应信息
this.log(`Received response from: ${this.config.apiBaseUrl}${url}`, {
status: response.status,
statusText: response.statusText,
data: response.data
});
// 处理响应
if (response.data.code !== '00000200') {
this.log(`Freightwaves API error: ${response.data.msg}`, { url, data, response: response.data });
throw new Error(response.data.msg);
}
return response.data;
} catch (error) {
// 更详细的错误记录
if (error.response) {
// 请求已发送,服务器返回错误状态码
this.log(`Freightwaves API request failed with status: ${error.response.status}`, {
url,
data,
response: error.response.data,
status: error.response.status,
headers: error.response.headers
});
} else if (error.request) {
// 请求已发送,但没有收到响应
this.log(`Freightwaves API request no response received`, {
url,
data,
request: error.request
});
} else {
// 请求配置时发生错误
this.log(`Freightwaves API request configuration error: ${error.message}`, {
url,
data,
error: error.message
});
}
throw error;
}
}
/**
* 费用试算
* @param params 费用试算参数
* @returns 费用试算结果
*/
async rateTry(params: Omit<RateTryRequest, 'partner'>): Promise<RateTryResponseData> {
const requestData: RateTryRequest = {
...params,
partner: this.config.partner,
};
const response = await this.sendRequest<RateTryResponseData>('/shipService/order/rateTry', requestData);
return response.data;
}
/**
* 创建订单
* @param params 创建订单参数
* @returns 创建订单结果
*/
async createOrder(params: Omit<CreateOrderRequest, 'partner'>): Promise<CreateOrderResponseData> {
const requestData: CreateOrderRequest = {
...params,
partner: this.config.partner,
};
const response = await this.sendRequest<CreateOrderResponseData>('/shipService/order/createOrder?apipost_id=0422aa', requestData);
return response.data;
}
/**
* 查询订单
* @param params 查询订单参数
* @returns 查询订单结果
*/
async queryOrder(params: Omit<QueryOrderRequest, 'partner'>): Promise<QueryOrderResponseData> {
const requestData: QueryOrderRequest = {
...params,
partner: this.config.partner,
};
const response = await this.sendRequest<QueryOrderResponseData>('/shipService/order/queryOrder', requestData);
return response.data;
}
/**
* 修改订单
* @param params 修改订单参数
* @returns 修改订单结果
*/
async modifyOrder(params: Omit<ModifyOrderRequest, 'partner'>): Promise<ModifyOrderResponseData> {
const requestData: ModifyOrderRequest = {
...params,
partner: this.config.partner,
};
const response = await this.sendRequest<ModifyOrderResponseData>('/shipService/order/modifyOrder', requestData);
return response.data;
}
/**
* 订单退款
* @param params 订单退款参数
* @returns 订单退款结果
*/
async refundOrder(params: Omit<RefundOrderRequest, 'partner'>): Promise<RefundOrderResponseData> {
const requestData: RefundOrderRequest = {
...params,
partner: this.config.partner,
};
const response = await this.sendRequest<RefundOrderResponseData>('/shipService/order/refundOrder', requestData);
return response.data;
}
/**
* 测试创建订单方法
* 用于演示如何使用createOrder方法
*/
async testCreateOrder() {
try {
// 设置必要的配置
this.setConfig({
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'https://tms.freightwaves.ca',
partner: '25072621035200000060'
});
// 准备测试数据
const testParams: Omit<CreateOrderRequest, 'partner'> = {
shipCompany: 'DHL',
partnerOrderNumber: `test-order-${Date.now()}`,
warehouseId: '25072621035200000060',
shipper: {
name: 'John Doe',
phone: '123-456-7890',
company: 'Test Company',
countryCode: 'CA',
city: 'Toronto',
state: 'ON',
address1: '123 Main St',
address2: 'Suite 400',
postCode: 'M5V 2T6',
countryName: 'Canada',
cityName: 'Toronto',
stateName: 'Ontario',
companyName: 'Test Company Inc.'
},
reciver: {
name: 'Jane Smith',
phone: '987-654-3210',
company: 'Receiver Company',
countryCode: 'CA',
city: 'Vancouver',
state: 'BC',
address1: '456 Oak St',
address2: '',
postCode: 'V6J 2A9',
countryName: 'Canada',
cityName: 'Vancouver',
stateName: 'British Columbia',
companyName: 'Receiver Company Ltd.'
},
packages: [
{
dimensions: {
length: 10,
width: 8,
height: 6,
lengthUnit: 'IN',
weight: 5,
weightUnit: 'LB'
},
currency: 'CAD',
description: 'Test Package'
}
],
declaration: {
boxNo: 'BOX-001',
sku: 'TEST-SKU-001',
cnname: '测试产品',
enname: 'Test Product',
declaredPrice: 100,
declaredQty: 1,
material: 'Plastic',
intendedUse: 'General use',
cweight: 5,
hsCode: '39269090',
battery: 'No'
},
signService: 0
};
// 调用创建订单方法
this.log('开始测试创建订单...');
this.log('测试参数:', testParams);
// 注意:在实际环境中取消注释以下行来执行真实请求
const result = await this.createOrder(testParams);
this.log('创建订单成功:', result);
// 返回模拟结果
return {
partnerOrderNumber: testParams.partnerOrderNumber,
shipOrderId: `simulated-shipOrderId-${Date.now()}`
};
} catch (error) {
this.log('测试创建订单失败:', error);
throw error;
}
}
/**
* 测试查询订单方法
* @returns 查询订单结果
*/
async testQueryOrder() {
try {
// 设置必要的配置
this.setConfig({
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
apiBaseUrl: 'https://tms.freightwaves.ca',
partner: '25072621035200000060'
});
// 准备测试数据 - 可以通过partnerOrderNumber或shipOrderId查询
const testParams: Omit<QueryOrderRequest, 'partner'> = {
// 选择其中一个参数进行测试
partnerOrderNumber: 'test-order-123456789', // 示例订单号
// shipOrderId: 'simulated-shipOrderId-123456789' // 或者使用运单号
};
// 调用查询订单方法
this.log('开始测试查询订单...');
this.log('测试参数:', testParams);
// 注意:在实际环境中取消注释以下行来执行真实请求
const result = await this.queryOrder(testParams);
this.log('查询订单成功:', result);
this.log('测试完成:查询订单方法调用成功(模拟)');
// 返回模拟结果
return {
thirdOrderId: 'thirdOrder-123456789',
shipCompany: 'DHL',
expressFinish: 0,
expressFailMsg: '',
expressOrder: {
mainTrackingNumber: '1234567890',
labelPath: ['https://example.com/label.pdf'],
totalAmount: 100,
currency: 'CAD',
balance: 50
},
partnerOrderNumber: testParams.partnerOrderNumber,
shipOrderId: 'simulated-shipOrderId-123456789'
};
} catch (error) {
this.log('测试查询订单失败:', error);
throw error;
}
}
/**
* 辅助日志方法处理logger可能未定义的情况
* @param message 日志消息
* @param data 附加数据
*/
private log(message: string, data?: any) {
if (this.logger) {
this.logger.info(message, data);
} else {
// 如果logger未定义使用console输出
if (data) {
console.log(message, data);
} else {
console.log(message);
}
}
}
}