# 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, additionalHandling: 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, perishable: false, crematedRemains: false, liveAnimal: false, // BEES, DAY_OLD_POULTRY, ADULT_BIRDS, OTHER_LIVES cod: false, // Collect on Delivery codAmount: false, endorsement: "", // ADDRESS_SERVICE_REQUESTED, CHANGE_SERVICE_REQUESTED, FORWARDING_SERVICE_REQUESTED, LEAVE_IF_NO_RESPONSE, RETURN_SERVICE_REQUESTED carrier_billing_account: {// Bill a third party's account number for the label type: "", // "" (ignores this entire option), "SENDER" (EasyPost default), "THIRD_PARTY", "RECEIVER", "COLLECT" carrier: "", // Carrier ID (should be used to filter rates) account_number: "", // Carrier account number to bill country: "", // Country account is based in postal_code: "" // Postal code of account }, dryIce: false, dryIceWeight: 0, dryIceMedical: false }; 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", eel_pfc: "", items: [] // {index: 0, description: "", qty: "", lbs: "", oz: "", 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() { // Not relevant to plugins } /** * Format as Endicia shipment object * @returns {Package.toSERAShipment.shipment} */ async toSERAShipment() { // Not relevant to plugins } /** * 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.irregular) { if (packaging.weight === false) { summary.push("Parcel"); } else { summary.push(`${weightStr} Parcel`); } summary.push("Additional Handling"); } else { if (packaging.weight === false) { summary.push(packaging.name); } else { summary.push(`${weightStr} ${packaging.name}`); } } } else { summary.push(weightStr); } if (this.extraServices.hazmat) { summary.push("HAZMAT"); } if (this.extraServices.liveAnimal === true) { summary.push("Live Animals"); } else if (typeof this.extraServices.liveAnimal == "string") { switch (this.extraServices.liveAnimal) { case "BEES": summary.push("Live Bees"); break; case "DAY_OLD_POULTRY": summary.push("Day-old Poultry"); break; case "ADULT_BIRDS": summary.push("Live Adult Birds"); break; case "OTHER_LIVES": default: summary.push("Live Animals"); break; } } if (this.extraServices.perishable) { summary.push("Perishable"); } if (this.extraServices.crematedRemains) { summary.push("Cremated Remains"); } 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 == "ADULT_SIGNATURE") { summary.push("Adult 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)); } if (this.extraServices.dryIce && this.extraServices.dryIceWeight > 0) { summary.push("Dry Ice: " + (this.extraServices.dryIceWeight * 1).toFixed(0) + " oz"); } if (this.extraServices.carrier_billing_account?.type) { if (this.extraServices.carrier_billing_account.type != "") { var accountNumber = this.extraServices.carrier_billing_account.account_number; var accountNumberCensored = accountNumber.substring(accountNumber.length - 4).padStart(accountNumber.length, "X"); var carrierName = this.extraServices.carrier_billing_account.carrier; switch (this.extraServices.carrier_billing_account.type) { case "SENDER": summary.push(`Bill to sender ${carrierName} account #${accountNumberCensored}`); break; case "THIRD_PARTY": summary.push(`Bill to third party ${carrierName} account #${accountNumberCensored}`); break; case "RECEIVER": summary.push(`Bill to receiver ${carrierName} account #${accountNumberCensored}`); break; case "COLLECT": if (accountNumber.length > 0) { summary.push(`Bill collect ${carrierName} account #${accountNumberCensored}`); } else { summary.push(`Bill collect`); } break; } } } 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 from docs for brevity. Just a bunch of if statements to catch problems. } /** * 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() { var a = null; if (typeof this.returnAddress == "object") { a = Address.fromObject(this.returnAddress); } else { a = Address.fromObject(this.originAddress); } if (a.country == "") { a.country = defaultCountryCode(); } return a; } getToAddress() { var a = Address.fromObject(this.toAddress); if (a.country == "") { a.country = defaultCountryCode(); } return a; } getFromAddress() { var a = null; if (typeof this.originAddress == "object") { a = Address.fromObject(this.originAddress); } else { a = Address.fromObject(this.returnAddress); } if (a.country == "") { a.country = defaultCountryCode(); } return a; } } ```