| Current Path : /home/deltalab/PMS/sms-connector/adapters/ |
| Current File : //home/deltalab/PMS/sms-connector/adapters/shippypro-adapter.js |
/**
* SMS adapter for ShippyPro. It exposes all standard methods of the adapter for an SMS system.
* - fetching rates from SMS (getRatesAsync)
* - create a new shipment in the SMS (createShipmentAsync)
* - get the shipment status in the SMS (getShipmentStatusAsync)
* - get the list of carriers in the SMS (getCarriersAsync)
*/
class ShippyProAdapter {
constructor() {
// http client used to perform requests to ShippyPro is axios
this._httpClient = require('axios');
}
/**
* Fetch all the rates for this given shipment
*
* @param {shipmentInfoModel} shipmentInfo the shipment information used to calculate the rate as described in the graphql schemas
* @returns a list of ShipmentRate objects representing supported carriers and their cost for the shipment
*/
getRatesAsync(shipmentInfo) {
return this._performRequestAsync(shipmentInfo,
this._mapRatesRequestInputAsync,
this._mapRatesRequestOutputAsync);
}
/**
* Create a shipment in the SMS system
*
* @param {object} shipmentData the shipment information as described in the graphql schema
* @returns A promise that will resolve in the creation-related data as described in the graphql schema
*/
createShipmentAsync(shipmentData) {
return this._performRequestAsync(shipmentData,
this._mapShipmentRequestInputAsync,
this._mapShipmentRequestOuputAsync);
}
/**
* Fetch a specific shipment information from the SMS system
*
* @param {string} shipmentId the shipment ID to fetch
* @returns A promise that will resolve in the shipment status information as described in the graphql schema
*/
getShipmentStatusAsync(shipmentId) {
return this._performRequestAsync(shipmentId,
this._mapShipmentStatusInputAsync,
this._mapShipmentStatusOutputAsync);
}
/**
* Fetch all the carriers configured on the SMS system
*
* @returns A promise that will resolve in the list of carriers configured on the SMS system, mapped as the carrier type described in graphql schemas
*/
getCarriersAsync() {
return this._performRequestAsync(null,
this._mapGetCarriersInputAsync,
this._mapGetCarriersOutputAsync);
}
/**
* Creates a manifest for the specified shipment and returns the created manifest data
*
* @param {string} shipmentId the shipment id to process
* @returns A promise that will resolve in the manifest data as defined on the graphql schema
*/
createManifestAsync(shipmentId) {
return this._performRequestAsync(shipmentId,
this._mapCreateManifestInputAsync,
this._mapGetManifestOutputAsync);
}
/**
* Gets an already created manifest using the specified id
*
* @param {int} manifestId the manifest id to search for
* @returns A promise that will resolve in the manifest data as defined in the graphql schema
*/
getManifestAsync(manifestId) {
return this._performRequestAsync(manifestId,
this._mapGetManifestInputAsync,
this._mapGetManifestOutputAsync);
}
/**
* Book a pickup for the specified parcels
*
* @param {object} pickupData the data used to book the pickup as described in the graphql schema
* @returns A promise that will resolve in the booking confirmation as defined in the graphql schema
*/
bookPickupAsync(pickupData) {
return this._performRequestAsync(pickupData,
this._mapBookPickupInputAsync,
this._mapBookPickupOutputAsync);
}
/**
* This method is working as a template for executing requests to ShippyPro.
*
* All adapter's methods can be resumed in these operations:
* 1. map input data to match the required from ShippyPro
* 2. execute the request and fetch the reply
* 3. map the result to match the SMS connector output data
*
* All mapping functions are asynchronous and are called with a parameter that contains the data to transform.
*
* @param {object} data the input data to the request
* @param {function} inputMappingFunction the function that maps input data to the required from ShippyPro
* @param {function} outputMappingFunction the mapping function that transforms the data fetched from ShippyPro to the one required from the connector
*
* @returns A promise that will resolve in the fetched data from ShippyPro.
*/
async _performRequestAsync(data, inputMappingFunction, outputMappingFunction) {
const requestData = await inputMappingFunction.call(this, data);
const apiResponse = await this._executeRequestAsync(requestData);
const result = await outputMappingFunction.call(this, apiResponse);
return result;
}
/**
* Gets a specific carrier from the SMS system
*
* @param {int} carrierId the ID of the carrier to fetch
* @returns A promise that will resolve in a specific carrier
*/
async _getSpecificCarrierAsync(carrierId) {
const carriers = await this.getCarriersAsync();
return carriers.find(c => c.smsid == carrierId);
}
/**
* Execute a request against ShippyPro, using the specified data
*
* @param {object} data the data to use as body of the request (in ShippyPro it contains all request params)
* @returns a promise that will resolve in the data returned from ShippyPro
*/
async _executeRequestAsync(data) {
const requestUrl = process.env.SMS_URL;
const request = this._httpClient.create();
const headers = {
'Authorization': `Basic ${Buffer.from(`${process.env.SMS_API_USERNAME}:`).toString('base64')}`,
'Content-Type': 'application/json;charset=utf-8',
'Accept': 'application/json;charset=utf-8',
};
try {
const httpResult = await request.post(requestUrl, data, { headers });
return httpResult.data;
} catch (e) {
this._manageHttpException(e);
}
}
/**
* Normalize an axios exception to a custom http one and throw it
*
* @param {Error} e the exception to manage as http exception
*/
_manageHttpException(e) {
const HttpException = require('../exceptions/http-exception');
const httpResult = e.response ? e.response : { status: 404, data: 'Not found' };
throw new HttpException(httpResult.status, httpResult.data);
}
// MAPPING FUNCTIONS - RATES
_mapRatesRequestInputAsync(data) {
const parcels = this._adaptParcels(data);
return new Promise((resolve) => {
resolve({
Method: 'GetRates',
Params: {
to_address: data.to,
from_address: data.from,
parcels: parcels,
Insurance: data.shipmentValueInfo.insurance,
InsuranceCurrency: data.shipmentValueInfo.insuranceCurrency,
CashOnDelivery: data.shipmentValueInfo.cashOnDelivery,
CashOnDeliveryCurrency: data.shipmentValueInfo.cashOnDeliveryCurrency,
ContentDescription: data.shipmentValueInfo.contentDescription,
TotalValue: `${data.shipmentValueInfo.totalValue} ${data.shipmentValueInfo.totalValueCurrency}`,
ShippingService: data.shipmentValueInfo.shippingService
}
});
});
}
_mapRatesRequestOutputAsync(data) {
return new Promise((resolve) => {
if (!data.Rates) resolve([]);
resolve(data.Rates.map(r => {
return {
carrier: {
smsid: +r.carrier_id,
name: r.carrier,
service: r.service,
},
rate: r.rate,
rateId: r.rate_id,
deliveryDays: r.delivery_days,
currency: r.currency
};
}));
});
}
// MAPPING FUNCTIONS - SHIPMENT CREATION
async _mapShipmentRequestInputAsync(data) {
const carrier = await this._getSpecificCarrierAsync(data.carrierId);
const parcels = this._adaptParcels(data);
const to_address = this._adaptAddress(data.to);
const from_address = this._adaptAddress(data.from);
return {
Method: 'Ship',
Params: {
to_address: to_address,
from_address: from_address,
parcels: parcels,
DeliveryFreightTypeCode: 'DAP',
Insurance: data.shipmentValueInfo.insurance,
InsuranceCurrency: data.shipmentValueInfo.insuranceCurrency,
CashOnDelivery: data.shipmentValueInfo.cashOnDelivery,
CashOnDeliveryCurrency: data.shipmentValueInfo.cashOnDeliveryCurrency,
ContentDescription: data.shipmentValueInfo.contentDescription,
TotalValue: `${data.shipmentValueInfo.totalValue} ${data.shipmentValueInfo.totalValueCurrency}`,
ShippingService: data.shipmentValueInfo.shippingService,
CarrierID: data.carrierId,
CarrierName: carrier.name,
CarrierService: carrier.service,
TransactionID: data.orderId,
OrderID: '',
RateID: data.rateId,
Incoterm: data.incoterm,
PaymentMethod: data.paymentMethod,
BillAccountNumber: '',
Note: data.note ? data.note : '',
Async: false
}
};
}
_mapShipmentRequestOuputAsync(data) {
return new Promise((resolve) => {
resolve({
error: data.Error,
shipmentId: data.NewOrderID,
labelUrl: Array.isArray(data.LabelURL) ? data.LabelURL : [data.LabelURL],
trackingCarrier: data.TrackingCarrier,
trackingNumber: data.TrackingNumber
});
});
}
// MAPPING FUNCTIONS - SHIPMENT STATUS
_mapShipmentStatusInputAsync(data) {
return new Promise((resolve) => {
resolve({
Method: 'GetOrder',
Params: {
OrderID: data
}
});
});
}
async _mapShipmentStatusOutputAsync(data) {
let shipmentStatus;
switch (data.Status) {
case '1':
shipmentStatus = 'INFO_RECEIVED';
break;
case '2':
shipmentStatus = 'IN_TRANSIT';
break;
case '3':
shipmentStatus = 'OUT_FOR_DELIVERY';
break;
case '4':
shipmentStatus = 'MISSED_DELIVERY';
break;
case '5':
shipmentStatus = 'EXCEPTION';
break;
case '6':
shipmentStatus = 'DELIVERED';
break;
}
const carrier = await this._getSpecificCarrierAsync(data.CarrierID);
return {
error: data.Error,
shipmentId: data.OrderID,
smsid: data.TransactionID,
carrier,
trackingCarrier: data.TrackingCarrier,
trackingNumber: data.TrackingNumber,
status: shipmentStatus,
labelUrl: Array.isArray(data.LabelURL) ? data.LabelURL : [data.LabelURL],
};
}
// MAPPING FUNCTIONS - GET CARRIERS
_mapGetCarriersInputAsync() {
return new Promise((resolve) => {
resolve({
Method: 'GetCarriers',
Params: {}
});
});
}
_mapGetCarriersOutputAsync(data) {
return new Promise((resolve) => {
let carriers = [];
for (const carrierName in data.Carriers) {
if (Object.hasOwnProperty.call(data.Carriers, carrierName)) {
const carrierServices = data.Carriers[carrierName];
carrierServices.forEach(cs => {
carriers.push({
smsid: cs.CarrierID,
name: carrierName,
service: cs.CarrierService
});
});
}
}
resolve(carriers);
});
}
// MAPPING FUNCTIONS - GET/CREATE MANIFEST
_mapCreateManifestInputAsync(data) {
return new Promise((resolve) => {
resolve({
Method: 'CreateManifest',
Params: {
OrderIDS: [parseInt(data)] // it will be a single number instead of a string, otherwise shippypro fails
}
});
});
}
_mapGetManifestInputAsync(data) {
return new Promise((resolve) => {
resolve({
Method: 'GetManifest',
Params: {
ManifestNumber: data
}
});
});
}
_mapGetManifestOutputAsync(data) {
return new Promise((resolve) => {
const manifestUrl = Array.isArray(data.ManifestURL)
? data.ManifestURL[data.ManifestURL.length - 1]
: data.ManifestURL;
resolve({
error: data.Error,
manifestUrl: manifestUrl,
manifestNumber: data.ManifestNumber
});
});
}
_adaptParcels(data) {
// adapting parcel weight
const parcels = [];
for (const dataParcel of data.parcels) {
// from grams to Kg
dataParcel.weight /= 1000;
dataParcel.weight_unit = "KG";
dataParcel.height /= 10; // from mm to cm
dataParcel.length /= 10; // from mm to cm
dataParcel.width /= 10; // from mm to cm
parcels.push(dataParcel);
}
return parcels;
}
_adaptAddress(data) {
return {
name: data.name ? data.name.substring(0, 35) : '',
company: data.company ? data.company.substring(0, 35) : '',
street1: data.street1 ? data.street1.substring(0, 35) : '',
street2: data.street2 ? data.street2.substring(0, 35) : '',
city: data.city ? data.city.substring(0, 35) : '',
state: data.state ? data.state.substring(0, 35) : '',
zip: data.zip ? data.zip.substring(0, 12) : '',
country: data.country,
phone: data.phone ? data.phone.substring(0, 25) : '+39',
email: data.email ? data.email.substring(0, 50) : '',
}
}
// MAPPING FUNCTIONS - PICKUP
async _mapBookPickupInputAsync(data) {
const carrier = await this._getSpecificCarrierAsync(data.carrierId);
const parcels = this._adaptParcels(data);
const to_address = this._adaptAddress(data.to);
const from_address = this._adaptAddress(data.from);
return {
Method: 'BookPickup',
Params: {
to_address: to_address,
from_address: from_address,
parcels: parcels,
CarrierName: carrier.name,
CarrierID: data.carrierId,
PickupTime: Math.round(data.pickupTime.getTime() / 1000),
PickupNote: data.pickupNote ? data.pickupNote : '',
PickupMorningMintime: data.pickupMorningMinTime,
PickupMorningMaxtime: data.pickupMorningMaxTime,
PickupAfternoonMintime: data.pickupAfternoonMinTime,
PickupAfternoonMaxtime: data.pickupAfternoonMaxTime,
OrderIds: data.orderIds,
}
};
}
_mapBookPickupOutputAsync(data) {
return new Promise((resolve) => {
if (data.Result === 'OK') {
return resolve({
result: `${data.Result}`,
message: `${data.Message}`,
confirmationId: `${data.ConfirmationID}`,
});
} else {
return resolve({
error: `${data.Error}`,
message: `${data.ValidationErrors}`,
})
}
});
}
}
module.exports = {
adapter: new ShippyProAdapter(),
};