zksu
/
API
forked from yoone/API
1
0
Fork 0
API/src/service/canadaPost.service.ts

172 lines
4.6 KiB
TypeScript

import { Provide } from '@midwayjs/core';
import { Config } from '@midwayjs/decorator';
import axios from 'axios';
import { parseStringPromise, Builder } from 'xml2js';
import { ShipmentType } from '../enums/base.enum';
@Provide()
export class CanadaPostService {
@Config('canadaPost.url')
url;
@Config('canadaPost.username')
username;
@Config('canadaPost.password')
password;
@Config('canadaPost.customerNumber')
customerNumber;
@Config('canadaPost.contractId')
contractId;
private getAuth() {
return {
username: this.username,
password: this.password,
};
}
private getHeaders(accept = 'application/vnd.cpc.shipment-v8+xml') {
return {
Accept: accept,
'Content-Type': accept,
'Accept-language': 'en-CA',
};
}
private async parseXML(xml: string) {
return await parseStringPromise(xml, { explicitArray: false });
}
private buildXML(data: any, rootName?: string, namespace?: string): string {
const builder = new Builder({
headless: false,
xmldec: { version: '1.0', encoding: 'UTF-8' },
});
// 如果指定了根节点和命名空间
if (rootName && namespace) {
const xmlObj = {
[rootName]: {
$: { xmlns: namespace },
...data,
},
};
return builder.buildObject(xmlObj);
}
// 默认直接构建(用于 createShipment 这类已有完整结构)
return builder.buildObject(data);
}
private normalizeCanadaPostRates(rawData: any) {
const services = rawData['price-quotes']?.['price-quote'];
if (!services) return [];
const list = Array.isArray(services) ? services : [services];
return list.map(s => ({
carrier_name: 'Canada Post',
service_name: s['service-name'],
service_id: s['service-code'],
total: {
value: 100 * parseFloat(s['price-details']['due']),
currency: 'CAD',
},
transit_time_days: s['service-standard']?.['expected-transit-time'],
type: ShipmentType.CANADAPOST,
}));
}
/** 获取运费估价 */
async getRates(rateRequest: any) {
const xmlBody = this.buildXML(
rateRequest,
'mailing-scenario',
'http://www.canadapost.ca/ws/ship/rate-v4'
);
const url = `${this.url}/rs/ship/price`;
const res = await axios.post(url, xmlBody, {
auth: this.getAuth(),
headers: this.getHeaders('application/vnd.cpc.ship.rate-v4+xml'),
});
return this.normalizeCanadaPostRates(await this.parseXML(res.data));
}
private normalizeCanadaPostShipment(rawData: any) {
const shipment = rawData['shipment-info'];
return {
id: shipment['shipment-id'],
tracking_provider: 'Canada Post',
unique_id: shipment['shipment-id'],
state: 'waiting-for-transit',
primary_tracking_number: shipment['tracking-pin'],
tracking_url: `https://www.canadapost-postescanada.ca/track-reperage/en#/details/${shipment['tracking-pin']}`,
labels: [
{
url: shipment['links']['link']?.find(
link => link['$']['rel'] === 'label'
)?.['$']['href'],
},
],
};
}
/** 创建运单 */
async createShipment(shipmentRequest: any) {
const xmlBody = this.buildXML(
shipmentRequest,
'shipment',
'http://www.canadapost.ca/ws/shipment-v8'
);
const url = `${this.url}/rs/${this.customerNumber}/${this.customerNumber}/shipment`;
const res = await axios.post(url, xmlBody, {
auth: this.getAuth(),
headers: this.getHeaders('application/vnd.cpc.shipment-v8+xml'),
});
return this.normalizeCanadaPostShipment(await this.parseXML(res.data));
}
/** 查询运单 */
async getShipment(pin: string) {
const url = `${this.url}/vis/track/pin/${pin}/summary`;
const res = await axios.get(url, {
auth: this.getAuth(),
headers: this.getHeaders('application/vnd.cpc.track-v2+xml'),
});
const shipment = await this.parseXML(res.data);
const eventType =
shipment['tracking-summary']['pin-summary']['event-type']?.toUpperCase();
return {
shipment: {
state:
eventType === 'INDUCTION'
? 'waiting-for-transit'
: eventType === 'DELIVERED'
? 'delivered'
: 'in-transit',
},
};
}
/** 取消运单 */
async cancelShipment(shipmentId: number) {
const url = `${this.url}/rs/${this.customerNumber}/${this.customerNumber}/shipment/${shipmentId}`;
const res = await axios.delete(url, {
auth: this.getAuth(),
headers: this.getHeaders('application/vnd.cpc.shipment-v8+xml'),
});
console.log(res);
return res.status === 200 || res.status === 204;
}
}