# Parcel/Package Object This object is supplied a plugin registered with `registerRateEndpoint` when PostalPoint requests shipping rates from the plugin. ```javascript export class Package { constructor(isPrepaid = false) { this.prepaid = isPrepaid; this.packaging = { type: "Parcel", service: "", carrier: "", length: 999999, width: 999999, height: 999999, weightOz: 999999, nonmachinable: false, internalid: 100, oversizeFlag: false }; this.extraServices = { certifiedMail: false, barcode3800: "", registeredMail: false, registeredMailAmount: false, // can be a number in USD returnReceipt: false, returnReceiptElectronic: false, insurance: false, // can be a number in USD signature: false, // can be false, "SIGNATURE", or "SIGNATURE_RESTRICTED" hazmat: false, liveAnimal: false, // DAY_OLD_POULTRY cod: false, // Collect on Delivery codAmount: false, endorsement: "" // ADDRESS_SERVICE_REQUESTED, CHANGE_SERVICE_REQUESTED, FORWARDING_SERVICE_REQUESTED, LEAVE_IF_NO_RESPONSE, RETURN_SERVICE_REQUESTED }; this.specialRateEligibility = false; this.customs = { contents: "", contentsExplanation: "", // needed if contents is "other" signature: "", restriction: "", restrictionComments: "", // needed if restriction is "other" nonDelivery: "return", // "return" or "abandon", items: [] // {index: 0, description: "", qty: "", weight: "", value: "", hscode: "", origin: US"} }; this.toAddress = new Address(); this.returnAddress = new Address(); this.originAddress = new Address(); this.trackingNumber = ""; } /** * Format as EasyPost shipment object * @returns {Package.toEasyPostShipment.shipment} */ async toEasyPostShipment() { // removed } /** * Format as Endicia shipment object * @returns {Package.toSERAShipment.shipment} */ async toSERAShipment() { // removed } toJSON() { return { prepaid: this.prepaid, packaging: this.packaging, extraServices: this.extraServices, specialRateEligibility: this.specialRateEligibility, customs: this.customs, toAddress: this.toAddress, returnAddress: this.returnAddress, originAddress: this.originAddress, trackingNumber: this.trackingNumber }; } /** * Get a human-readable summary of size and options. * Does not include address data. * @returns {String} */ async toString() { let summary = []; let packaging = await getPackagingByID(this.packaging.internalid); let weight = ozToLbsOz(this.packaging.weightOz); let weightStr = this.packaging.weightOz >= 16 ? `${weight[0]} lbs ${weight[1]} oz` : `${weight[1]} oz`; if (packaging != false) { if (packaging.weight === false) { summary.push(packaging.name); } else { summary.push(`${weightStr} ${packaging.name}`); } } else { summary.push(weightStr); } if (this.extraServices.liveAnimal) { summary.push("Contains Live Animals"); } if (this.extraServices.certifiedMail) { summary.push("Certified Mail"); } else if (this.extraServices.registeredMail) { summary.push("Registered Mail"); summary.push("Registered for $" + (this.extraServices.registeredMailAmount * 1.0).toFixed(2)); } else if (this.extraServices.signature == "SIGNATURE") { summary.push("Signature Required"); } if (this.extraServices.signature == "SIGNATURE_RESTRICTED") { summary.push("Restricted Delivery"); } if (this.extraServices.returnReceiptElectronic) { summary.push("Return Receipt Electronic"); } if (this.extraServices.returnReceipt) { summary.push("Return Receipt"); } if (this.extraServices.insurance) { summary.push("Insured for $" + (this.extraServices.insurance * 1.0).toFixed(2)); } if (this.extraServices.cod) { summary.push("Collect on Delivery: $" + (this.extraServices.codAmount * 1.0).toFixed(2)); } return summary.join("\n"); } async needsHAZMATPrompt() { try { let packagingInfo = await getPackagingByID(this.packaging.internalid); if (packagingInfo.hazmat) { return true; } if (this.packaging.weight > 10) { return true; } if (packagingInfo.l >= -1 && Math.max(this.packaging.length, this.packaging.width, this.packaging.height) > 0.5) { return true; } switch (packagingInfo.type) { case "Letter": case "Card": return false; } return true; } catch (ex) { return true; } } get isPrepaid() { return this.prepaid == true; } setCustomsInfo(contents, contentsExplanation, signature, restriction, restrictionComments, nonDelivery) { let items = this.customs.items; // Save this and copy it back in so we don't overwrite it this.customs = { contents: contents, contentsExplanation: contentsExplanation, // needed if contents is "other" signature: signature, restriction: restriction, restrictionComments: restrictionComments, // needed if restriction is "other" nonDelivery: nonDelivery, // "return" or "abandon", items: items }; } /** * Get the customs items, ignoring any that are blank. * @returns {Array} */ getCustomsItems() { let items = []; for (let i = 0; i < this.customs.items.length; i++) { let item = this.customs.items[i]; if (item.description == "" && (item.qty == "" || item.qty == 0) && (item.weight == "" || item.weight == 0) && (item.value == "" || item.value == 0)) { continue; } items.push(item); } return items; } setCustomsItems(items) { this.customs.items = items; } getCustoms() { this.customs.items = this.getCustomsItems(); return this.customs; } /** * Attempt to automatically fix simple issues like overweight letters. * @returns {undefined} */ async fixIssues() { if (this.packaging.type == "Letter" && this.packaging.weightOz > 3.5) { if (this.packaging.nonmachinable) { return; // Has to be a parcel, can't fix without dimensions } this.packaging.type = "Flat"; this.packaging.internalid = 104; } } /** * Do some basic checks to see if this package is even remotely shippable * @param {boolean} kioskMode If true, returned strings are suitable for display in kiosk mode. * @returns {boolean|string} true if okay, human-readable error message and instructions if not okay */ async isValid(kioskMode = false) { // removed for brevity. Just a bunch of if statements. } /** * Set package characteristics * @param {string} type "Parcel", "Letter", "Flat", "Card" * @param {type} service * @param {type} carrier * @param {type} length * @param {type} width * @param {type} height * @param {type} weightOz * @returns {undefined} */ setPackaging(type, service, carrier, length, width, height, weightOz, nonmachinable) { if (typeof nonmachinable == "undefined") { nonmachinable = false; } if (type == "Card") { // Postcards weightOz = 1; this.packaging.internalid = 105; } else if (type == "Flat") { this.packaging.internalid = 104; } else if (type == "Letter") { this.packaging.internalid = 102; if (nonmachinable) { this.packaging.internalid = 103; } } this.packaging.type = type; this.packaging.service = service; this.packaging.carrier = carrier; this.packaging.weightOz = weightOz; this.packaging.nonmachinable = nonmachinable; // Enforce Length > Width > Height let size = [length, width, height]; size.sort(function (a, b) { return b - a; }); this.packaging.length = size[0]; this.packaging.width = size[1]; this.packaging.height = size[2]; } /** * Set an extra service * @param {string} id Service ID * @param {boolean} enabled Turn it on or off * @param {string} value Service value, if needed (some are not just a boolean) * @returns {undefined} */ setExtraService(id, enabled, value) { if (typeof value != "undefined" && enabled) { this.extraServices[id] = value; } else { this.extraServices[id] = enabled == true; } } getExtraServices() { return this.extraServices; } /** * Set to "MEDIA_MAIL", "LIBRARY_MAIL", or false * @param {type} rate * @returns {undefined} */ set specialRate(rate) { if (rate == "MEDIA") { rate = "MEDIA_MAIL"; } else if (rate == "LIBRARY") { rate = "LIBRARY_MAIL"; } if (rate != "MEDIA_MAIL" && rate != "LIBRARY_MAIL") { rate = false; } this.specialRateEligibility = rate; } get specialRate() { return this.specialRateEligibility; } /** * Save an address to this package. * @param {string} type "to", "return", or "origin" * @param {string} name * @param {string} company * @param {string} street1 * @param {string} street2 * @param {string} city * @param {string} state * @param {string} zip * @param {string} country ISO 2-char country code * @param {string} phone * @param {string} email * @returns {undefined} */ setAddress(type, name, company, street1, street2, city, state, zip, country, phone, email) { let address = Address.fromObject({ name: name, company: company, street1: street1, street2: street2, city: city, state: state, zip: zip, country: country, phone: phone, email: email }); switch (type) { case "to": this.toAddress = address; break; case "return": this.returnAddress = address; break; case "origin": this.originAddress = address; break; } } /** * Set an address using an object that matches the internal form (see setAddress()) * @param {string} type * @param {object} data * @returns {undefined} */ setAddressWhole(type, address) { switch (type) { case "to": this.toAddress = Address.fromObject(address); break; case "return": this.returnAddress = Address.fromObject(address); break; case "origin": this.originAddress = Address.fromObject(address); break; } } get tracking() { return this.trackingNumber; } set tracking(n) { this.trackingNumber = n; } /** * Get the "from" address that will be shown, * using the return address or origin address as needed * @returns {address} */ getReturnAddress() { if (typeof this.returnAddress == "object") { return this.returnAddress; } return this.originAddress; } getToAddress() { return this.toAddress; } getFromAddress() { if (typeof this.originAddress == "object") { return this.originAddress; } return this.returnAddress; } } ```