Open TheFloydman opened 3 years ago
I think this is a great idea, in general doing it like this looks more clean. If we choose these approach we should look how to pass optional arguments and how to append pieces of XML correctly. Maybe you already have an solution for this ?
Yes, I've been poking around with rating.js. In this case, I've added mandatory checks before the json is built and optional checks after it's built. Duplicate tags (like <Package>
) are supported. You just have to make the element an array. I've tested this and it is working:
const https = require("https");
const buildAccessRequest = require("../utils/accesRequest");
createRating = function() {
return function RatingRequest(options) {
settings = this;
return new Promise(function(resolve, reject) {
try {
const req = https.request({
host: settings.sandbox ? settings.SANDBOX_API : settings.LIVE_API,
path: "/ups.app/xml/Rate",
method: "POST",
});
/* build the request data for rating and write it to
the request body
*/
const { body } = buildRequestData(options, reject);
req.write(body);
req.on("response", function(res) {
let responseData = "";
res.on("data", function(data) {
data = data.toString();
responseData += data;
});
res.on("end", function() {
if (settings.useJSON) {
let parseString = require("xml2js").parseString;
parseString(responseData, function(err, result) {
if (err) reject(err.message);
if (result.RatingServiceSelectionResponse.Response[0].Error) {
reject(result.RatingServiceSelectionResponse.Response[0].Error[0]);
} else {
resolve(result);
}
});
} else {
resolve(responseData);
}
});
});
req.on("error", (e) => {
reject(e.message);
});
req.end();
} catch (error) {
reject(error.message);
}
});
};
};
function buildRequestData(data, reject) {
if (!settings.licenseId || !settings.userId || !settings.password) reject("Credentials missing");
if (!data.customerContext) reject("Missing Customer Context");
if (!data.shipment) reject("Missing Shipment");
if (!data.shipment.shipper.address) reject("Missing shipment address");
if (!data.shipment.shipTo) reject("Missing ShipTo");
if (!data.shipment.shipFrom) reject("Missing shipFrom");
if (!data.shipment.service) reject("Missing shipment service");
if (!data.shipment.package) reject("Missing Shipment Packages");
if (!data.shipment.schedule) reject("Missing Shipment Schedule");
var request = {
AccessRequest: buildAccessRequest(settings.licenseId, settings.userId, settings.password).AccessRequest,
RatingServiceSelectionRequest: {
$: {
'xml:lang': 'en-US'
},
Request: {
TransactionReference: {
CustomerContext: 'Rating and Service',
XpciVersion: 1.0
},
RequestAction: 'Rate'
},
Shipment: {
Description: data.shipment.description,
Shipper: {
Name: data.shipment.name,
PhoneNumber: data.shipment.phoneNumber,
ShipperNumber: data.shipment.shipperNumber,
Address: {
AddressLine1: data.shipment.shipper.address.addressLine,
City: data.shipment.shipper.address.city,
StateProvinceCode: data.shipment.shipper.address.stateProvinceCode,
PostalCode: data.shipment.shipper.address.postalCode,
CountryCode: data.shipment.shipper.address.countryCode
}
},
ShipTo: {
CompanyName: data.shipment.shipTo.companyName,
PhoneNumber: data.shipment.shipTo.phoneNumber,
Address: {
AddressLine1: data.shipment.shipTo.address.addressLine,
City: data.shipment.shipTo.address.city,
StateProvinceCode: data.shipment.shipTo.address.stateProvinceCode,
PostalCode: data.shipment.shipTo.address.postalCode,
CountryCode: data.shipment.shipTo.address.countryCode
}
},
ShipFrom: {
CompanyName: data.shipment.shipFrom.companyName,
PhoneNumber: data.shipment.shipFrom.phoneNumber,
FaxNumber: data.shipment.shipFrom.faxNumber,
Address: {
AddressLine1: data.shipment.shipFrom.address.addressLine,
City: data.shipment.shipFrom.address.city,
StateProvinceCode: data.shipment.shipFrom.address.stateProvinceCode,
PostalCode: data.shipment.shipFrom.address.postalCode,
CountryCode: data.shipment.shipFrom.address.countryCode
}
},
Service: {
Code: data.shipment.service.code
},
ShipmentServiceOptions: {
OnCallAir: {
Schedule: {
PickupDay: data.shipment.schedule.pickupDay,
Method: data.shipment.schedule.method
}
}
}
}
}
};
if (data.pickUpType) {
request.RatingServiceSelectionRequest.PickupType = {
Code: data.pickUpType.code,
Description: data.pickUpType.description
};
}
if (data.shipment.paymentInformation) {
request.RatingServiceSelectionRequest.PaymentInformation = {
Prepaid: {
BillShipper: {
AccountNumber: data.shipment.paymentInformation.accountNumber
}
}
};
}
var packages = [];
data.shipment.package.forEach(function(val) {
insert = buildPackageInternals(val);
if (insert) {
packages.push(insert);
} else {
reject("Bad Package Internals");
}
request.RatingServiceSelectionRequest.Shipment.Package = packages;
});
var xml2js = require('xml2js');
var builder = new xml2js.Builder();
var unrooted = builder.buildObject(request).replace('<root>', '').replace('</root>', '');
return { body: unrooted };
}
const buildPackageInternals = function(val) {
var request = {
PackagingType: {
Code: val.code || '02'
},
PackageWeight: {
Weight: val.weight || '1'
}
};
if (val.description) {
request.Description = val.description;
}
//TODO: Insurance
if (val.insurance) {
request.PackageServiceOptions = {
InsuredValue: buildInsurance(val.insurance)
};
}
return request;
};
module.exports = createRating;
How would you feel about using xml2js more to create the XML, rather than adding on to a continuous xml string?
buildAccesRequest.js could be this:
Also makes it less likely you'll mess up closing the element.