486 lines
13 KiB
TypeScript
486 lines
13 KiB
TypeScript
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);
|
||
}
|
||
}
|
||
}
|
||
} |