diff --git a/next/Plugin API/ui/index.html b/next/Plugin API/ui/index.html index e789a72..509b023 100644 --- a/next/Plugin API/ui/index.html +++ b/next/Plugin API/ui/index.html @@ -1802,7 +1802,7 @@ will be run as a function and will not be expected to return a page component. id string -Page ID. Make it unique, or pass an empty string to be assigned a random ID. +Page ID. Make it unique, or pass an empty string to be assigned a random ID. Tools with a random ID are not pinnable to the program's main navigation bar. description diff --git a/next/search/search_index.json b/next/search/search_index.json index 44fff48..90d9dd3 100644 --- a/next/search/search_index.json +++ b/next/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Overview","text":"

PostalPoint\u00ae supports JavaScript plugin extensions. Plugins can hook into PostalPoint to add features and integrations.

"},{"location":"#what-plugins-can-do","title":"What plugins can do","text":""},{"location":"#postalpoint-devtools-and-testing-builds","title":"PostalPoint DevTools and Testing Builds","text":"

The PostalPoint build server creates installers from the latest prerelease changes. It is not recommended to use these builds for production purposes, but they contain the latest changes to plugin APIs.

For Windows developers, you'll want to download a \"postalpoint-retail-sdk_x.xx.exe\" installer, as it contains the Chromium DevTools.

To enable DevTools on Linux, simply run sudo apt install nw.js-sdk and restart PostalPoint.

To access the DevTools, press F12 or right-click anywhere inside PostalPoint and click Inspect. Depending on various factors, some plugin console output may go to the \"background page\"; right-click and click \"Inspect background page\" to view that console.

"},{"location":"#plugin-package-structure","title":"Plugin Package Structure","text":"

A plugin is distributed as a simple ZIP file, containing a folder. The folder then has at least one file, named plugin.js. The exports.init function in plugin.js is executed when PostalPoint launches, allowing the plugin to request involvement with various events in PostalPoint.

PostalPoint installs plugin packages by unzipping their contents into a plugins folder. Plugins are uninstalled by deleting their folder.

"},{"location":"#postalpoint-plugin-api","title":"PostalPoint Plugin API","text":"

The PostalPoint plugin API is a globally-available object named global.apis. It contains many useful functions for integrating with PostalPoint. All the APIs listed under the Plugin API section must be prefixed with global.apis. in order to work.

"},{"location":"#minimal-plugin-code","title":"Minimal Plugin Code","text":"plugin-name/plugin.js
exports.init = function () {\n    global.apis.alert(\"This message appears when PostalPoint launches.\", \"Hello!\");\n};\n

Yes, the smallest plugin really is just two lines of code, and accessing PostalPoint features really is that easy.

"},{"location":"#plugin-metadata-file","title":"Plugin Metadata File","text":"

While not strictly required, a package.json is encouraged, and allows specifying the plugin's display name, PostalPoint version compatibility, and other metadata.

Sample:

{\n    \"name\": \"plugin-id-here\",\n    \"main\": \"plugin.js\",\n    \"description\": \"Human-readable description of the plugin\",\n    \"version\": \"1.0.0\",\n    \"author\": \"Your Name\",\n    \"license\": \"Code license name\",\n    \"postalpoint\": {\n        \"pluginname\": \"Display Name for Plugin\",\n        \"minVersion\": \"000034\",\n        \"maxVersion\": \"001000\"\n    }\n}\n

PostalPoint version codes are MMMnnn where MMM is the major version and nnn is the minor version, zero-padded. So version 0.35 is \"000035\", and 1.23 is \"001023\".

"},{"location":"Docs/Address/","title":"Address object","text":"
export default class Address {\n    constructor(uuid = \"\", name = \"\", company = \"\", street1 = \"\", street2 = \"\", zip = \"\", city = \"\", state = \"\", country = \"\", phone = \"\", email = \"\", taxid = \"\") {\n        this.uuid = uuid;\n        this.name = name;\n        this.company = company;\n        this.street1 = street1;\n        this.street2 = street2;\n        this.zip = zip;\n        this.city = city;\n        this.state = state;\n        this.country = country;\n        this.phone = phone;\n        this.email = email;\n        this.taxid = taxid;\n        this.residential = null;\n    }\n\n    static fromObject(address) {\n        if (address instanceof Address) {\n            return address;\n        }\n        var a = new Address(address.uuid ?? \"\", address.name, address.company, address.street1,\n                address.street2, address.zip, address.city, address.state, address.country,\n                address.phone, address.email, address.taxid);\n        return a;\n    }\n\n    toStringArray(expandCountry = false) {\n        var citystatezipLine = [this.city, this.state, this.zip].filter(Boolean);\n        var country = this.country == defaultCountryCode() ? \"\" : this.country;\n        if (expandCountry && country != \"\") {\n            country = getCountryNameForISO(country);\n        }\n        return [this.name, this.company, this.street1, this.street2, `${citystatezipLine.join(\" \")}`, country, (this.taxid ? \"Tax ID \" + this.taxid : \"\")].filter(Boolean);\n    }\n\n    /**\n     * Test if the address provided is the same as this address.\n     */\n    equals(address, checkUUID = false) {\n        if (\n                (checkUUID ? this.uuid == address.uuid : true)\n                && this.name == address.name\n                && this.company == address.company\n                && this.street1 == address.street1\n                && this.street2 == address.street2\n                && this.city == address.city\n                && this.state == address.state\n                && this.zip == address.zip\n                && this.country == address.country\n                && this.taxid == address.taxid) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Test if an address is the same delivery point as this address.\n     */\n    dpEquals(address) {\n        if (\n                this.street1 == address.street1\n                && this.street2 == address.street2\n                && this.city == address.city\n                && this.state == address.state\n                && this.zip == address.zip\n                && this.country == address.country) {\n            return true;\n        }\n        return false;\n    }\n}\n
"},{"location":"Docs/Carrier_Service/","title":"getCarrierName and getServiceName","text":"

This is the source code for global.apis.shipping.getCarrierName and global.apis.shipping.getServiceName.

"},{"location":"Docs/Carrier_Service/#getcarriername","title":"getCarrierName","text":"
export function getCarrierName(carrier) {\n    switch (carrier) {\n        case \"USPS\":\n        case \"USPSReturns\":\n        case \"usps\":\n            return \"USPS\";\n        case \"UPS\":\n        case \"UPSDAP\":\n        case \"ups\":\n            return \"UPS\";\n        case \"FedExDefault\":\n        case \"FedExSmartPost\":\n        case \"fedex\":\n            return \"FedEx\";\n        case \"DHLExpress\":\n        case \"dhl_express\":\n            return \"DHL Express\";\n        case \"DhlEcs\":\n            return \"DHL eCommerce\";\n        case \"SpeedeeAccount\":\n            return \"Spee-Dee\";\n        case \"globalpost\":\n            return \"GlobalPost\";\n        case \"AccurateAccount\":\n            return \"Accurate Courier Express\";\n        case \"ApcAccount\":\n            return \"APC\";\n        case \"AsendiaUsaAccount\":\n            return \"Asendia\";\n        case \"BetterTrucksAccount\":\n            return \"Better Trucks\";\n        case \"BluestreakAccount\":\n            return \"Blue Streak\";\n        case \"CanadaPostAccount\":\n        case \"CanadaPost\":\n            return \"Canada Post\";\n        case \"GsoAccount\":\n            return \"GLS\";\n        case \"LsoAccount\":\n            return \"LSO\";\n        case \"OntracV3Account\":\n            return \"OnTrac\";\n        case \"USAExportPBA\":\n            return \"Asendia\";\n        case \"FirstChoiceAccount\":\n            return \"1st Choice Delivery\";\n        case \"PurolatorAccount\":\n            return \"Purolator\";\n        case \"CanparAccount\":\n            return \"Canpar\";\n        case \"MaerskAccount\":\n            return \"Maersk\";\n    }\n    if (carrier.endsWith(\"Account\")) {\n        // A lot of EasyPost carriers are the carrier name and the word Account\n        return carrier.replace(\"Account\", \"\");\n    }\n    return carrier;\n}\n
"},{"location":"Docs/Carrier_Service/#getservicename","title":"getServiceName","text":"
export function getServiceName(serviceId, carrierId = \"USPS\") {\n    var carrier = getCarrierName(carrierId);\n    switch (serviceId) {\n        // Ambiguous\n        case \"\":\n            // Used internally if no service ID defined by a plugin or whatever\n            return \"\";\n        case \"Ground\":\n            return \"Ground\";\n        case \"Express\":\n            if (carrier == \"UPS\") {\n                return \"Worldwide Express\";\n            } else if (carrier == \"USPS\") {\n                return \"Priority Mail Express\";\n            } else {\n                return \"Express\";\n            }\n        case \"Priority\":\n            if (carrier == \"USPS\") {\n                return \"Priority Mail\";\n            }\n            return \"Priority\";\n\n\n            // USPS\n        case \"First\":\n        case \"usps_first_class_mail\":\n        case \"First-Class Mail\":\n            return \"First-Class Mail\";\n        case \"FirstClassMailInternational\":\n        case \"usps_first_class_mail_international\":\n        case \"First-Class Mail International\":\n            return \"First-Class Mail International\";\n        case \"GroundAdvantage\":\n        case \"usps_ground_advantage\":\n        case \"Ground Advantage\":\n            return \"Ground Advantage\";\n        case \"MediaMail\":\n        case \"usps_media_mail\":\n        case \"Media Mail\":\n            return \"Media Mail\";\n        case \"LibraryMail\":\n        case \"usps_library_mail\":\n        case \"Library Mail\":\n            return \"Library Mail\";\n        case \"usps_priority_mail\":\n        case \"Priority Mail\":\n            return \"Priority Mail\";\n        case \"PriorityMailInternational\":\n        case \"usps_priority_mail_international\":\n        case \"Priority Mail International\":\n            return \"Priority Mail International\";\n        case \"usps_priority_mail_express\":\n        case \"Priority Mail Express\":\n            return \"Priority Mail Express\";\n        case \"ExpressMailInternational\":\n        case \"usps_priority_mail_express_international\":\n        case \"Priority Mail Express International\":\n            return \"Priority Mail Express International\";\n        case \"FirstClassPackageInternationalService\":\n        case \"First-Class Pkg International\":\n            return \"First-Class Pkg International\";\n\n\n            // UPS\n        case \"UPSWorldwideExpress\":\n        case \"ups_worldwide_express\":\n            return \"Worldwide Express\";\n        case \"ExpressPlus\":\n        case \"UPSExpressPlus\":\n        case \"ups_worldwide_express_plus\":\n            return \"Worldwide Express Plus\";\n        case \"Expedited\":\n        case \"UPSWorldwideExpedited\":\n        case \"ups_worldwide_expedited\":\n            return \"Worldwide Expedited\";\n        case \"UPSWorldwideEconomyDDU\":\n            return \"Worldwide Economy DDU\";\n        case \"NextDayAir\":\n        case \"ups_next_day_air\":\n            return \"Next Day Air\";\n        case \"NextDayAirSaver\":\n        case \"ups_next_day_air_saver\":\n            return \"Next Day Air Saver\";\n        case \"NextDayAirEarlyAM\":\n        case \"ups_next_day_air_early\":\n            return \"Next Day Air A.M.\";\n        case \"2ndDayAir\":\n        case \"ups_2nd_day_air\":\n            return \"2nd Day Air\";\n        case \"2ndDayAirAM\":\n        case \"ups_2nd_day_air_am\":\n            return \"2nd Day Air A.M.\";\n        case \"3DaySelect\":\n        case \"ups_3_day_select\":\n            return \"3-Day Select\";\n        case \"ups_ground\":\n            return \"Ground\";\n        case \"UPSGroundsaverGreaterThan1lb\":\n            return \"Ground Saver\";\n        case \"UPSStandard\":\n        case \"ups_standard\":\n            return \"Standard\";\n        case \"UPSSaver\":\n        case \"ups_worldwide_saver\":\n            return \"Worldwide Saver\";\n\n\n            // FedEx\n        case \"FEDEX_GROUND\":\n        case \"fedex_ground\":\n            return \"Ground\";\n        case \"FEDEX_2_DAY\":\n        case \"fedex_2day\":\n            return \"2 Day\";\n        case \"FEDEX_2_DAY_AM\":\n        case \"fedex_2day_am\":\n            return \"2 Day AM\";\n        case \"FEDEX_EXPRESS_SAVER\":\n        case \"fedex_express_saver\":\n            return \"Express Saver\";\n        case \"STANDARD_OVERNIGHT\":\n        case \"fedex_standard_overnight\":\n            return \"Standard Overnight\";\n        case \"FIRST_OVERNIGHT\":\n        case \"fedex_first_overnight\":\n            return \"First Overnight\";\n        case \"PRIORITY_OVERNIGHT\":\n        case \"fedex_priority_overnight\":\n            return \"Priority Overnight\";\n        case \"INTERNATIONAL_ECONOMY\":\n        case \"fedex_international_economy\":\n            return \"International Economy\";\n        case \"INTERNATIONAL_FIRST\":\n        case \"fedex_international_first\":\n            return \"International First\";\n        case \"FEDEX_INTERNATIONAL_PRIORITY\":\n        case \"INTERNATIONAL_PRIORITY\":\n        case \"fedex_international_priority\":\n            return \"International Priority\";\n        case \"FEDEX_INTERNATIONAL_PRIORITY_EXPRESS\":\n        case \"INTERNATIONAL_PRIORITY_EXPRESS\":\n            return \"International Priority Express\";\n        case \"FEDEX_INTERNATIONAL_CONNECT_PLUS\":\n        case \"INTERNATIONAL_CONNECT_PLUS\":\n            return \"International Connect Plus\";\n        case \"GROUND_HOME_DELIVERY\":\n        case \"fedex_home_delivery\":\n            return \"Ground Home Delivery\";\n        case \"SMART_POST\":\n            return \"Ground Economy\";\n        case \"FEDEX_FIRST_FREIGHT\":\n            return \"First Overnight Freight\";\n        case \"FEDEX_1_DAY_FREIGHT\":\n            return \"1-Day Freight\";\n        case \"FEDEX_2_DAY_FREIGHT\":\n            return \"2-Day Freight\";\n        case \"FEDEX_3_DAY_FREIGHT\":\n            return \"3-Day Freight\";\n        case \"INTERNATIONAL_PRIORITY_FREIGHT\":\n            return \"International Priority Freight\";\n        case \"INTERNATIONAL_ECONOMY_FREIGHT\":\n            return \"International Economy Freight\";\n\n\n            // USPS Returns\n        case \"GroundAdvantageReturn\":\n            return \"Ground Advantage Return\";\n        case \"PriorityMailReturn\":\n            return \"Priority Mail Return\";\n        case \"PriorityMailExpressReturn\":\n            return \"Priority Mail Express Return\";\n\n\n            // DHL Express\n        case \"DomesticEconomySelect\":\n            return \"Domestic Economy Select\";\n        case \"DomesticExpress\":\n            return \"Domestic Express\";\n        case \"DomesticExpress1030\":\n            return \"Domestic Express 10:30\";\n        case \"DomesticExpress1200\":\n            return \"Domestic Express 12:00\";\n        case \"EconomySelect\":\n            return \"Economy Select Document\";\n        case \"EconomySelectNonDoc\":\n            return \"Economy Select Non-Document\";\n        case \"EuroPack\":\n            return \"EuroPack Document\";\n        case \"EuropackNonDoc\":\n            return \"EuroPack Non-Document\";\n        case \"Express1030\":\n            return \"10:30 Document\";\n        case \"Express1030NonDoc\":\n            return \"10:30 Non-Document\";\n        case \"Express1200NonDoc\":\n            return \"12:00 Non-Document\";\n        case \"Express1200\":\n            return \"12:00 Document\";\n        case \"Express900\":\n            return \"9:00 Document\";\n        case \"Express900NonDoc\":\n            return \"9:00 Non-Doc\";\n        case \"ExpressEasy\":\n            return \"Easy Document\";\n        case \"ExpressEasyNonDoc\":\n            return \"Easy Non-Document\";\n        case \"ExpressEnvelope\":\n            return \"Envelope\";\n        case \"ExpressWorldwide\":\n        case \"dhl_express_worldwide\":\n            return \"Worldwide Document\";\n        case \"ExpressWorldwideB2C\":\n            return \"Worldwide B2C\";\n        case \"ExpressWorldwideB2CNonDoc\":\n            return \"Worldwide B2C Non-Document\";\n        case \"ExpressWorldwideECX\":\n            return \"Worldwide ECX\";\n        case \"ExpressWorldwideNonDoc\":\n            return \"Worldwide Non-Document\";\n\n\n            // DHL eCommerce\n        case \"DHLParcelExpedited\":\n            return \"Parcel Expedited\";\n        case \"DHLParcelExpeditedMax\":\n            return \"Parcel Expedited Max\";\n        case \"DHLParcelGround\":\n            return \"Parcel Ground\";\n        case \"DHLBPMExpedited\":\n            return \"PM Expedited\";\n        case \"DHLBPMGround\":\n            return \"PM Ground\";\n        case \"DHLParcelInternationalDirect\":\n            return \"Parcel International Direct\";\n        case \"DHLParcelInternationalStandard\":\n            return \"Parcel International Standard\";\n        case \"DHLParcelInternationalDirectSMB\":\n            return \"Parcel International Direct\";\n        case \"DHLParcelInternationalStandardSMB\":\n            return \"Parcel International Standard\";\n        case \"DHLPacketInternational\":\n            return \"Packet International\";\n        case \"DHLParcelInternationalDirectPriority\":\n            return \"Parcel International Direct Priority\";\n        case \"DHLParcelInternationalDirectStandard\":\n            return \"Parcel International Direct Standard\";\n\n            // Asendia USA Export\n        case \"USAExportStandard\":\n            return \"USA Export Standard\";\n        case \"USAExportPlus\":\n            return \"USA Export Plus\";\n        case \"USAExportSelect\":\n            return \"USA Export Select\";\n\n            // Spee-Dee\n        case \"SpeeDeeDelivery\":\n            return \"Delivery\";\n\n            // GlobalPost\n        case \"globalpost_standard_international\":\n            return \"Standard International\";\n        case \"globalpost_economy_international\":\n            return \"Economy International\";\n        case \"globalpost_plus\":\n            return \"Plus\";\n        case \"globalpost_first_class_international\":\n            return \"First-Class International\";\n\n            // Canada Post\n        case \"RegularParcel\":\n            return \"Regular Parcel\";\n        case \"ExpeditedParcel\":\n            return \"Expedited Parcel\";\n        case \"Xpresspost\":\n            return \"Xpresspost\";\n        case \"ExpeditedParcelUSA\":\n            return \"Expedited Parcel USA\";\n        case \"SmallPacketUSAAir\":\n            return \"Small Packet USA Air\";\n        case \"TrackedPacketUSA\":\n            return \"Tracked Packet USA\";\n        case \"TrackedPacketUSALVM\":\n            return \"Tracked Packet USA LVM\";\n        case \"XpresspostUSA\":\n            return \"Xpresspost USA\";\n        case \"XpresspostInternational\":\n            return \"Xpresspost Int'l\";\n        case \"InternationalParcelAir\":\n            return \"Int'l Parcel Air\";\n        case \"InternationalParcelSurface\":\n            return \"Int'l Parcel Surface\";\n        case \"SmallPacketInternationalAir\":\n            return \"Small Packet Int'l Air\";\n        case \"SmallPacketInternationalSurface\":\n            return \"Small Packet Int'l Surface\";\n        case \"TrackedPacketInternational\":\n            return \"Tracked Packet Int'l\";\n        case \"ExpeditedParcelPlus\":\n            return \"Expedited Parcel Plus\";\n\n\n            // GLS US\n        case \"EarlyPriorityOvernight\":\n            return \"Early Priority Overnight\";\n        case \"PriorityOvernight\":\n            return \"Priority Overnight\";\n        case \"CaliforniaParcelService\":\n            return \"California Parcel\";\n        case \"SaturdayDeliveryService\":\n            return \"Saturday Delivery\";\n        case \"EarlySaturdayService\":\n            return \"Early Saturday\";\n        case \"NoonPriorityService\":\n            return \"Noon Priority\";\n\n            // OnTrac\n        case \"GRND\":\n            return \"Ground\";\n\n            // LSO\n        case \"PriorityEarly\":\n            return \"Priority Early\";\n        case \"PriorityBasic\":\n            return \"Priority Basic\";\n        case \"Priority2ndDay\":\n            return \"Priority 2nd Day\";\n        case \"GroundEarly\":\n            return \"Ground Early\";\n        case \"GroundBasic\":\n            return \"Ground Basic\";\n        case \"ECommerce\":\n            return \"ECommerce\";\n\n            // Canpar\n        case \"USA\":\n            return \"USA\";\n        case \"SelectLetter\":\n            return \"Select Letter\";\n        case \"SelectPak\":\n            return \"Select Pak\";\n        case \"Select\":\n            return \"Select\";\n        case \"OvernightLetter\":\n            return \"Overnight Letter\";\n        case \"Overnight\":\n            return \"Overnight\";\n        case \"USALetter\":\n            return \"USA Letter\";\n        case \"USAPak\":\n            return \"USA Pak\";\n        case \"SelectUSA\":\n            return \"Select USA\";\n        case \"International\":\n            return \"International\";\n\n            // Purolator\n        case \"PurolatorExpress\":\n            return \"Express\";\n        case \"PurolatorExpress12PM\":\n            return \"Express 12PM\";\n        case \"PurolatorExpressPack12PM\":\n            return \"Express Pack 12PM\";\n        case \"PurolatorExpressBox12PM\":\n            return \"Express Box 12PM\";\n        case \"PurolatorExpressEnvelope12PM\":\n            return \"Express Envelope 12PM\";\n        case \"PurolatorExpress1030AM\":\n            return \"Express 10:30AM\";\n        case \"PurolatorExpress9AM\":\n            return \"Express 9AM\";\n        case \"PurolatorExpressBox\":\n            return \"Express Box\";\n        case \"PurolatorExpressBox1030AM\":\n            return \"Express Box 10:30AM\";\n        case \"PurolatorExpressBox9AM\":\n            return \"Express Box 9AM\";\n        case \"PurolatorExpressBoxEvening\":\n            return \"Express Box Evening\";\n        case \"PurolatorExpressBoxInternational\":\n            return \"Express Box International\";\n        case \"PurolatorExpressBoxUS\":\n            return \"Express Box US\";\n        case \"PurolatorExpressEnvelope\":\n            return \"Express Envelope\";\n        case \"PurolatorExpressEnvelope1030AM\":\n            return \"Express Envelope 10:30AM\";\n        case \"PurolatorExpressEnvelope9AM\":\n            return \"Express Envelope 9AM\";\n        case \"PurolatorExpressEnvelopeEvening\":\n            return \"Express Envelope Evening\";\n        case \"PurolatorExpressEnvelopeInternational\":\n            return \"Express Envelope International\";\n        case \"PurolatorExpressEnvelopeUS\":\n            return \"Express Envelope US\";\n        case \"PurolatorExpressEvening\":\n            return \"Express Evening\";\n        case \"PurolatorExpressInternational\":\n            return \"Express International\";\n        case \"PurolatorExpressInternational1030AM\":\n            return \"Express International 10:30AM\";\n        case \"PurolatorExpressInternational1200\":\n            return \"Express International 12:00\";\n        case \"PurolatorExpressInternational9AM\":\n            return \"Express International 9AM\";\n        case \"PurolatorExpressBoxInternational1030AM\":\n            return \"Express Box International 10:30AM\";\n        case \"PurolatorExpressBoxInternational1200\":\n            return \"Express Box International 12:00\";\n        case \"PurolatorExpressBoxInternational9AM\":\n            return \"Express Box International 9AM\";\n        case \"PurolatorExpressEnvelopeInternational1030AM\":\n            return \"Express Envelope International 10:30AM\";\n        case \"PurolatorExpressEnvelopeInternational1200\":\n            return \"Express Envelope International 12:00\";\n        case \"PurolatorExpressEnvelopeInternational9AM\":\n            return \"Express Envelope International 9AM\";\n        case \"PurolatorExpressPackInternational1030AM\":\n            return \"Express Pack International 10:30AM\";\n        case \"PurolatorExpressPackInternational1200\":\n            return \"Express Pack International 12:00\";\n        case \"PurolatorExpressPackInternational9AM\":\n            return \"Express Pack International 9AM\";\n        case \"PurolatorExpressPack\":\n            return \"Express Pack\";\n        case \"PurolatorExpressPack1030AM\":\n            return \"Express Pack 10:30AM\";\n        case \"PurolatorExpressPack9AM\":\n            return \"Express Pack 9AM\";\n        case \"PurolatorExpressPackEvening\":\n            return \"Express Pack Evening\";\n        case \"PurolatorExpressPackInternational\":\n            return \"Express Pack International\";\n        case \"PurolatorExpressPackUS\":\n            return \"Express Pack US\";\n        case \"PurolatorExpressUS\":\n            return \"Express US\";\n        case \"PurolatorExpressUS1030AM\":\n            return \"Express US 10:30AM\";\n        case \"PurolatorExpressUS1200\":\n            return \"Express US 12:00\";\n        case \"PurolatorExpressUS9AM\":\n            return \"Express US 9AM\";\n        case \"PurolatorExpressBoxUS1030AM\":\n            return \"Express Box US 10:30AM\";\n        case \"PurolatorExpressBoxUS1200\":\n            return \"Express Box US 12:00\";\n        case \"PurolatorExpressBoxUS9AM\":\n            return \"Express Box US 9AM\";\n        case \"PurolatorExpressEnvelopeUS1030AM\":\n            return \"Express Envelope US 10:30AM\";\n        case \"PurolatorExpressEnvelopeUS1200\":\n            return \"Express Envelope US 12:00\";\n        case \"PurolatorExpressEnvelopeUS9AM\":\n            return \"Express Envelope US 9AM\";\n        case \"PurolatorExpressPackUS1030AM\":\n            return \"Express Pack US 10:30AM\";\n        case \"PurolatorExpressPackUS1200\":\n            return \"Express Pack US 12:00\";\n        case \"PurolatorExpressPackUS9AM\":\n            return \"Express Pack US 9AM\";\n        case \"PurolatorGround\":\n            return \"Ground\";\n        case \"PurolatorGround1030AM\":\n            return \"Ground 10:30AM\";\n        case \"PurolatorGround9AM\":\n            return \"Ground 9AM\";\n        case \"PurolatorGround12PM\":\n            return \"Ground 12PM\";\n        case \"PurolatorGroundDistribution\":\n            return \"Ground Distribution\";\n        case \"PurolatorGroundEvening\":\n            return \"Ground Evening\";\n        case \"PurolatorGroundUS\":\n            return \"Ground US\";\n        case \"PurolatorQuickShip\":\n            return \"QuickShip\";\n        case \"PurolatorQuickShipEnvelope\":\n            return \"QuickShip Envelope\";\n        case \"PurolatorQuickShipPack\":\n            return \"QuickShip Pack\";\n        case \"PurolatorQuickShipBox\":\n            return \"QuickShip Box\";\n\n\n            // Maersk Parcel\n        case \"Maersk-3-Day\":\n            return \"3 Day\";\n\n    }\n\n    console.warn(\"Unknown shipping service ID: \", serviceId, \"Carrier ID:\", carrierId);\n\n    // Snipped: code that attempts to format the service ID nicely based on common naming patterns found in the wild.\n\n    return serviceName;\n}\n
"},{"location":"Docs/Database/","title":"Database Drivers","text":"

global.apis.database.getConnection() returns one of these, depending on which database is in use.

"},{"location":"Docs/Database/#sqlite","title":"SQLite","text":"
export class SQLiteAdapter {\n    constructor(db) {\n        this.type = \"sqlite\";\n        this.db = db;\n    }\n\n    async query(query, replace) {\n        if (global.devMode) {\n            console.info(query, replace);\n        }\n        return await this.db.all(query, replace);\n    }\n\n    async run(statement, replace) {\n        if (global.devMode) {\n            console.info(statement, replace);\n        }\n        return await this.db.run(statement, replace);\n    }\n\n    async exec(statement) {\n        if (global.devMode) {\n            console.info(statement);\n        }\n        return await this.db.exec(statement);\n    }\n\n    async exists(table, where, replace) {\n        const q = await this.db.all(\"SELECT EXISTS(SELECT 1 FROM \" + table + \" WHERE \" + where + \") as n\", replace);\n        if (q[0].n > 0) {\n            return true;\n        }\n        return false;\n    }\n\n    async close() {\n\n    }\n\n    async tableExists(table) {\n        return (await this.db.get(`SELECT count(name) AS cnt FROM sqlite_master WHERE type='table' AND name=?`, table)).cnt > 0;\n    }\n\n    /**\n     * Get the version code set in the database by setSchemaVersion().\n     */\n    async getSchemaVersion() {\n        var res = await this.db.all(`PRAGMA user_version`);\n        return res[0].user_version;\n    }\n\n    /**\n     * Set the database version, using PRAGMA user_version.  Must be an integer.\n     */\n    async setSchemaVersion(version) {\n        await this.db.exec(`PRAGMA user_version = ${version}`);\n    }\n\n}\n
"},{"location":"Docs/Database/#remote-hostmaster","title":"Remote host/master","text":"
export class RemoteDatabaseAdapter {\n    constructor() {\n        this.type = \"remote\";\n    }\n\n    async apirequest(args) {\n        var resp = await sendToPostalPointHTTPServer(args, \"database\");\n        if (typeof resp.status == \"string\" && resp.status == \"OK\") {\n            return resp.result;\n        } else if (typeof resp.status == \"string\" && resp.status == \"ERR\") {\n            if (typeof resp.message == \"string\") {\n                throw new Error(resp.message);\n            } else {\n                throw new Error(resp);\n            }\n        } else {\n            throw new Error(resp);\n        }\n    }\n\n    async query(query, replace = []) {\n        return await this.apirequest({type: \"query\", query: query, replace: replace});\n    }\n\n    async run(statement, replace = []) {\n        return await this.apirequest({type: \"run\", query: statement, replace: replace});\n    }\n\n    async exec(statement) {\n        return await this.apirequest({type: \"exec\", query: statement});\n    }\n\n    async exists(table, where, replace = []) {\n        return await this.apirequest({type: \"exists\", table: table, where: where, replace: replace});\n    }\n\n    async close() {\n        // NOOP: We don't care about this\n    }\n\n    async tableExists(table) {\n        return await this.apirequest({type: \"tableExists\", table: table});\n    }\n\n    async getSchemaVersion() {\n        return await this.apirequest({type: \"getSchemaVersion\"});\n    }\n\n    async setSchemaVersion(version) {\n        // NOOP: Don't upgrade server's installation, it can do that itself\n    }\n}\n
"},{"location":"Docs/Database/#mariadbmysql","title":"MariaDB/MySQL","text":"
export class MariaDBAdapter {\n    constructor(connection) {\n        this.type = \"mariadb\";\n        this.conn = connection;\n    }\n\n    async query(query, replace) {\n        if (global.devMode) {\n            console.info(query, replace);\n        }\n        return await this.conn.query(query, replace);\n    }\n\n    async run(statement, replace) {\n        if (global.devMode) {\n            console.info(statement, replace);\n        }\n        return await this.query(statement, replace);\n    }\n\n    async exec(statement) {\n        if (global.devMode) {\n            console.info(statement);\n        }\n        return await this.run(statement);\n    }\n\n    async exists(table, where, replace) {\n        const q = await this.query(\"SELECT EXISTS(SELECT 1 FROM \" + table + \" WHERE \" + where + \") as n\", replace);\n        if (q[0].n > 0) {\n            return true;\n        }\n        return false;\n    }\n\n    async close() {\n        await this.conn.release();\n    }\n\n    async tableExists(table) {\n        return (await this.query(\"SHOW TABLES LIKE ?\", table)).length > 0;\n    }\n\n    /**\n     * Get the version code set in the database by setSchemaVersion().  Returns zero if not set.\n     */\n    async getSchemaVersion() {\n        if (await this.tableExists(\"database_metadata\")) {\n            var res = await this.query(\"SELECT `value` FROM database_metadata WHERE `key`='schema_version' LIMIT 1\");\n            console.log(res);\n            console.log(res[0].value);\n            if (res.length == 1) {\n                return res[0].value;\n            }\n        }\n        return 0;\n    }\n\n    /**\n     * Set a version number for the database schema.\n     * Must be an integer to maintain code compatibility with SQLite driver.\n     * Will create a \"database_metadata\" table if required to store the version number.\n     */\n    async setSchemaVersion(version) {\n        if (await this.tableExists(\"database_metadata\")) {\n            await this.query(\"REPLACE INTO `database_metadata` (`key`, `value`) VALUES (?, ?)\", [\"schema_version\", version]);\n        } else {\n            await this.exec(\"CREATE TABLE IF NOT EXISTS `database_metadata` ( `key` VARCHAR(50) NOT NULL, `value` VARCHAR(255) NOT NULL DEFAULT '', PRIMARY KEY (`key`))\");\n            await this.setSchemaVersion(version);\n        }\n    }\n}\n
"},{"location":"Docs/Events/","title":"Event Bus","text":"

Plugins can use global.apis.eventbus to receive events when certain actions happen in PostalPoint.

"},{"location":"Docs/Events/#event-list","title":"Event List","text":""},{"location":"Docs/Events/#example-code","title":"Example Code","text":"
// Handle a barcode scan.\n// Remember that PostalPoint will probably also be doing something in response to the barcode.\nglobal.apis.eventbus.on(\"barcodeScanned\", function (barcodedata) {\n    // do something with the barcode\n});\n\n// Close the embedded web browser, returning the user to whatever was onscreen before it opened\nglobal.apis.eventbus.emit(\"browserCloseRequest\");\n
"},{"location":"Docs/Events/#event-data-objects","title":"Event Data Objects","text":"

For events that return an object instead of a single value.

"},{"location":"Docs/Events/#transactionfinished","title":"transactionFinished","text":"

See Receipt

"},{"location":"Docs/Events/#customersignaturecollected","title":"customerSignatureCollected","text":"
{\n    \"svg\": \"data:image/svg+xml;base64,...\",\n    \"png\": \"data:image/png;base64,...\"\n}\n
"},{"location":"Docs/Events/#packagecheckin","title":"packageCheckIn","text":"
{\n    tag: \"abcxyz123456\", // Unique ID for the package, also found in the shelf label barcode.\n    tracking: \"94001...\", // Package tracking number. May be an empty string for items without tracking.\n    carrier: \"FedEx\", // Package carrier name, if detectable from the tracking number.  Otherwise an empty string.\n    mailbox: \"123\", // Mailbox number. Will be \"HAL\" for Hold At Location packages.\n    isHAL: false, // True if package is for Hold At Location.\n    recipient: \"\", // Hold At Location recipient name, or empty string if not HAL.\n    toLocker: \"5\", // Parcel locker number, or false if not going to a parcel locker.\n    shelfLabelPrinted: true // Indicates if a shelf label was printed for this package.  Will be false if going to a locker, if the user requested no label, or if the label failed to print.\n}\n
"},{"location":"Docs/FormPS1583/","title":"FormPS1583 object","text":"
export class FormPS1583 {\n    constructor() {\n        this.formRevision = LATEST_FORM_REVISION; // Currently \"June2024\"\n        this.pmbOpenedDate = new Date();\n        this.pmbClosedDate = null;\n        this.cmraStreetAddress = getSetting(\"origin_street1\");\n        this.pmbNumber = \"\";\n        this.cmraZIP = getSetting(\"origin_zip\");\n        var cmraZIPData = getZIP(this.cmraZIP);\n        if (cmraZIPData) {\n            this.cmraCity = cmraZIPData.city;\n            this.cmraState = cmraZIPData.state;\n        } else {\n            this.cmraCity = getSetting(\"origin_city\", \"\");\n            this.cmraState = getSetting(\"origin_state\", \"\");\n        }\n        this.serviceTypeBusiness = false; // true for business PMB, false for residential\n        this.applicant = {\n            firstName: \"\",\n            lastName: \"\",\n            middleName: \"\",\n            phone: \"\",\n            email: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            courtProtected: false,\n            photoID: {\n                name: \"\",\n                number: \"\",\n                issuer: \"\",\n                expirationDate: null,\n                type: null  // \"DL/ID\", \"UniformedService\", \"USAccess\", \"USUni\",\n                        // \"Passport\", \"Matricula\", \"NEXUS\",\n                        // \"CertOfNaturalization\", \"USPermResident\"\n            },\n            addressID: {\n                name: \"\",\n                streetAddress: \"\",\n                city: \"\",\n                state: \"\",\n                zip: \"\",\n                country: \"\",\n                type: null, // \"DL/ID\", \"Lease\", \"Mortgage\", \"Insurance\", \"VehicleReg\", \"Voter\"\n                expirationDate: null // Optional currently but must be kept current - Oct 2025\n            }\n        };\n        this.authorizedIndividual = {\n            firstName: \"\",\n            lastName: \"\",\n            middleName: \"\",\n            phone: \"\",\n            email: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            photoID: {\n                name: \"\",\n                number: \"\",\n                issuer: \"\",\n                expirationDate: null,\n                type: null  // \"DL/ID\", \"UniformedService\", \"USAccess\", \"USUni\",\n                        // \"Passport\", \"Matricula\", \"NEXUS\",\n                        // \"CertOfNaturalization\", \"USPermResident\"\n            },\n            addressID: {\n                name: \"\",\n                streetAddress: \"\",\n                city: \"\",\n                state: \"\",\n                zip: \"\",\n                country: \"\",\n                type: null, // \"DL/ID\", \"Lease\", \"Mortgage\", \"Insurance\", \"VehicleReg\", \"Voter\"\n                expirationDate: null // Optional currently but must be kept current - Oct 2025\n            }\n        };\n        this.mailTransferredTo = {\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            phone: \"\",\n            email: \"\"\n        };\n        this.business = {\n            name: \"\",\n            type: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            phone: \"\",\n            placeOfRegistration: \"\"\n        };\n        this.additionalRecipients = []; // Array of strings containing names\n        this.applicantSignature = \"\"; // PNG image data URI\n        this.applicantSignatureDate = null;\n        this.cmraSignature = \"\"; // PNG image data URI\n        this.cmraSignatureDate = null;\n        this.hasForwardingAddress = false;\n    }\n\n    getTermsAndConditions() {\n        return DEFAULT_TERMS_CONDITIONS[this.formRevision];\n    }\n\n    getApplicantForwardingAddress() {\n        if (this.mailTransferredTo.streetAddress != \"\") {\n            return new Address(null,\n                    [this.applicant.firstName, this.applicant.lastName].filter(Boolean).join(\" \"),\n                    this.business.name ?? \"\",\n                    this.mailTransferredTo.streetAddress,\n                    \"\",\n                    this.mailTransferredTo.zip,\n                    this.mailTransferredTo.city,\n                    this.mailTransferredTo.state,\n                    this.mailTransferredTo.country ?? \"US\",\n                    this.mailTransferredTo.phone ?? \"\",\n                    this.mailTransferredTo.email ?? \"\"\n                    );\n        }\n        return new Address(null,\n                [this.applicant.firstName, this.applicant.lastName].filter(Boolean).join(\" \"),\n                this.business.name ?? \"\",\n                this.applicant.streetAddress,\n                \"\",\n                this.applicant.zip,\n                this.applicant.city,\n                this.applicant.state,\n                this.applicant.country ?? \"US\",\n                this.applicant.phone ?? \"\",\n                this.applicant.email ?? \"\"\n                );\n    }\n\n    getFormFields() {\n        var fields = FORM_FIELDS[this.formRevision];\n        function getNestedValue(obj, path) {\n            return path.split('.').reduce((o, key) => (o ? o[key] : \"\"), obj);\n        }\n        var outfields = [];\n        var groupheading = {};\n        var groupfields = [];\n        for (var prop in fields) {\n            if (fields[prop].t == \"heading\") {\n                if (groupfields.length > 0) {\n                    groupheading.fields = groupfields;\n                    outfields.push(groupheading);\n                    groupfields = [];\n                }\n                groupheading = {\n                    heading: fields[prop].l,\n                    groupid: fields[prop].group ?? null,\n                    fields: []\n                };\n            }\n            fields[prop].n = prop;\n            fields[prop].v = getNestedValue(this, prop);\n            if (typeof fields[prop].v == \"undefined\" || fields[prop].v == null) {\n                fields[prop].v = \"\";\n            }\n            if (fields[prop].t == \"date\") {\n                if (fields[prop].v instanceof Date) {\n                    // Cancel out the timezone in the date object\n                    // If we don't do this, the dates will be subtracted by one day each time we load\n                    // https://stackoverflow.com/a/17329571\n                    fields[prop].v.setTime(fields[prop].v.getTime() + fields[prop].v.getTimezoneOffset() * 60 * 1000);\n                }\n                fields[prop].v = formatTimestamp(\"Y-m-d\", fields[prop].v);\n                if (fields[prop].v == \"1969-12-31\" || fields[prop].v == \"1970-01-01\") {\n                    fields[prop].v = \"\";\n                }\n            }\n            if (fields[prop].t == \"select\" && typeof fields[prop].b == \"boolean\") {\n                fields[prop].v = fields[prop].v ? \"true\" : \"\";\n            }\n            if (fields[prop].t != \"heading\") {\n                groupfields.push(fields[prop]);\n            }\n        }\n        if (groupfields != []) {\n            groupheading.fields = groupfields;\n            outfields.push(groupheading);\n        }\n        return outfields;\n    }\n\n    static fromHTMLFormData(formdata, revision = LATEST_FORM_REVISION) {\n        var f = new FormPS1583();\n\n        function setNestedValue(obj, path, value) {\n            const keys = path.split('.');\n            const lastKey = keys.pop();\n            const target = keys.reduce((o, key) => {\n                if (o[key] === undefined)\n                    o[key] = {};\n                return o[key];\n            }, obj);\n            if (typeof FORM_FIELDS[revision][path].b == \"boolean\") {\n                target[lastKey] = (value == \"true\" || value == true);\n            } else {\n                target[lastKey] = value;\n            }\n        }\n\n        for (var prop in formdata) {\n            setNestedValue(f, prop, formdata[prop]);\n        }\n\n        return f;\n    }\n\n    static fromJSON(o) {\n        var f = new FormPS1583();\n        f.formRevision = o.formRevision ?? LATEST_FORM_REVISION;\n        f.pmbOpenedDate = new Date(o.pmbOpenedDate);\n        f.pmbClosedDate = o.pmbClosedDate ? new Date(o.pmbClosedDate) : null;\n        f.cmraStreetAddress = o.cmraStreetAddress;\n        f.pmbNumber = o.pmbNumber;\n        f.cmraCity = o.cmraCity;\n        // snip, see constructor for full data structure\n        return f;\n    }\n\n    toJSON() {\n        return {\n            formRevision: this.formRevision,\n            pmbOpenedDate: this.pmbOpenedDate,\n            pmbClosedDate: this.pmbClosedDate,\n            cmraStreetAddress: this.cmraStreetAddress,\n            pmbNumber: this.pmbNumber,\n            cmraCity: this.cmraCity,\n            // snip, see constructor for full data structure\n        };\n    }\n\n    /**\n     * Render this form to PDF\n     * @returns PDF bytes\n     */\n    async getPDF() {\n        // snip, it draws the form contents onto a PDF using the pdf-lib library\n        // If you really want to see how, email us for the code\n        return await document.save();\n    }\n}\n
"},{"location":"Docs/HTTP_API_Server/","title":"HTTP API Server","text":"

PostalPoint runs a local HTTP server to allow communication with other devices on the LAN.

With the httpserver plugin API, plugins can add their own local API endpoints.

Valid HTTP API requests are POST requests with either a JSON request body, or an entirely empty body.

The default HTTP server settings are to bind to all addresses on port 7678. There is a basic API web client accessible on / for manual API access, for example, at http://localhost:7678/.

"},{"location":"Docs/HTTP_API_Server/#security","title":"Security","text":"

API requests are authenticated with a \"Network Connection Key\". Each installation of PostalPoint maintains a user-configurable list of keys which are allowed to connect to that installation's API server.

This means that, in order for a remote client to connect to PostalPoint, it must generate a random alphanumeric ID, which must be saved in the PostalPoint settings.

"},{"location":"Docs/HTTP_API_Server/#adding-an-api-endpoint","title":"Adding an API endpoint","text":"

This code adds an endpoint reachable by POST to http://[local hostname]:[port]/testendpointname.

global.apis.httpserver.addEndpoint(\"testendpointname\", async function (request) {\n    // `request` is an object parsed from the request body.\n    if (request.abc == \"123\") {\n        // A non-string `body` is converted to JSON before the HTTP reply is sent.\n        return {body: {json: true, abc: 123}, httpcode: 200, contentType: \"application/json\"};\n    }\n    // A string `body` is sent to the client as-is using whatever contentType you specify.\n    return {body: \"abc\", httpcode: 200, contentType: \"text/plain\"};\n});\n
"},{"location":"Docs/HTTP_API_Server/#connecting-to-a-remote-endpoint","title":"Connecting to a remote endpoint","text":"

By default, sendRequestToRemote uses the configured \"Host PC\", but a different hostname/IP may be specified.

This code hits the endpoint added above.

try {\n    const responseObject = await global.apis.httpserver.sendRequestToRemote({abc: \"123\"}, \"testendpointname\");\n    // responseObject will be {json: true, abc: 123}\n    console.log(responseObject);\n} catch (ex) {\n    global.apis.alert(ex.message, \"Request Error\");\n}\n
"},{"location":"Docs/HiddenConfigs/","title":"Configuration Flags","text":"

In the Advanced settings of PostalPoint, there is a tool to manually get and set settings by ID.

Most PostalPoint settings are available in the Settings screen, and their IDs can be found in the JSON file created by running a database backup.

However, there are some special \"hidden\" setting options that can override default behavior.

Warning: PostalPoint is tested with these settings at their default values (usually, unset). There is a small chance of bugs being created by changing a value.

"},{"location":"Docs/Parcel/","title":"Parcel/Package Object","text":"

This object is supplied a plugin registered with registerRateEndpoint when PostalPoint requests shipping rates from the plugin.

export class Package {\n    constructor(isPrepaid = false) {\n        this.prepaid = isPrepaid;\n        this.packaging = {\n            type: \"Parcel\",\n            service: \"\",\n            carrier: \"\",\n            length: 999999,\n            width: 999999,\n            height: 999999,\n            weightOz: 999999,\n            nonmachinable: false,\n            additionalHandling: false,\n            internalid: 100,\n            oversizeFlag: false\n        };\n        this.extraServices = {\n            certifiedMail: false,\n            barcode3800: \"\",\n            registeredMail: false,\n            registeredMailAmount: false, // can be a number in USD\n            returnReceipt: false,\n            returnReceiptElectronic: false,\n            insurance: false, // can be a number in USD\n            signature: false, // can be false, \"SIGNATURE\", or \"SIGNATURE_RESTRICTED\"\n            hazmat: false,\n            perishable: false,\n            crematedRemains: false,\n            liveAnimal: false, // BEES, DAY_OLD_POULTRY, ADULT_BIRDS, OTHER_LIVES\n            cod: false, // Collect on Delivery\n            codAmount: false,\n            endorsement: \"\", // ADDRESS_SERVICE_REQUESTED, CHANGE_SERVICE_REQUESTED, FORWARDING_SERVICE_REQUESTED, LEAVE_IF_NO_RESPONSE, RETURN_SERVICE_REQUESTED\n            carrier_billing_account: {// Bill a third party's account number for the label\n                type: \"\", // \"\" (ignores this entire option), \"SENDER\" (EasyPost default), \"THIRD_PARTY\", \"RECEIVER\", \"COLLECT\"\n                carrier: \"\", // Carrier ID (should be used to filter rates)\n                account_number: \"\", // Carrier account number to bill\n                country: \"\", // Country account is based in\n                postal_code: \"\" // Postal code of account\n            },\n            dryIce: false,\n            dryIceWeight: 0,\n            dryIceMedical: false\n        };\n        this.description = \"\"; // Fillable on customs form, or generated before rating call using customs items\n        this.specialRateEligibility = false;\n        this.customs = {\n            contents: \"\",\n            contentsExplanation: \"\", // needed if contents is \"other\", will be copied from this.description if blank for maximum carrier compatibility\n            signature: \"\",\n            restriction: \"\",\n            restrictionComments: \"\", // needed if restriction is \"other\"\n            nonDelivery: \"return\", // \"return\" or \"abandon\",\n            eel_pfc: \"\",\n            items: [] // {index: 0, description: \"\", qty: \"\", lbs: \"\", oz: \"\", value: \"\", hscode: \"\", origin: US\"}\n        };\n        this.toAddress = new Address();\n        this.returnAddress = new Address();\n        this.originAddress = new Address();\n        this.trackingNumber = \"\";\n    }\n\n    /**\n     * Format as EasyPost shipment object\n     * @returns {Package.toEasyPostShipment.shipment}\n     */\n    async toEasyPostShipment() {\n        // Not relevant to plugins\n    }\n\n    /**\n     * Format as Endicia shipment object\n     * @returns {Package.toSERAShipment.shipment}\n     */\n    async toSERAShipment() {\n        // Not relevant to plugins\n    }\n\n    /**\n     * Get a human-readable summary of size and options.\n     * Does not include address data.\n     * @returns {String}\n     */\n    async toString() {\n        let summary = [];\n        let packaging = await getPackagingByID(this.packaging.internalid);\n        let weight = ozToLbsOz(this.packaging.weightOz);\n        let weightStr = this.packaging.weightOz >= 16 ? `${weight[0]} lbs ${weight[1]} oz` : `${weight[1]} oz`;\n        if (packaging != false) {\n            if (packaging.irregular) {\n                if (packaging.weight === false) {\n                    summary.push(\"Parcel\");\n                } else {\n                    summary.push(`${weightStr} Parcel`);\n                }\n                summary.push(\"Additional Handling\");\n            } else {\n                if (packaging.weight === false) {\n                    summary.push(packaging.name);\n                } else {\n                    summary.push(`${weightStr} ${packaging.name}`);\n                }\n            }\n        } else {\n            summary.push(weightStr);\n        }\n        if (this.extraServices.hazmat) {\n            summary.push(\"HAZMAT\");\n        }\n        if (this.extraServices.liveAnimal === true) {\n            summary.push(\"Live Animals\");\n        } else if (typeof this.extraServices.liveAnimal == \"string\") {\n            switch (this.extraServices.liveAnimal) {\n                case \"BEES\":\n                    summary.push(\"Live Bees\");\n                    break;\n                case \"DAY_OLD_POULTRY\":\n                    summary.push(\"Day-old Poultry\");\n                    break;\n                case \"ADULT_BIRDS\":\n                    summary.push(\"Live Adult Birds\");\n                    break;\n                case \"OTHER_LIVES\":\n                default:\n                    summary.push(\"Live Animals\");\n                    break;\n            }\n        }\n        if (this.extraServices.perishable) {\n            summary.push(\"Perishable\");\n        }\n        if (this.extraServices.crematedRemains) {\n            summary.push(\"Cremated Remains\");\n        }\n        if (this.extraServices.certifiedMail) {\n            summary.push(\"Certified Mail\");\n        } else if (this.extraServices.registeredMail) {\n            summary.push(\"Registered Mail\");\n            summary.push(\"Registered for $\" + (this.extraServices.registeredMailAmount * 1.0).toFixed(2));\n        } else if (this.extraServices.signature == \"SIGNATURE\") {\n            summary.push(\"Signature Required\");\n        }\n        if (this.extraServices.signature == \"ADULT_SIGNATURE\") {\n            summary.push(\"Adult Signature Required\");\n        }\n        if (this.extraServices.signature == \"SIGNATURE_RESTRICTED\") {\n            summary.push(\"Restricted Delivery\");\n        }\n        if (this.extraServices.returnReceiptElectronic) {\n            summary.push(\"Return Receipt Electronic\");\n        }\n        if (this.extraServices.returnReceipt) {\n            summary.push(\"Return Receipt\");\n        }\n        if (this.extraServices.insurance) {\n            summary.push(\"Insured for $\" + (this.extraServices.insurance * 1.0).toFixed(2));\n        }\n        if (this.extraServices.cod) {\n            summary.push(\"Collect on Delivery: $\" + (this.extraServices.codAmount * 1.0).toFixed(2));\n        }\n        if (this.extraServices.dryIce && this.extraServices.dryIceWeight > 0) {\n            summary.push(\"Dry Ice: \" + (this.extraServices.dryIceWeight * 1).toFixed(0) + \" oz\");\n        }\n        if (this.extraServices.carrier_billing_account?.type) {\n            if (this.extraServices.carrier_billing_account.type != \"\") {\n                var accountNumber = this.extraServices.carrier_billing_account.account_number;\n                var accountNumberCensored = accountNumber.substring(accountNumber.length - 4).padStart(accountNumber.length, \"X\");\n                var carrierName = this.extraServices.carrier_billing_account.carrier;\n                switch (this.extraServices.carrier_billing_account.type) {\n                    case \"SENDER\":\n                        summary.push(`Bill to sender ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"THIRD_PARTY\":\n                        summary.push(`Bill to third party ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"RECEIVER\":\n                        summary.push(`Bill to receiver ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"COLLECT\":\n                        if (accountNumber.length > 0) {\n                            summary.push(`Bill collect ${carrierName} account #${accountNumberCensored}`);\n                        } else {\n                            summary.push(`Bill collect`);\n                        }\n                        break;\n                }\n            }\n        }\n        return summary.join(\"\\n\");\n    }\n\n    async needsHAZMATPrompt() {\n        try {\n            let packagingInfo = await getPackagingByID(this.packaging.internalid);\n            if (packagingInfo.hazmat) {\n                return true;\n            }\n            if (this.packaging.weight > 10) {\n                return true;\n            }\n            if (packagingInfo.l >= -1 && Math.max(this.packaging.length, this.packaging.width, this.packaging.height) > 0.5) {\n                return true;\n            }\n            switch (packagingInfo.type) {\n                case \"Letter\":\n                case \"Card\":\n                    return false;\n            }\n            return true;\n        } catch (ex) {\n            return true;\n        }\n    }\n\n    get isPrepaid() {\n        return this.prepaid == true;\n    }\n\n    setCustomsInfo(contents, contentsExplanation, signature, restriction, restrictionComments, nonDelivery) {\n        let items = this.customs.items; // Save this and copy it back in so we don't overwrite it\n        this.customs = {\n            contents: contents,\n            contentsExplanation: contentsExplanation, // needed if contents is \"other\"\n            signature: signature,\n            restriction: restriction,\n            restrictionComments: restrictionComments, // needed if restriction is \"other\"\n            nonDelivery: nonDelivery, // \"return\" or \"abandon\",\n            items: items\n        };\n    }\n\n    /**\n     * Get the customs items, ignoring any that are blank.\n     * @returns {Array}\n     */\n    getCustomsItems() {\n        let items = [];\n        for (let i = 0; i < this.customs.items.length; i++) {\n            let item = this.customs.items[i];\n            if (item.description == \"\" && (item.qty == \"\" || item.qty == 0) && (item.weight == \"\" || item.weight == 0) && (item.value == \"\" || item.value == 0)) {\n                continue;\n            }\n            items.push(item);\n        }\n        return items;\n    }\n\n    setCustomsItems(items) {\n        this.customs.items = items;\n    }\n\n    getCustoms() {\n        this.customs.items = this.getCustomsItems();\n        return this.customs;\n    }\n\n    /**\n     * Attempt to automatically fix simple issues like overweight letters.\n     * @returns {undefined}\n     */\n    async fixIssues() {\n        if (this.packaging.type == \"Letter\" && this.packaging.weightOz > 3.5) {\n            if (this.packaging.nonmachinable) {\n                return; // Has to be a parcel, can't fix without dimensions\n            }\n            this.packaging.type = \"Flat\";\n            this.packaging.internalid = 104;\n        }\n    }\n\n    /**\n     * Do some basic checks to see if this package is even remotely shippable\n     * @param {boolean} kioskMode If true, returned strings are suitable for display in kiosk mode.\n     * @returns {boolean|string} true if okay, human-readable error message and instructions if not okay\n     */\n    async isValid(kioskMode = false) {\n        // Removed from docs for brevity. Just a bunch of if statements to catch problems.\n    }\n\n    /**\n     * Set package characteristics\n     * @param {string} type \"Parcel\", \"Letter\", \"Flat\", \"Card\"\n     * @param {type} service\n     * @param {type} carrier\n     * @param {type} length\n     * @param {type} width\n     * @param {type} height\n     * @param {type} weightOz\n     * @returns {undefined}\n     */\n    setPackaging(type, service, carrier, length, width, height, weightOz, nonmachinable) {\n        if (typeof nonmachinable == \"undefined\") {\n            nonmachinable = false;\n        }\n        if (type == \"Card\") {\n            // Postcards\n            weightOz = 1;\n            this.packaging.internalid = 105;\n        } else if (type == \"Flat\") {\n            this.packaging.internalid = 104;\n        } else if (type == \"Letter\") {\n            this.packaging.internalid = 102;\n            if (nonmachinable) {\n                this.packaging.internalid = 103;\n            }\n        }\n        this.packaging.type = type;\n        this.packaging.service = service;\n        this.packaging.carrier = carrier;\n        this.packaging.weightOz = weightOz;\n        this.packaging.nonmachinable = nonmachinable;\n\n        // Enforce Length > Width > Height\n        let size = [length, width, height];\n        size.sort(function (a, b) {\n            return b - a;\n        });\n        this.packaging.length = size[0];\n        this.packaging.width = size[1];\n        this.packaging.height = size[2];\n    }\n\n    /**\n     * Set an extra service\n     * @param {string} id Service ID\n     * @param {boolean} enabled Turn it on or off\n     * @param {string} value Service value, if needed (some are not just a boolean)\n     * @returns {undefined}\n     */\n    setExtraService(id, enabled, value) {\n        if (typeof value != \"undefined\" && enabled) {\n            this.extraServices[id] = value;\n        } else {\n            this.extraServices[id] = enabled == true;\n        }\n    }\n\n    getExtraServices() {\n        return this.extraServices;\n    }\n\n    /**\n     * Set to \"MEDIA_MAIL\", \"LIBRARY_MAIL\", or false\n     * @param {type} rate\n     * @returns {undefined}\n     */\n    set specialRate(rate) {\n        if (rate == \"MEDIA\") {\n            rate = \"MEDIA_MAIL\";\n        } else if (rate == \"LIBRARY\") {\n            rate = \"LIBRARY_MAIL\";\n        }\n        if (rate != \"MEDIA_MAIL\" && rate != \"LIBRARY_MAIL\") {\n            rate = false;\n        }\n        this.specialRateEligibility = rate;\n    }\n\n    get specialRate() {\n        return this.specialRateEligibility;\n    }\n\n    /**\n     * Save an address to this package.\n     * @param {string} type \"to\", \"return\", or \"origin\"\n     * @param {string} name\n     * @param {string} company\n     * @param {string} street1\n     * @param {string} street2\n     * @param {string} city\n     * @param {string} state\n     * @param {string} zip\n     * @param {string} country ISO 2-char country code\n     * @param {string} phone\n     * @param {string} email\n     * @returns {undefined}\n     */\n    setAddress(type, name, company, street1, street2, city, state, zip, country, phone, email) {\n        let address = Address.fromObject({\n            name: name,\n            company: company,\n            street1: street1,\n            street2: street2,\n            city: city,\n            state: state,\n            zip: zip,\n            country: country,\n            phone: phone,\n            email: email\n        });\n        switch (type) {\n            case \"to\":\n                this.toAddress = address;\n                break;\n            case \"return\":\n                this.returnAddress = address;\n                break;\n            case \"origin\":\n                this.originAddress = address;\n                break;\n        }\n    }\n\n    /**\n     * Set an address using an object that matches the internal form (see setAddress())\n     * @param {string} type\n     * @param {object} data\n     * @returns {undefined}\n     */\n    setAddressWhole(type, address) {\n        switch (type) {\n            case \"to\":\n                this.toAddress = Address.fromObject(address);\n                break;\n            case \"return\":\n                this.returnAddress = Address.fromObject(address);\n                break;\n            case \"origin\":\n                this.originAddress = Address.fromObject(address);\n                break;\n        }\n    }\n\n    get tracking() {\n        return this.trackingNumber;\n    }\n\n    set tracking(n) {\n        this.trackingNumber = n;\n    }\n\n    /**\n     * Get the \"from\" address that will be shown,\n     * using the return address or origin address as needed\n     * @returns {address}\n     */\n    getReturnAddress() {\n        var a = null;\n        if (typeof this.returnAddress == \"object\") {\n            a = Address.fromObject(this.returnAddress);\n        } else {\n            a = Address.fromObject(this.originAddress);\n        }\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n\n    getToAddress() {\n        var a = Address.fromObject(this.toAddress);\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n\n    getFromAddress() {\n        var a = null;\n        if (typeof this.originAddress == \"object\") {\n            a = Address.fromObject(this.originAddress);\n        } else {\n            a = Address.fromObject(this.returnAddress);\n        }\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n}\n
"},{"location":"Docs/Receipt/","title":"Receipt Objects","text":""},{"location":"Docs/Receipt/#sample-receipt-object","title":"Sample receipt object","text":"

This is an example of the data global.apis.pos.onReceiptChange and global.apis.pos.onTransactionFinished send to plugins. All data for a PostalPoint transaction is available in its receipt object.

The money-related properties near the bottom of this sample are generated by PostalPoint from the items and payments.

"},{"location":"Docs/Receipt/#notes","title":"Notes:","text":"
{\n    \"items\": [\n        ReceiptItem.fromJSON({\n            \"id\": \"9100123456789012345678\",\n            \"label\": \"Test Package\",\n            \"text\": \"Package Details\\nTracking # 9100 1234 5678 9012 3456 78\\nTo:\\nTEST PERSON\\nORGANIZATION INC\\n123 TEST ROAD\\nTESTTOWN TE 99999-0001\",\n            \"priceEach\": 8,\n            \"qty\": 1,\n            \"cost\": 0,\n            \"retail\": 8,\n            \"taxRate\": 0,\n            \"free\": false,\n            \"barcode\": \"9100123456789012345678\",\n            \"certifiedInfo\": false,\n            \"isMerch\": false,\n            \"surcharge\": false,\n            \"toAddress\": {\n                \"name\": \"TEST PERSON\",\n                \"company\": \"ORGANIZATION INC\",\n                \"street1\": \"123 TEST ROAD\",\n                \"street2\": null,\n                \"city\": \"TESTTOWN\",\n                \"state\": \"TE\",\n                \"zip\": \"99999-0001\",\n                \"email\": null,\n                \"phone\": null,\n                \"country\": \"US\"\n            },\n            \"fromAddress\": {\n                \"name\": \"TEST PERSON\",\n                \"company\": \"ORGANIZATION INC\",\n                \"street1\": \"123 TEST ROAD\",\n                \"street2\": null,\n                \"city\": \"TESTTOWN\",\n                \"state\": \"TE\",\n                \"zip\": \"99999-0001\",\n                \"email\": null,\n                \"phone\": null,\n                \"country\": \"US\"\n            }\n        }),\n        ReceiptItem.fromJSON({\n            \"id\": \"testitem\",\n            \"label\": \"Test Item\",\n            \"text\": \"\",\n            \"priceEach\": 2,\n            \"qty\": 1,\n            \"cost\": 0,\n            \"retail\": 2,\n            \"taxRate\": 0.1,\n            \"free\": false,\n            \"barcode\": \"\",\n            \"certifiedInfo\": false,\n            \"isMerch\": true,\n            \"surcharge\": false\n        })\n    ],\n    \"payments\": [\n        ReceiptPayment.fromJSON({\n            \"amount\": 10,\n            \"type\": \"cash\",\n            \"text\": \"\",\n            \"id\": \"testcash\"\n        })\n    ],\n    \"subtotal\": 10,\n    \"subtotalFormatted\": \"$10.00\",\n    \"tax\": 0.2,\n    \"taxFormatted\": \"$0.20\",\n    \"grandTotal\": 10.2,\n    \"grandTotalFormatted\": \"$10.20\",\n    \"paid\": 10.2,\n    \"paidFormatted\": \"$10.20\",\n    \"due\": 0,\n    \"dueFormatted\": \"$0.00\",\n    \"emailTo\": null,\n    \"pendingEmailTo\": null,\n    \"uuid\": \"1234567890abcdef\",\n    \"customerAccountId\": null,\n    \"topTextHTML\": \"\",\n    \"bottomTextHTML\": \"\"\n}\n
"},{"location":"Docs/Receipt/#globalapisposreceiptitem","title":"global.apis.pos.ReceiptItem","text":"
export class ReceiptItem {\n    /**\n     *\n     * @param {string|number} id Unique ID number for this item (UPC code, inventory number, etc). Used to deduplicate line items. Unique items (like shipping labels) should be random or empty.\n     * @param {string} label One-line item information.\n     * @param {string} text Extra item information.\n     * @param {number} priceEach Price per unit\n     * @param {number} quantity Number of units\n     * @param {number} cost Cost per unit. Used for automatic expense tracking.\n     * @param {number} taxrate Examples: 0 (for 0%), 0.05 (for 5%), etc\n     * @param {string} taxableAmount The part of the sale price that's taxable. \"\" for default (all), \"markup\" for only taxing profit.\n     * @returns {ReceiptItem}\n     */\n    constructor(id, label, text, priceEach, quantity, cost, taxrate = 0.0, taxableAmount = \"\") {\n        this.id = id;\n        this.label = label;\n        if (text == null) {\n            this.txt == \"\";\n        } else {\n            this.txt = text;\n        }\n        this.priceEach = num(priceEach);\n        this.qty = num(quantity);\n        this.cost = num(cost);\n        if (isNaN(taxrate)) {\n            this.taxRate = 0;\n        } else {\n            this.taxRate = num(taxrate);\n        }\n        this.taxableAmount = taxableAmount;\n        this.merch = false;\n        this.merchid = null;\n        this.surcharge = false;\n        this.retail = 0; // For ensuring PostalPoint fee collection on office mode shipments\n        this.mailboxNumber = null;\n        this.mailboxDays = 0;\n        this.mailboxMonths = 0;\n        this.category = \"\"; // merch category\n        this.electronicReturnReceipt = false;\n        this.isStamp = false;\n        this.extraData = {};\n    }\n\n    static fromJSON(obj) {\n        var item = new ReceiptItem(obj.id, obj.label, obj.text, obj.priceEach, obj.qty, obj.cost, obj.taxRate, obj.taxableAmount ?? \"\");\n        item.free = obj.free;\n        item.barcode = obj.barcode;\n        item.certifiedInfo = obj.certifiedInfo;\n        item.toAddress = obj.toAddress;\n        item.fromAddress = obj.fromAddress;\n        item.merch = obj.isMerch == true || (typeof obj.merchid == \"string\" && obj.merchid.length > 0);\n        item.merchid = obj.merchid ?? null;\n        item.mailboxNumber = obj.mailboxNumber ?? null;\n        item.mailboxDays = obj.mailboxDays ?? 0;\n        item.mailboxMonths = obj.mailboxMonths ?? 0;\n        item.surcharge = obj.surcharge;\n        item.retailPrice = obj.retail;\n        item.carrier = obj.carrier ?? null;\n        item.service = obj.service ?? null;\n        item.category = obj.category ?? \"\";\n        item.electronicReturnReceipt = obj.electronicReturnReceipt ?? false;\n        item.isStamp = obj.isStamp ?? false;\n        item.extraData = obj.extraData ?? {};\n        return item;\n    }\n\n    toJSON() {\n        return {\n            id: this.id,\n            label: this.label,\n            text: this.text,\n            priceEach: num(this.priceEach),\n            qty: num(this.qty),\n            cost: num(this.cost),\n            retail: num(this.retail),\n            taxRate: num(this.taxRate),\n            taxableAmount: this.taxableAmount,\n            taxTotal: this.taxAmount,\n            free: this.free,\n            barcode: this.barcode,\n            certifiedInfo: this.certifiedInfo,\n            isMerch: this.merch,\n            merchid: this.merchid,\n            surcharge: this.surcharge,\n            toAddress: this.toAddress,\n            fromAddress: this.fromAddress,\n            mailboxNumber: this.mailboxNumber,\n            mailboxDays: this.mailboxDays,\n            mailboxMonths: this.mailboxMonths,\n            carrier: this.carrier,\n            service: this.service,\n            category: this.category,\n            electronicReturnReceipt: this.electronicReturnReceipt,\n            isStamp: this.isStamp,\n            extraData: this.extraData\n        };\n    }\n\n    setExtra(key, val) {\n        this.extraData[key] = val;\n    }\n\n    getExtra(key) {\n        if (typeof this.extraData[key] != \"undefined\") {\n            return this.extraData[key];\n        }\n        return null;\n    }\n\n    get text() {\n        if (typeof this.txt == \"string\") {\n            return this.txt;\n        }\n        return \"\";\n    }\n\n    set text(t) {\n        if (typeof t == \"string\") {\n            this.txt = t;\n        } else {\n            this.txt = \"\";\n        }\n    }\n\n    get certifiedInfo() {\n        if (typeof this.certified == \"undefined\") {\n            return false;\n        }\n        return this.certified;\n    }\n\n    set certifiedInfo(info) {\n        this.certified = info;\n    }\n\n    setCertifiedInfo(tracking, certfee, extrafees, postage, date, location, toaddress) {\n        this.certified = {\n            tracking: tracking,\n            certifiedFee: num(certfee),\n            extraFees: extrafees,\n            postage: num(postage),\n            date: date,\n            location: location,\n            to: toaddress\n        };\n    }\n\n    setQuantity(q) {\n        this.qty = num(q);\n    }\n\n    get free() {\n        return this.isFree == true;\n    }\n\n    set free(free) {\n        this.isFree = free == true;\n    }\n\n    get barcode() {\n        if (typeof this.barcodeData != \"string\") {\n            return \"\";\n        }\n        return this.barcodeData;\n    }\n\n    set barcode(data) {\n        this.barcodeData = data;\n    }\n\n    get linePrice() {\n        return round(m(this.priceEach, this.qty), 2);\n    }\n\n    get priceEachFormatted() {\n        return getCurrencySymbol() + round(num(this.priceEach), 2).toFixed(2);\n    }\n\n    get linePriceFormatted() {\n        return getCurrencySymbol() + round(num(this.linePrice), 2).toFixed(2);\n    }\n\n    get texthtml() {\n        if (typeof this.text != \"string\") {\n            return \"\";\n        }\n        var lines = this.text.split(\"\\n\");\n        for (var i = 0; i < lines.length; i++) {\n            if (lines[i].startsWith(\"Tracking # \")) {\n                // Allow copying tracking number\n                lines[i] = \"Tracking # <span class=\\\"usall\\\">\" + lines[i].replace(\"Tracking # \", \"\") + \"</span>\";\n            }\n        }\n        return lines.join(\"<br />\");\n    }\n\n    get taxAmount() {\n        if (this.taxableAmount == \"markup\") {\n            var lineCost = m(this.cost, this.qty);\n            var margin = s(this.linePrice, lineCost);\n            if (margin <= 0) {\n                return 0;\n            }\n            return round(m(margin, this.taxRate), 2);\n        } else {\n            return round(m(this.linePrice, this.taxRate), 2);\n        }\n    }\n\n    get retailPrice() {\n        if (typeof this.retail == \"number\") {\n            return this.retail;\n        }\n        return this.priceEach * this.qty;\n    }\n\n    set retailPrice(price) {\n        this.retail = num(price);\n    }\n\n}\n
"},{"location":"Docs/Receipt/#globalapisposreceiptpayment","title":"global.apis.pos.ReceiptPayment","text":"
export class ReceiptPayment {\n\n    /**\n     *\n     * @param {number} amount amount paid\n     * @param {string} type payment type\n     * @param {string} text extra data (credit card info, etc)\n     * @returns {ReceiptPayment}\n     */\n    constructor(amount, type, text) {\n        this.id = (Math.random() * 100000000) + \"_\" + type + \"_\" + amount;\n        this.text = (typeof text != \"string\" ? \"\" : text);\n        this.type = type;\n        this.amount = amount;\n        this.extraData = {};\n    }\n\n    static fromJSON(obj) {\n        var item = new ReceiptPayment(obj.amount, obj.type, obj.text);\n        item.id = obj.id;\n        item.extraData = obj.extraData ?? {};\n        return item;\n    }\n\n    toJSON() {\n        return {\n            amount: round(this.amount, 2),\n            type: this.type,\n            text: this.text,\n            id: this.id,\n            extraData: this.extraData ?? {}\n        };\n    }\n\n    setExtra(key, val) {\n        this.extraData[key] = val;\n    }\n\n    getExtra(key) {\n        if (typeof this.extraData[key] != \"undefined\") {\n            return this.extraData[key];\n        }\n        return null;\n    }\n\n    get texthtml() {\n        if (typeof this.text != \"string\") {\n            return \"\";\n        }\n        return this.text.replaceAll(\"\\n\", \"<br />\");\n    }\n\n    get amountFormatted() {\n        return getCurrencySymbol() + this.amount.toFixed(2);\n    }\n\n    get label() {\n        if (typeof this.type != \"string\") {\n            return \"Payment\";\n        }\n        switch (this.type) {\n            case \"cash\":\n                return \"Cash\";\n            case \"check\":\n                return \"Check\";\n            case \"card\":\n                return \"Card\";\n            case \"card_manual\":\n                return \"Card\";\n            case \"account\":\n                return \"Account\";\n            case \"free\":\n                return \"Free\";\n            case \"discount\":\n                return \"Discount\";\n            case \"crypto\":\n                return \"Cryptocurrency\";\n            case \"ach\":\n                return \"ACH Debit\";\n            case \"rounding\":\n                return \"Cash Rounding\"; // Used in penniless countries to balance a cash-only transaction\n            default:\n                return this.type;\n        }\n    }\n}\n
"},{"location":"Docs/ReceiptPrinter/","title":"Receipt Printer driver functions","text":"

PostalPoint abstracts the receipt printer hardware commands, so the same functions are available on all brands and languages of receipt printer, and printer media size and settings are also handled for you.

The drivers operate in line mode, where each successive command appends content to the bottom of the page.

These functions are available on the object supplied by the promise returned from global.apis.print.getReceiptPrinter().

"},{"location":"Docs/ReceiptPrinter/#functions","title":"Functions","text":"
//\n// Add one or more lines of text, with automatic wrapping.\n// If both firsttext and secondtext are provided, two columns of text are generated,\n// with the first left-justified and the second right-justified.\n// `firstjustify` can be \"L\" (left), \"C\" (center), or \"R\" (right).\n// Not all printers support all the formatting options, and may render them in different ways,\n// but the formatting intent is made clear regardless.\naddFieldBlock(firsttext, firstjustify, secondtext = \"\", secondjustify = \"R\", bold = false, doubleheight = false, underline = false);\n\n// Add a blank line to the label.\nnewLine();\n\n// Draw a horizontal line across the page.\ndrawLine();\n\n// Render a Code 128 barcode, centered horizontally, with a human-readable label beneath.\n// Important: this function is sometimes asynchronous depending on the printer driver.\nbarcode128(content);\n\n// Print an image.  Width is in pixels.\n// pixelByteArray is a Uint8Array where each bit is a pixel (1=black, 0=white),\n// starting at the top-left of the image and going across and then down. Use `imageToBitmap` to\n// obtain this data from a Jimp image.\n// Use \"L\" as the position to print on the next line, centered horizontally.\n// Some printers also support position = \"C\", which will\n// ignore other commands and print the image centered on the label,\n// but if you're doing that, just use `global.apis.print.printLabelImage()` instead.\ndrawImage(width, position, pixelByteArray);\n\n// If supported by the printer, opens an attached cash drawer.  Command is ignored if unavailable.\nopenCashDrawer();\n\n// The last command to run, when ready to print. Returns the raw data to send to the printer.\n// Important: this function is sometimes asynchronous depending on the printer driver.\ngetData();\n
"},{"location":"Docs/ReceiptPrinter/#example","title":"Example","text":"
var printer = await global.apis.print.getReceiptPrinter();\n\nprinter.addFieldBlock(\"Hello Bold World!\", \"C\", \"\", \"\", true);\nprinter.drawLine();\nawait printer.barcode128(\"1234567890\");\nprinter.newLine();\n\nawait global.apis.printer.printReceiptData(await printer.getData());\n
"},{"location":"Docs/TrackingBarcode/","title":"TrackingBarcode class","text":"

For your reference, here is the source code of the TrackingBarcode class, used to represent a prepaid drop-off. This class is provided to plugins as global.apis.barcode.TrackingBarcode.

export class TrackingBarcode {\n    /**\n     * Create a tracking barcode object.\n     * @param {string} code Tracking number.\n     * @returns {TrackingBarcode}\n     */\n    constructor(code) {\n        // All data are optional except for the tracking number. Missing data is gracefully handled by the PostalPoint UI.\n        this.cleanCode = code;\n        // Original barcode data this was created from\n        this.barcode = code;\n        // Destination ZIP Code, for domestic shipments. The city and state are automatically added.  If toAddress is specified, toZip is ignored in favor of it.\n        this.toZip = \"\";\n        // Two-letter destination country code.  If not \"US\", toZip is ignored, and the full country name is appended to the displayed address information.\n        this.toCountry = defaultCountryCode();\n        // If toAddress is set, it will be used instead of the toZip when displaying the destination.\n        // If both toZip and toAddress are empty strings, no destination will be displayed.\n        this.toAddress = \"\";\n        // If message is not empty, the barcode will NOT be added and the message will be displayed to the user.\n        this.message = \"\";\n        // If warning is not empty, the barcode WILL be added and a message will be displayed to the user.\n        this.warning = \"\";\n        // Shipping carrier name.\n        this.carrier = \"\";\n        // Shipping service/mail class full name and description. Example: \"Priority Mail Adult Signature Required\".\n        this.serviceName = \"\";\n        // Shipping service/mail class name, without extra info such as \"signature required\".\n        // Example: \"Priority Mail\"\n        this.serviceShort = \"\";\n        // If set to false, the barcode will be rejected with a suitable message when PostalPoint is running in self-serve kiosk mode.\n        this.dropoff = true;\n        // If false, app may prompt user to specify the shipping carrier\n        this.confidentCarrier = true;\n        // Extra description strings, like \"Signature Required\"\n        this.extraInfo = [];\n    }\n\n    /**\n     * Set the tracking number\n     * @param {string} str\n     * @returns {undefined}\n     */\n    set tracking(str) {\n        this.cleanCode = str;\n    }\n\n    /**\n     * Set the service/mail class description string.\n     * @param {string} str\n     * @returns {undefined}\n     */\n    set service(str) {\n        this.serviceShort = str;\n        this.serviceName = str;\n    }\n\n    /**\n     * Get the tracking number.\n     * @returns {String}\n     */\n    get tracking() {\n        return this.cleanCode;\n    }\n\n    /**\n     * Get the destination ZIP code.\n     * @returns {String}\n     */\n    get zip() {\n        return this.toZip;\n    }\n\n    /**\n     * Get the service/mail class description.\n     * @returns {String}\n     */\n    get service() {\n        if (this.serviceShort != \"\") {\n            return this.serviceShort;\n        } else if (this.serviceName != \"\") {\n            return this.serviceName;\n        }\n        return \"\";\n    }\n\n    /**\n     * Get the carrier and service info.\n     * @returns {String}\n     */\n    get serviceString() {\n        var str = [];\n        if (this.carrier != \"\") {\n            str.push(this.carrier);\n        }\n        if (this.serviceShort != \"\") {\n            str.push(this.serviceShort);\n        } else if (this.serviceName != \"\") {\n            str.push(this.serviceName);\n        }\n        return str.join(\" \");\n    }\n\n    /**\n     * Get the destination information as a human-presentable multiline string.\n     * @returns {String}\n     */\n    get destString() {\n        var addressLines = [];\n        if (this.toAddress != \"\") {\n            addressLines.push(...this.toAddress.split(\"\\n\"));\n        }\n        if (defaultCountryCode() == this.toCountry.toUpperCase() && this.toCountry.toUpperCase() == \"US\" && this.toZip != \"\" && this.toAddress == \"\") {\n            // Domestic shipment within USA, look up ZIP code\n            var zipdata = getZIP(this.toZip);\n            if (zipdata != false) {\n                addressLines.push(`${zipdata.city} ${zipdata.state} ${this.toZip}`);\n            } else {\n                addressLines.push(`${this.toZip}`);\n            }\n        } else if (defaultCountryCode() == this.toCountry.toUpperCase()) {\n            // Domestic shipment, outside USA, add postal code line if we have one\n            if (this.toZip != \"\" && this.toAddress.includes(this.toZip) != true) {\n                addressLines.push(`${this.toZip}`);\n            }\n        } else {\n            // International shipment, add country name\n            addressLines.push(getCountryNameForISO(this.toCountry));\n        }\n        return addressLines.join(\"\\n\");\n    }\n\n    /**\n     * Get the package information in a format suitable for display on a receipt.\n     * @param {boolean} includeTrackingNumber If false, the tracking number will be suppressed.\n     * @returns {String}\n     */\n    toString(includeTrackingNumber = true) {\n        var lines = [];\n        if (includeTrackingNumber && this.cleanCode) {\n            lines.push(this.cleanCode);\n        }\n        var serv = this.serviceString;\n        if (serv != \"\") {\n            lines.push(serv);\n        }\n        var dest = this.destString;\n        if (dest != \"\") {\n            var destlines = dest.split(\"\\n\");\n            destlines[0] = \"To \" + destlines[0];\n            lines.push(...destlines);\n        }\n\n        if (typeof this.extraInfo == \"object\" && this.extraInfo.length > 0) {\n            lines.push(...this.extraInfo);\n        }\n\n        return lines.join(\"\\n\");\n    }\n}\n
"},{"location":"Examples/01Minimal/","title":"Minimal Plugin","text":"

This is the smallest possible valid plugin.

plugin-name/plugin.js
exports.init = function () {\n    global.apis.alert(\"This message appears when PostalPoint launches.\", \"Hello!\");\n};\n
"},{"location":"Examples/02Basic/","title":"Basic Plugin","text":"

This sample plugin showcases some of the features many plugins will want to use.

Download JavaScript

plugin.js
// Sample plugin to demonstrate plugin capabilities and structure.\n\nasync function getPage() {\n    // A Framework7 component page\n    return global.apis.getPluginFolder(\"basic-demo\") + \"/uipluginpage.f7\";\n}\n\n// This is run when PostalPoint loads the plugin at launch.\n// Use it to register for things you want to do, like adding a page, hooking into payments or shipping rates, etc.\nexports.init = function () {\n    console.log(global.apis.settings.get(\"basic-demo_secretcode\"));\n    global.apis.ui.addToolsPage(getPage, \"Sample Page Title\", \"sampletool1234\", \"A sample plugin page\", \"Sample\", \"fa-solid fa-circle\");\n};\n\n// This defines a settings UI to display for the plugin.\n// If exports.config is a function instead of an array, it will be executed when opening the settings\n// and must return an array like the one below.\n// If exports.config is undefined, a settings menu will not be provided to the user.\nexports.config = [\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.alert(\"Button pressed\");\n        }\n    },\n    {\n        type: \"text\",\n        key: \"app.postalpoint.basic-demo_somestring\", // Try to make sure this is unique by using a prefix,\n        // settings storage is global so there could be conflicts if you aren't careful\n        defaultVal: \"\",\n        label: \"Type a string\",\n        placeholder: \"\",\n        text: \"Description text next to the input box\",\n        sync: false // Add sync: false to prevent automatically syncing this setting between\n                    // PostalPoint installations (i.e. it's a device-specific setting, like a pairing code)\n                    // If it's not present, or is any truthy value, it could be synced between PCs\n    },\n    {\n        type: \"password\",\n        key: \"app.postalpoint.basic-demo_secretcode\",\n        defaultVal: \"\",\n        label: \"Secret Code\",\n        placeholder: \"\",\n        text: \"Don't tell anyone this secret code:\"\n    },\n    {\n        type: \"textarea\",\n        key: \"app.postalpoint.basic-demo_sometext\",\n        defaultVal: \"\",\n        label: \"Text Box\",\n        placeholder: \"...\",\n        text: \"You can type a few lines of text here.\"\n    },\n    {\n        type: \"select\",\n        key: \"app.postalpoint.basic-demo_dropdownbox\",\n        defaultVal: \"\",\n        label: \"Choose an option\",\n        placeholder: \"\",\n        text: \"\",\n        options: [[\"key1\", \"Value 1\"], [\"key2\", \"Value 2\"]]\n    }\n];\n

Download HTML

uipluginpage.f7
<template>\n    <div class=\"page\">\n        <div class=\"navbar\">\n            <div class=\"navbar-bg\"></div>\n            <div class=\"navbar-inner\">\n                <div class=\"title\">${title}</div>\n            </div>\n        </div>\n        <div class=\"page-content\">\n            <a class=\"button\" @click=${openAlert}>Open Alert</a>\n            <a class=\"button\" @click=${printSomething}>Print Something</a>\n            <div class=\"list simple-list\">\n                <ul>\n                    ${names.map((name) => $h`\n                    <li>${name}</li>\n                    `)}\n                </ul>\n            </div>\n        </div>\n    </div>\n</template>\n<!-- component styles -->\n<style>\n    .red-link {\n        color: red;\n    }\n</style>\n<!-- rest of component logic -->\n<script>\n    // script must return/export component function\n    export default (props, { $f7, $on }) => {\n        const title = 'Component Page';\n        const names = ['John', 'Vladimir', 'Timo'];\n\n        const openAlert = () => {\n            $f7.dialog.alert('Hello world!\\nblah blah blah');\n        }\n\n        async function printSomething() {\n            // Print some text to the receipt printer\n            var printer = await global.apis.print.getReceiptPrinter();\n            printer.addFieldBlock('Hello world!\\nblah blah blah\\n\\n', \"C\");\n            global.apis.print.printReceiptData(await printer.getData());\n        }\n\n        $on('pageInit', () => {\n            // do something on page init\n        });\n        $on('pageAfterOut', () => {\n            // page has left the view\n        });\n\n        // component function must return render function\n        return $render;\n    }\n</script>\n
"},{"location":"Examples/03Shipping/","title":"Shipping Plugin","text":"

This sample plugin demonstrates how to add support for a custom shipping carrier.

Download JavaScript

plugin.js
// This is a sample PostalPoint plugin for adding support for a shipping carrier.\n\nvar rateCache = [];\nvar parcelCache = {};\n\nexports.init = function () {\n    // Add support for shipping rating and label purchasing\n    global.apis.shipping.registerRateEndpoint(getRates, purchase, \"uniqueprefixhere_\");\n\n    // Add support for prepaid drop-offs\n    global.apis.barcode.onPrepaidScan(function (barcode) {\n        if (barcode.startsWith(\"mycarrierbarcode\")) { // Replace this with your checks for barcode validity\n            var data = new global.apis.barcode.TrackingBarcode(barcode);\n            data.carrier = \"Carrier Name\";\n            data.service = \"Service Name\";\n            return data;\n        }\n        return false;\n    });\n}\n\nasync function purchase(rateid) {\n    for (var i = 0; i < rateCache.length; i++) {\n        if (rateCache[i].rateid == rateid) {\n            var rate = rateCache[i];\n            //\n            // Fetch label and tracking and such\n            //\n            var label;\n            var tracking = \"123456\";\n            var toAddressLines = parcelCache.toAddress.toStringArray();\n\n            // Create receipt item\n            var receiptitem = new global.apis.pos.ReceiptItem(`uniqueprefixhere_${tracking}`,\n                    `${rate.carrierName} ${rate.serviceName}`,\n                    `Tracking # ${global.apis.util.string.chunk(tracking, 3).join(\" \")}\\nTo:\\n${toAddressLines.join(\"\\n\")}`,\n                    rate.retail_rate, 1, rate.cost_rate, 0\n                    );\n            receiptitem.barcode = tracking;\n            receiptitem.carrier = \"Carrier Name\";\n            receiptitem.service = \"Service Name\";\n\n            return {\n                label: label,\n                labeltype: \"PNG\",\n                receiptItem: receiptitem,\n                tracking: tracking,\n                cost: rate.cost_rate,\n                price: rate.retail_rate,\n                carrier: rate.carrierName,\n                service: rate.serviceName,\n                delivery_days: rate.delivery_days,\n                delivery_date: rate.delivery_date,\n                to: toAddressLines\n            };\n        }\n    }\n}\n\nasync function getRates(parcel) {\n    // parcel is an object as shown in docs/Parcel.md\n    var rates = [];\n    rates.push({\n        rateid: \"uniqueprefixhere_\" + global.apis.util.uuid.v4(),\n        carrier: \"Carrier\",\n        carrierName: \"Carrier Name\",\n        service: \"CARRIER_SERVICE_ID\",\n        cost_rate: 10,\n        retail_rate: 15,\n        delivery_days: 3,\n        delivery_date: null,\n        guaranteed: true,\n        serviceName: \"Service Name\",\n        color: \"green\" // Rate card color\n    });\n\n    // Save details for later use if purchased\n    rateCache = rates;\n    parcelCache = parcel;\n\n    return rates;\n}\n
"},{"location":"Examples/04CardProcessor/","title":"Card Payments","text":"

This plugin template shows how to implement a card payment processor, allowing PostalPoint to interface with your card reader/terminal hardware, as well as saving, retrieving, and charging cards on file.

Download JavaScript

plugin-name/plugin.js
// This is a sample PostalPoint plugin that adds a card payment processor.\n\nexports.init = function () {\n    global.apis.pos.registerCardProcessor({\n        name: \"Demo Card Processor\",\n        init: async function () {\n            // This function runs once after starting PostalPoint\n            // and before any other card processor functions are called.\n        },\n        checkout: async function({amount, capture = true}) {\n            // amount is an integer number of pennies.\n\n            // If an error is encountered during processing,\n            //    display an error message in a dialog and return boolean false.\n            //    If this function returns anything except false or undefined, and doesn't throw an error,\n            //    it is assumed the payment was successful.\n            try {\n                if (capture) {\n                    // authorize, capture, add a ReceiptPayment to the receipt, and return boolean true.\n                    global.apis.pos.addOnscreenPaymentLog(\"Getting card payment...\"); // Add a line to the onscreen card processing status log\n                    await global.apis.util.delay(1000); // Replace this with something useful!\n                    global.apis.pos.addReceiptPayment(\n                        new global.apis.pos.ReceiptPayment(\n                            (amount / 100).toFixed(2) * 1,\n                            \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.  Other types will be displayed as-is to the user and on the receipt.\n                            \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n                        )\n                    );\n                    global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n                    return true;\n                } else {\n                    // only authorize the payment, don't actually capture/charge the payment method,\n                    // and return whatever transaction data that will be passed to finishPayment to capture the payment.\n                    await global.apis.util.delay(1000); // Replace this with something useful!\n                    return {amount: amount};\n                }\n            } catch (ex) {\n                global.apis.pos.addOnscreenPaymentLog(`Error: ${ex.message} [okay to put extra details here for troubleshooting or tech support, it's visible to the cashier]`);\n                if (global.apis.kiosk.isKiosk()) {\n                    // This message will be shown to an end-user/customer, not a cashier/employee\n                    global.apis.alert(\"Your card was declined.\", \"Card Error\");\n                } else {\n                    global.apis.alert(\"The customer's card was declined.\", \"Card Error\");\n                }\n                return false;\n            }\n        },\n        cancelCheckout: function () {\n            // The user requested to cancel the payment.\n            // Reset the terminal to its resting state, clear its screen, etc.\n        },\n        finishPayment: async function ({checkoutResponse}) {\n            // Finish a payment that was authorized but not captured because checkout was called with capture = false\n            // If payment was already captured and added to the receipt for some reason, just return true.\n            await global.apis.util.delay(1000); // Replace this with something useful!\n            global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                    (checkoutResponse.amount / 100).toFixed(2) * 1,\n                    \"card\", // Payment type.\n                    \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n                )\n            );\n            return true;\n        },\n        updateCartDisplay: function (receipt) {\n            // Show transaction data on the card reader display.\n            // This function will be called when the cart or total changes.\n            console.log(receipt);\n            // Sample structure of the receipt variable:\n            receipt = {\n                \"items\": [\n                    {\n                        \"id\": \"testitem\",\n                        \"label\": \"Test Item\",\n                        \"text\": \"\",\n                        \"priceEach\": 2,\n                        \"qty\": 1,\n                        \"cost\": 0,\n                        \"retail\": 2,\n                        \"taxRate\": 0.1,\n                        \"free\": false,\n                        \"barcode\": \"\",\n                        \"certifiedInfo\": false,\n                        \"isMerch\": true,\n                        \"surcharge\": false\n                    },\n                    {\n                        \"id\": \"9100123456789012345678\",\n                        \"label\": \"Test Package\",\n                        \"text\": \"Package Details\\nTracking # 9100 1234 5678 9012 3456 78\\nTo:\\nTEST PERSON\\nORGANIZATION INC\\n123 TEST ROAD\\nTESTTOWN TE 99999-0001\",\n                        \"priceEach\": 8,\n                        \"qty\": 1,\n                        \"cost\": 0,\n                        \"retail\": 8,\n                        \"taxRate\": 0,\n                        \"free\": false,\n                        \"barcode\": \"9100123456789012345678\",\n                        \"certifiedInfo\": false,\n                        \"isMerch\": false,\n                        \"surcharge\": false,\n                        \"toAddress\": {\n                            \"name\": \"TEST PERSON\",\n                            \"company\": \"ORGANIZATION INC\",\n                            \"street1\": \"123 TEST ROAD\",\n                            \"street2\": null,\n                            \"city\": \"TESTTOWN\",\n                            \"state\": \"TE\",\n                            \"zip\": \"99999-0001\",\n                            \"email\": null,\n                            \"phone\": null,\n                            \"country\": \"US\"\n                        },\n                        \"fromAddress\": {\n                            \"name\": \"TEST PERSON\",\n                            \"company\": \"ORGANIZATION INC\",\n                            \"street1\": \"123 TEST ROAD\",\n                            \"street2\": null,\n                            \"city\": \"TESTTOWN\",\n                            \"state\": \"TE\",\n                            \"zip\": \"99999-0001\",\n                            \"email\": null,\n                            \"phone\": null,\n                            \"country\": \"US\"\n                        }\n                    }\n                ],\n                \"payments\": [\n                    {\n                        \"amount\": 10,\n                        \"amountFormatted\": \"$10.00\",\n                        \"type\": \"cash\",\n                        \"label\": \"Cash\",\n                        \"text\": \"\",\n                        \"texthtml\": \"\",\n                        \"id\": \"12345678_cash_10\"\n                    },\n                    {\n                        \"amount\": 12.34,\n                        \"amountFormatted\": \"$12.34\",\n                        \"type\": \"card\",\n                        \"label\": \"Card\",\n                        \"text\": \"Card Details here\\n1234abcd\",\n                        \"texthtml\": \"Card Details here<br />1234abcd\",\n                        \"id\": \"87654321_card_12.34\"\n                    }\n                ],\n                \"subtotal\": 10,\n                \"subtotalFormatted\": \"$10.00\",\n                \"tax\": 0.2,\n                \"taxFormatted\": \"$0.20\",\n                \"grandTotal\": 10.2,\n                \"grandTotalFormatted\": \"$10.20\",\n                \"paid\": 22.34,\n                \"paidFormatted\": \"$22.34\",\n                \"due\": -12.14, // If negative, is the amount of change owed to the customer instead\n                \"dueFormatted\": \"$12.14\"\n            };\n        },\n        checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {\n            // Same as checkout() except using a payment method already on file.\n            // customerID and paymentMethodID are provided by getSavedPaymentMethods below.\n            await global.apis.util.delay(1000); // Replace this with something useful!\n            var error = false;\n            if (error) {\n                // If you can't charge the payment method, throw an Error with a string to display to the user.\n                throw new Error(\"The saved card didn't work.\");\n            }\n            global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                    (amount / 100).toFixed(2) * 1,\n                    \"card\", // Payment type.\n                    \"Card on File\\nx1234\" // Additional text for receipt\n                )\n            );\n            // Must return true upon success.\n            // If the payment is not successful, and you didn't throw an Error to show the user,\n            // then `return false` instead and it'll appear that the user's action to start the payment did nothing.\n            return true;\n        },\n        saveCardForOfflineUse: async function ({statusCallback, customerUUID, name, company, street1, street2, city, state, zip, country, email, phone}) {\n            // Use the card reader to capture an in-person card and save it for offline use.\n            // Provided details are the customer's info, which might be empty strings except for the customerUUID.\n            // Saved card details must be tied to the customerUUID, as that's how saved cards are looked up.\n\n            // statusCallback(string, boolean) updates the progress message on the cashier's screen.\n            // If the boolean is true, the progress message is replaced with a confirmation message.\n            statusCallback(\"Insert the card into the reader.\", false);\n\n            await global.apis.util.delay(1000); // Wait for the customer to insert their card,\n            //then save it for later offline billing\n\n            statusCallback(\"Saving card details...\", false);\n\n            await global.apis.util.delay(1000);\n\n            statusCallback(\"Card saved!\", true);\n\n            return true; // Card saved to customer\n            // If an error occurred, you can throw it and the error message will be displayed to the cashier.\n            // Alternatively, return boolean false and display the error yourself with global.apis.alert(message, title) or something.\n        },\n        cancelSaveCardForOfflineUse: function () {\n            // Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.\n        },\n        getSavedPaymentMethods: async function ({customerUUID}) {\n            // Return all saved payment methods tied to the provided customer UUID.\n            var methods = [];\n            methods.push({\n                customer: \"<internal string referencing the customer>\", // Passed to checkoutSavedMethod as customerID\n                customer_uuid: customerUUID,\n                id: \"<card/payment method identifier>\", // Passed to checkoutSavedMethod as paymentMethodID\n                type: \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.\n                label: \"Visa debit x1234 (exp. 12/29)\", // Label for payment method\n                label_short: \"Visa debit x1234\" // Abbreviated label for payment method\n            });\n            return methods;\n        },\n        deleteSavedPaymentMethod: async function ({customerUUID, customerID, paymentMethodID}) {\n            // Delete the payment method identified by paymentMethodID and tied to the PostalPoint customerUUID and the card processor customerID.\n            // If unable to delete, throw an error and the error message will be displayed to the cashier.\n            await global.apis.util.delay(1000);\n        }\n    });\n}\n\n// Plugin settings to display.\nexports.config = [\n    {\n        type: \"password\",\n        key: \"democardprocessor_apikey\",\n        defaultVal: \"\",\n        label: \"API Key\",\n        placeholder: \"\",\n        text: \"API Key\"\n    },\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.ui.openSystemWebBrowser(\"https://postalpoint.app\");\n        }\n    }\n];\n
"},{"location":"Examples/05CryptoProcessor/","title":"Cryptocurrency Payments","text":"

This plugin template shows how to implement a cryptocurrency payment processor, using the customer-facing display to show a payment QR code webpage.

Download JavaScript

plugin-name/plugin.js
// This is a sample PostalPoint plugin that adds a card payment processor.\n\nexports.init = function () {\n    global.apis.pos.registerCryptoProcessor({\n        name: \"Demo Crypto\",\n        init: async function () {\n            // This is run after PostalPoint starts, and before any other crypto functions are called.\n        },\n        checkout: async function ( {amount}) {\n            // Run the checkout process.\n            // amount is the amount of USD to collect, in pennies.\n\n            // If an error is encountered during processing,\n            //    display an error message in a dialog and return boolean false.\n            //    If this function returns anything except false or undefined, and doesn't throw an error,\n            //    it is assumed the payment was successful.\n\n            // Adds a line of text visible to the cashier\n            global.apis.pos.addOnscreenPaymentLog(\"Getting crypto payment...\");\n\n            // Display a web page (i.e. with a payment QR code) to the customer on the customer-facing display.\n            global.apis.ui.setCustomerScreen(\"<html></html>\", \"html\");\n            global.apis.ui.setCustomerScreen(\"https://postalpoint.app\", \"raw\");\n\n            // Poll the status of the crypto transaction\n            var paymentComplete = false;\n            do {\n                await global.apis.util.delay(1000);\n                paymentComplete = true;\n            } while (paymentComplete != true);\n\n            global.apis.pos.addReceiptPayment(\n                    new global.apis.pos.ReceiptPayment(\n                            (amount / 100).toFixed(2) * 1,\n                            \"crypto\", // Payment type.\n                            \"Bitcoin\\n0.00001234 BTC\" // Additional text for receipt\n                            )\n                    );\n            global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n            global.apis.ui.clearCustomerScreen();\n        },\n        cancelCheckout: function () {\n            // The user requested to cancel the payment.\n            // Reset things accordingly.\n            global.apis.ui.clearCustomerScreen();\n        },\n        isConfigured: function () {\n            // Is this plugin properly setup and able to process payments?  If not, return false.\n            return true;\n        }\n    });\n}\n\n// Plugin settings to display.\nexports.config = [\n    {\n        type: \"password\",\n        key: \"democryproprocessor_apikey\",\n        defaultVal: \"\",\n        label: \"API Key\",\n        placeholder: \"\",\n        text: \"API Key\"\n    },\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.ui.openSystemWebBrowser(\"https://postalpoint.app\");\n        }\n    }\n];\n
"},{"location":"Plugin%20API/barcode/","title":"Barcode","text":""},{"location":"Plugin%20API/barcode/#barcode-object","title":"barcode : object","text":"

Handle tracking barcodes

Kind: global namespace

"},{"location":"Plugin%20API/barcode/#barcodetrackingbarcode","title":"barcode.TrackingBarcode","text":"

Kind: static class of barcode Properties

Name Type Description tracking string Tracking number barcode string Original barcode data this was created from toZip string Destination ZIP Code, for domestic shipments. The city and state are automatically added. If toAddress is specified, toZip is ignored in favor of it. toCountry string Two-letter destination country code. If it doesn't match the country PostalPoint is running in, the full country name is appended to the displayed address information. toAddress string Destination mailing/shipping address. carrier string Shipping carrier name. service string Shipping service/mail class name. Example: \"Priority Mail\". dropoff boolean If set to false, the barcode will be rejected with a suitable message when PostalPoint is running in self-serve kiosk mode. confidentCarrier boolean If false, PostalPoint may prompt user to specify the shipping carrier. extraInfo Array.<string> Extra description strings, like \"Signature Required\". message string If not empty, the barcode will NOT be added and the contents of message will be displayed to the user. warning string If not empty, the barcode WILL be added and the contents of warning will be displayed to the user. destString string (read only) Get the destination information as a human-presentable multiline string. serviceString string (read only) Get the carrier and service. toString() function Get the package information in a format suitable for display on a receipt. toString(false) function Get the package information in a format suitable for display on a receipt, suppressing the tracking number.

"},{"location":"Plugin%20API/barcode/#new-trackingbarcodecode","title":"new TrackingBarcode(code)","text":"

A Tracking barcode object.

Param Type Description code string Barcode data

"},{"location":"Plugin%20API/barcode/#barcodeaddprepaidbarcodetrackingbarcodedata","title":"barcode.addPrepaidBarcode(trackingBarcodeData)","text":"

Add a TrackingBarcode object to the transaction receipt at any time other than onPrepaidScan.

Kind: static method of barcode

Param Type trackingBarcodeData TrackingBarcode

"},{"location":"Plugin%20API/barcode/#barcodeinjectbarcodedata","title":"barcode.inject(barcodeData)","text":"

Pass data to the internal barcode event subsystem. The data is handled as if it were just received from a physical barcode scanner.

Kind: static method of barcode

Param Type barcodeData string

"},{"location":"Plugin%20API/barcode/#barcodeonprepaidscanf","title":"barcode.onPrepaidScan(f)","text":"

The function passed to onPrepaidScan is run when a barcode is scanned on the Prepaid page. The function is passed one argument, a string containing the raw barcode data. The function shall return boolean false if unable or unwilling to handle the barcode. If the barcode is handled by this function, it shall return a TrackingBarcode object.

Kind: static method of barcode

Param Type f function

"},{"location":"Plugin%20API/barcode/#barcoderegisterdropoffcarrierscanhandlercarrier-fn","title":"barcode.registerDropOffCarrierScanHandler(carrier, fn)","text":"

Register to handle prepaid drop off scans for a particular shipping carrier. Scans are kept in a local, disk-backed queue and the function registered here will be called when a queued barcode is processed for the provided carrier. This function is intended for carrier drop-off reimbursement programs such as ASO and FASC.

Kind: static method of barcode Throws:

Param Type Description carrier string Carrier name to register for. fn function Async function to pass scan details to. Returns true if processed, false if not processed (but the barcode should be removed from queue), or throws an Error if it should be retried later. See example for data and usage.

Example

global.apis.barcode.registerDropOffCarrierScanHandler(\"FedEx\", function (data) {\n    global.apis.alert(`Carrier: ${data.carrier}, Tracking number: ${data.tracking}, `\n        + `Raw scanned barcode: ${data.barcode}, `\n        + `UNIX timestamp of scan: ${data.timestamp}, Scan UUID: ${data.uuid}`,\n        \"Processing DropOffCarrierScan data\");\n\n    return false; // Not processed but should be discarded\n    return true; // Processed, discard from queue\n    throw new Error(\"Failed to process, try again later\");\n});\n

"},{"location":"Plugin%20API/database/","title":"Database","text":""},{"location":"Plugin%20API/database/#database-object","title":"database : object","text":"

Database connection

Kind: global namespace

"},{"location":"Plugin%20API/database/#databasegetconnection-promisedatabaseadapter","title":"database.getConnection() \u21d2 Promise.<DatabaseAdapter>","text":"

Return a database connection object to run SQL against the store database. See the Database docs for details.

Kind: static method of database

"},{"location":"Plugin%20API/fs/","title":"Fs","text":""},{"location":"Plugin%20API/fs/#fs-object","title":"fs : object","text":"

Basic filesystem access utility functions, wrapping Node.JS and/or NW.JS code.

Kind: global namespace

"},{"location":"Plugin%20API/fs/#fsopenfilesavedialogsuggestedfilename-fileextensions-promisestringnull","title":"fs.openFileSaveDialog(suggestedFilename, fileExtensions) \u21d2 Promise.<(string|null)>","text":"

Open a file save as dialog prompting the user to save a file. Opens to the user's Documents folder, with sane fallbacks if it cannot be located.

Kind: static method of fs Returns: Promise.<(string|null)> - The full file path the user selected, or null if they cancelled.

Param Type Description suggestedFilename string The filename string to pre-fill in the dialog. fileExtensions string The file type filter to show. Examples: \".csv\", \".csv,.html\"

"},{"location":"Plugin%20API/fs/#fsopenfilebrowsedialogchoosefolder-accept-dialogtitle-string-null","title":"fs.openFileBrowseDialog(chooseFolder, accept, dialogTitle) \u21d2 string | null","text":"

Open a file browse/file open dialog prompting the user to select a file or folder. Opens to the user's Documents folder, with sane fallbacks if it cannot be located.

Kind: static method of fs Returns: string | null - The selected file/folder path, or null if cancelled.

Param Type Default Description chooseFolder boolean false Set to true to choose a folder instead of a file. accept string File filter. \".csv,.html\", \"image/*\", etc. dialogTitle string | null null Title of the file open dialog.

"},{"location":"Plugin%20API/fs/#fswritefilefilename-data-encoding-flag-promise","title":"fs.writeFile(filename, data, encoding, flag) \u21d2 Promise","text":"

Write a file to disk.

Kind: static method of fs

Param Type Default Description filename string The path and filename to write to. data string | Buffer | ArrayBuffer | Uint8Array Data to write to the file. encoding string | null \"utf8\" Text encoding. Set to empty if not passing string data. flag string | null \"w+\" Filesystem flag.

"},{"location":"Plugin%20API/fs/#fsreadfilefilename-encoding-flag-promisestringbuffer","title":"fs.readFile(filename, encoding, flag) \u21d2 Promise.<(string|Buffer)>","text":"

Read a file from disk and return its contents.

Kind: static method of fs

Param Type Default Description filename string The path and filename to read from. encoding string \"utf8\" File encoding. Set to null or empty string when reading binary data. flag string \"r+\" Filesystem flag.

"},{"location":"Plugin%20API/fs/#fsfileexistsfilename-boolean","title":"fs.fileExists(filename) \u21d2 boolean","text":"

Check if a file exists.

Kind: static method of fs

Param Type Description filename string Path and filename to check."},{"location":"Plugin%20API/global%20functions/","title":"Global functions","text":""},{"location":"Plugin%20API/global%20functions/#f7","title":"f7","text":"

The Framework7 app instance for PostalPoint's entire UI, created by new Framework7(). See https://framework7.io/docs/app for details.

Kind: global constant

"},{"location":"Plugin%20API/global%20functions/#getpluginfolderid-string","title":"getPluginFolder([id]) \u21d2 string","text":"

Get the filesystem path to a plugin's installation folder.

Kind: global function Returns: string - \"/home/user/.config/postalpoint-retail/Default/storage/plugins/...\", \"C:\\Users\\user\\AppData...\", etc

Param Type Description [id] string Plugin ID. If omitted or empty, will return the parent folder plugins are installed within.

"},{"location":"Plugin%20API/global%20functions/#getappfolder-string","title":"getAppFolder() \u21d2 string","text":"

Get the filesystem path to the PostalPoint installation folder.

Kind: global function

"},{"location":"Plugin%20API/global%20functions/#alerttext-title-callback","title":"alert(text, title, [callback])","text":"

Display a simple alert-style dialog box.

Kind: global function

Param Type Default Description text string Body text of the dialog. title string Dialog title. [callback] function Function to call when the alert is closed."},{"location":"Plugin%20API/graphics/","title":"Graphics","text":""},{"location":"Plugin%20API/graphics/#graphics-object","title":"graphics : object","text":"

PostalPoint uses the Jimp library version 1.6 for creating and manipulating images and shipping labels.

Kind: global namespace

"},{"location":"Plugin%20API/graphics/#graphicsjimp-jimp","title":"graphics.Jimp() \u21d2 Jimp","text":"

The JavaScript Image Manipulation Program.

Kind: static method of graphics Example

const {Jimp} = global.apis.graphics.Jimp();\n

"},{"location":"Plugin%20API/graphics/#graphicsloadfontfilename-promise","title":"graphics.loadFont(filename) \u21d2 Promise","text":"

Replacement for Jimp's loadFont function, which gets very confused about our JS environment and ends up crashing everything.

Kind: static method of graphics

Param Type filename string"},{"location":"Plugin%20API/httpserver/","title":"Httpserver","text":""},{"location":"Plugin%20API/httpserver/#httpserver-object","title":"httpserver : object","text":"

Add features to PostalPoint's integrated LAN HTTP API server.

Kind: global namespace

"},{"location":"Plugin%20API/httpserver/#httpserveraddendpointid-oncall","title":"httpserver.addEndpoint(id, onCall)","text":"

Add a custom HTTP JSON POST endpoint to the LAN HTTP API server running inside PostalPoint. Requests must be POSTed and contain a JSON body (or empty body, which will be converted to null).

Kind: static method of httpserver

Param Type Description id string Endpoint ID. Used in URL, for example: http://<host>:7678/<id> onCall function Async function to call when the endpoint is called, which returns the response.

Example

global.apis.httpserver.addEndpoint(\"testendpoint\", async function (request) {\n    if (request.abc == \"123\") {\n        // A non-string `body` is converted to JSON before the HTTP reply is sent.\n        return {body: {json: true, abc: 123}, httpcode: 200, contentType: \"application/json\"};\n    }\n    // A string `body` is sent to the client as-is using whatever contentType you specify.\n    return {body: \"abc\", httpcode: 200, contentType: \"text/plain\"};\n});\n

"},{"location":"Plugin%20API/httpserver/#httpservergetserverport-number","title":"httpserver.getServerPort() \u21d2 number","text":"

Get the local HTTP server's port number.

Kind: static method of httpserver

"},{"location":"Plugin%20API/httpserver/#httpservergetclientkey-string","title":"httpserver.getClientKey() \u21d2 string","text":"

Get the local machine's HTTP client key it uses to authenticate with other installations of PostalPoint on the LAN.

Kind: static method of httpserver

"},{"location":"Plugin%20API/httpserver/#httpserversendrequesttoremotedata-endpointid-serveraddress-serverport-promiseobject","title":"httpserver.sendRequestToRemote(data, endpointID, serverAddress, serverPort) \u21d2 Promise.<Object>","text":"

Send a HTTP request to another PostalPoint installation on the local network.

Kind: static method of httpserver Returns: Promise.<Object> - The JSON reply. Throws:

Param Type Description data Object Data to encode as JSON in the request body. endpointID string Endpoint to call. serverAddress string | undefined Address of the PostalPoint server. If undefined, uses the host address configured in PostalPoint's Databases settings. serverPort number | undefined Port of the PostalPoint server. If undefined, the default PostalPoint port number is used."},{"location":"Plugin%20API/i18n/","title":"I18n","text":""},{"location":"Plugin%20API/i18n/#i18n-object","title":"i18n : object","text":"

Functions to help support multiple currencies and countries.

Kind: global namespace

"},{"location":"Plugin%20API/i18n/#i18ncountry-string","title":"i18n.country() \u21d2 string","text":"

Get the 2-character ISO country code that PostalPoint is running in.

Kind: static method of i18n Returns: string - \"US\", \"CA\", etc.

"},{"location":"Plugin%20API/i18n/#i18ncurrency-string","title":"i18n.currency() \u21d2 string","text":"

Get the 3-character currency code in use.

Kind: static method of i18n Returns: string - \"usd\", \"cad\", etc.

"},{"location":"Plugin%20API/i18n/#i18nsymbol-string","title":"i18n.symbol() \u21d2 string","text":"

Get the currency symbol.

Kind: static method of i18n Returns: string - \"$\", \"\u20ac\", \"\u20a4\", etc

"},{"location":"Plugin%20API/i18n/#i18ndecimals-number","title":"i18n.decimals() \u21d2 number","text":"

Get the number of decimal places for the currency: for example, USD has 2 ($x.00), KRW has 0 (x), UYW has 4 (x.0000).

Kind: static method of i18n Returns: number - 0, 2, 3, or 4

"},{"location":"Plugin%20API/i18n/#i18nconvertcurrencyamount-fromcurrency-tocurrency-promisenumber","title":"i18n.convertCurrency(amount, fromCurrency, [toCurrency]) \u21d2 Promise.<number>","text":"

Convert an amount of money to a different currency. Conversion rate is retrieved from the internet and cached for 4 hours.

Kind: static method of i18n

Param Type Default Description amount number Amount of money in the \"wrong\" currency fromCurrency string The currency code for the \"wrong\" currency that needs conversion [toCurrency] string \"getCurrencyCode()\" The \"correct\" currency we want the amount to be in.

"},{"location":"Plugin%20API/i18n/#i18nmoneytofixedamount-string","title":"i18n.moneyToFixed(amount) \u21d2 string","text":"

Returns the amount as a string formatted with the correct number of decimal places for the currency in use.

Kind: static method of i18n Returns: string - \"1.23\", \"1.2345\", etc

Param Type amount number

"},{"location":"Plugin%20API/i18n/#i18nmoneystringamount-string","title":"i18n.moneyString(amount) \u21d2 string","text":"

Returns the amount as a string, with the correct decimal places, and the local currency symbol.

Kind: static method of i18n Returns: string - \"$1.23\", etc

Param Type amount number

"},{"location":"Plugin%20API/i18n/#i18ncurrencyminortomajoramount-number","title":"i18n.currencyMinorToMajor(amount) \u21d2 number","text":"

Convert an amount in cents to dollars (or the local equivalent currency units).

Kind: static method of i18n Returns: number - Dollars, etc

Param Type Description amount number Cents, etc

"},{"location":"Plugin%20API/i18n/#i18ncurrencymajortominoramount-number","title":"i18n.currencyMajorToMinor(amount) \u21d2 number","text":"

Convert an amount in dollars to cents (or the local equivalent currency units).

Kind: static method of i18n Returns: number - Cents, etc

Param Type Description amount number Dollars, etc"},{"location":"Plugin%20API/kiosk/","title":"Kiosk","text":""},{"location":"Plugin%20API/kiosk/#kiosk-object","title":"kiosk : object","text":"

Self-serve kiosk mode

Kind: global namespace

"},{"location":"Plugin%20API/kiosk/#kioskiskiosk-boolean","title":"kiosk.isKiosk() \u21d2 boolean","text":"

Check if PostalPoint is running in kiosk mode.

Kind: static method of kiosk Returns: boolean - - True if system is in kiosk mode, else false.

"},{"location":"Plugin%20API/mailboxes/","title":"Mailboxes","text":""},{"location":"Plugin%20API/mailboxes/#mailboxes-object","title":"mailboxes : object","text":"

Add, modify, and delete mailboxes and mailbox customers.

Kind: global namespace

"},{"location":"Plugin%20API/mailboxes/#mailboxesformps1583","title":"mailboxes.FormPS1583","text":"

Kind: static class of mailboxes

"},{"location":"Plugin%20API/mailboxes/#new-formps1583","title":"new FormPS1583()","text":"

USPS Form PS1583 object, with all the fields needed by USPS for CMRA customers.

"},{"location":"Plugin%20API/mailboxes/#mailboxesgetlistfilter-promisearray","title":"mailboxes.getList(filter) \u21d2 Promise.<Array>","text":"

Get the list of mailboxes and boxholders as an array of objects, see example.

Kind: static method of mailboxes

Param Type Default Description filter null | object Filter to mailboxes matching a column condition, such as getList({number: \"102\"}) or getList({\"size >=\": \"2\"}). Supported filter names include \"number\" (string, box number), \"expires\" (expiration date), \"size\" (number 1-10), and \"barcode\" (string) SQL injection warning: Filter names are inserted directly into query strings without sanitization. Only the values are safe for user input.

Example

[{\n    num: \"123\", // Box number as string\n    expires: 1234567890, // UNIX timestamp (in seconds) or false if box vacant\n    size: \"2\", // Box size, 1-10\n    notes: \"\", // Notes for mailbox, not currently shown in Mailbox Manager UI but may be used in the future\n    barcode: \"\", // Unique barcode for the mailbox, for future use\n    renewalMerchID: \"\", // Merchandise item ID used for autorenewing this mailbox\n    isBusiness: false, // True if the box is for a business, false if for personal use\n    names: [], // Array of boxholders. See addOrUpdateBoxholder for the format.\n    packages: [], // Array of packages awaiting pickup, see below\n    vacant: false // True if the box is currently vacant, else false\n}]\n
Example
// Data objects in the packages array:\n{\n    tracking: tracking ?? \"[Untracked]\", // Package tracking number\n    finalized: true, // True if package check-in is finished and shelf tag/mailbox slips printed, false if not finalized\n    available_date: Date(), // The date and time the package was checked in\n    tag: \"\" // Unique number assigned to the package and printed on shelf tags, scanned by employee when customer picks up package\n}\n

"},{"location":"Plugin%20API/mailboxes/#mailboxesadddaystomailboxboxnumber-days-months-promise","title":"mailboxes.addDaysToMailbox(boxNumber, days, months) \u21d2 Promise","text":"

Add a number of days or months to a mailbox's expiration. Use either days or months, not both.

Kind: static method of mailboxes

Param Type Default Description boxNumber string Mailbox number. days number 0 Days to add. months number 0 Months to add.

"},{"location":"Plugin%20API/mailboxes/#mailboxessetmailboxexpirationdateboxnumber-date-promise","title":"mailboxes.setMailboxExpirationDate(boxNumber, date) \u21d2 Promise","text":"

Set the box expiration to a specific JavaScript Date object, or a UNIX timestamp (in seconds).

Kind: static method of mailboxes

Param Type boxNumber string date number | Date

"},{"location":"Plugin%20API/mailboxes/#mailboxescreatemailboxnumber-size-notes-barcode-promise","title":"mailboxes.createMailbox(number, size, notes, barcode) \u21d2 Promise","text":"

Create a new mailbox number with the specified box size. Throws an error if the box number is already in use.

Kind: static method of mailboxes

Param Type Description number string Mailbox number size number Box size (1 - 10) notes string Arbitrary string with human-readable notes about the box. barcode null | string A barcode value representing this mailbox, typically a sticker on the the physical box visible when delivering mail.

"},{"location":"Plugin%20API/mailboxes/#mailboxeseditmailboxoldnumber-newnumber-newsize-barcode-promise","title":"mailboxes.editMailbox(oldNumber, newNumber, newSize, barcode) \u21d2 Promise","text":"

Change the number and/or size of a mailbox while preserving the boxholders and packages associated. If only changing size, set oldNumber and newNumber to the same value.

Kind: static method of mailboxes

Param Type Default Description oldNumber string Currently assigned box number. newNumber string New box number. Must not exist yet. newSize number | null Box size (1 - 10), if changing the size. barcode null | string A barcode value representing this mailbox, typically a sticker on the the physical box visible when delivering mail.

"},{"location":"Plugin%20API/mailboxes/#mailboxesdeletemailboxnumber-promise","title":"mailboxes.deleteMailbox(number) \u21d2 Promise","text":"

Delete a mailbox. Throws an Error if the mailbox has boxholders attached.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to delete.

"},{"location":"Plugin%20API/mailboxes/#mailboxesclosemailboxnumber-promise","title":"mailboxes.closeMailbox(number) \u21d2 Promise","text":"

Close a mailbox by removing the boxholders and marking it as vacant. Boxholder PS Form 1583 records are automatically archived per USPS regulations.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to close.

"},{"location":"Plugin%20API/mailboxes/#mailboxesmailboxexistsnumber-promiseboolean","title":"mailboxes.mailboxExists(number) \u21d2 Promise.<boolean>","text":"

Returns true if the mailbox number exists, false if it doesn't.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to check.

"},{"location":"Plugin%20API/mailboxes/#mailboxesaddorupdateboxholderboxnumber-info-promise","title":"mailboxes.addOrUpdateBoxholder(boxNumber, info) \u21d2 Promise","text":"

Modify or add a boxholder to a mailbox. info is the boxholder structure below. If the uuid given already belongs to a boxholder, their info is updated with what you supply. Otherwise, the info is added as a new boxholder.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number info Object Boxholder information.

Example

// Unless noted, all fields are strings and default to an empty string.\n{\n    name: [bizname, fname, mname, lname].filter(Boolean).join(\" \"),\n    fname: \"\", // First name\n    mname: \"\", // Middle name\n    lname: \"\", // Last name\n    email: \"\", // Email\n    phone: \"\", // Phone\n    uuid: \"\", // Customer UUID\n    bizname: \"\", // Business name\n    street1: \"\", // Street address\n    city: \"\", // City\n    state: \"\", // Two-character state\n    zipcode: \"\", // ZIP or postal code\n    country: \"\", // Two-character country code\n    primary: true // True if the primary (first) boxholder, false if an additional authorized mail recipient\n}\n

"},{"location":"Plugin%20API/mailboxes/#mailboxesremoveboxholderboxnumber-uuid-promise","title":"mailboxes.removeBoxholder(boxNumber, uuid) \u21d2 Promise","text":"

Remove a boxholder by their UUID, and archive their PS Form 1583 data per USPS regulations.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number. uuid string Boxholder UUID.

"},{"location":"Plugin%20API/mailboxes/#mailboxesget1583boxnumber-uuid-archivenumber-promiseformps1583","title":"mailboxes.get1583(boxNumber, uuid, archiveNumber) \u21d2 Promise.<FormPS1583>","text":"

Get the FormPS1583 object for a boxholder by UUID.

Kind: static method of mailboxes

Param Type Default Description boxNumber string Mailbox number. uuid string Boxholder UUID. archiveNumber boolean false If true, returns the form for a deleted boxholder from the archive.

"},{"location":"Plugin%20API/mailboxes/#mailboxesset1583boxnumber-uuid-formps1583-promise","title":"mailboxes.set1583(boxNumber, uuid, formps1583) \u21d2 Promise","text":"

Set the FormPS1583 object for a boxholder by UUID.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number. uuid string Boxholder UUID. formps1583 FormPS1583 The FormPS1583 object to use.

"},{"location":"Plugin%20API/mailboxes/#mailboxesboxnumbervalid-boolean","title":"mailboxes.boxNumberValid() \u21d2 boolean","text":"

Returns true if the mailbox number is an acceptable format, false if it isn't. Does not check if the box actually exists, merely if the number is acceptable to use as a mailbox number.

Kind: static method of mailboxes

"},{"location":"Plugin%20API/mailboxes/#mailboxesgetmailboxproducts-promisearray","title":"mailboxes.getMailboxProducts() \u21d2 Promise.<Array>","text":"

Get a list of merchandise items that are usable for mailbox renewals.

Kind: static method of mailboxes Example

[{\n    id: \"\", // Unique ID for this entry in the merchandise table\n    name: \"\", // Merch item name\n    category: \"\", // Merch item category\n    price: 0.0, // Sale price in dollars\n    cost: 0.0, // Merchandise cost in dollars (likely not used for mailboxes)\n    barcode: \"\", // Barcode/UPC (likely not used for mailboxes)\n    tax: 0.0, // Sales tax rate\n    rentaldays: 30, // Number of days this item adds to a mailbox (mutually exclusive with rentalmonths)\n    rentalmonths: 1, // Number of months (mutually exclusive with rentaldays)\n    boxsize: \"1\" // Mailbox size tier, 1-10\n}]\n

"},{"location":"Plugin%20API/pos/","title":"Pos","text":""},{"location":"Plugin%20API/pos/#pos-object","title":"pos : object","text":"

Point of Sale, transaction, and payment-related functionality.

Kind: global namespace

"},{"location":"Plugin%20API/pos/#posreceiptitem","title":"pos.ReceiptItem","text":"

Kind: static class of pos Properties

Name Type Default Description merch boolean false True if merchandise, false if shipping. barcode string Item barcode, or tracking number if merch = false. qty number 1 Item quantity. retailPrice number The calculated retail/markup price for a shipment, regardless of actual sale price. If unset, defaults to priceEach * qty. taxRate number 0 Tax rate toAddress Address Shipping destination address. fromAddress Address Shipping return address. carrier string Shipping carrier. service string Shipping service. category string Merchandise/item category. electronicReturnReceipt boolean false If true, the customer's receipt will have instructions on retrieveing the return receipt from USPS. mailboxDays number 0 Number of days this item adds to a mailbox's expiration date. mailboxMonths number 0 Number of months this item adds to a mailbox's expiration date. mailboxNumber string Mailbox number to apply mailboxDays or mailboxMonths to after checkout. setCertifiedInfo() function Set Certified Mail receipt data. setCertifiedInfo(trackingNumber, certfee, extrafees, postage, date, location, toaddress) toJSON() function Get the item as an object suitable for JSON encoding. fromJSON(json) static_function Returns a ReceiptItem created from the object returned by item.toJSON().

"},{"location":"Plugin%20API/pos/#new-receiptitemid-label-text-priceeach-quantity-cost-taxrate-taxableamount","title":"new ReceiptItem(id, label, text, priceEach, quantity, cost, taxrate, taxableAmount)","text":"

A class representing a sale item in the current transaction.

Param Type Default Description id string Unique ID number for this item (UPC code, inventory number, etc). Used to deduplicate and merge line items on the receipt. Unique items (like shipping labels) should be a unique/random ID. label string One-line item information. text string Extra multi-line item information. priceEach number Sale price per unit. quantity number Number of units. cost number Cost per unit. Used for automatic expense tracking. taxrate number 0.0 Examples: 0 (for 0%), 0.05 (for 5%), etc taxableAmount string The part of the sale price that's taxable. \"\" for default (all), \"markup\" for only taxing profit.

"},{"location":"Plugin%20API/pos/#posreceiptpayment","title":"pos.ReceiptPayment","text":"

Kind: static class of pos Properties

Name Type Description label string (readonly) The human-readable string of the payment type. id string Automatically-generated unique ID for this payment. toJSON() function Get the payment as an object suitable for JSON encoding. fromJSON(json) static_function Returns a ReceiptPayment created from the object returned by payment.toJSON().

"},{"location":"Plugin%20API/pos/#new-receiptpaymentamount-type-text","title":"new ReceiptPayment(amount, type, text)","text":"

A class representing a payment entry for the current transaction.

Param Type Description amount number amount paid type string payment type text string extra data (credit card info, etc)

"},{"location":"Plugin%20API/pos/#posaddreceiptitemitem","title":"pos.addReceiptItem(item)","text":"

Add an item (shipment, merchandise, etc) to the current transaction.

Kind: static method of pos

Param Type item ReceiptItem

"},{"location":"Plugin%20API/pos/#posaddreceiptpaymentpayment","title":"pos.addReceiptPayment(payment)","text":"

Add a payment to the current transaction/receipt.

Kind: static method of pos

Param Type payment ReceiptPayment

"},{"location":"Plugin%20API/pos/#posaddonscreenpaymentlogmsg","title":"pos.addOnscreenPaymentLog(msg)","text":"

Append a line of text to the onscreen log displayed during credit card processing. Not shown in kiosk mode.

Kind: static method of pos

Param Type Description msg string Line of text to add to the log.

"},{"location":"Plugin%20API/pos/#posgetreceiptid-string","title":"pos.getReceiptID() \u21d2 string","text":"

Get the unique alphanumeric ID for the current transaction/receipt. This is the same code printed on receipts and used in digital receipt URLs.

Kind: static method of pos

"},{"location":"Plugin%20API/pos/#posonreceiptchangef","title":"pos.onReceiptChange(f)","text":"

Specify a function to be called whenever the transaction data/receipt is changed. It is passed a single argument, a Receipt object containing the entire transaction so far.

Kind: static method of pos

Param Type f function

"},{"location":"Plugin%20API/pos/#posontransactionfinishedf","title":"~~pos.onTransactionFinished(f)~~","text":"

Deprecated

The supplied function will be called when a transaction is finished. It is passed a single argument, a Receipt object containing the entire transaction. Recommended to listen for the transactionFinished event instead.

Kind: static method of pos

Param Type f function

"},{"location":"Plugin%20API/pos/#posregistercardprocessorf","title":"pos.registerCardProcessor(f)","text":"

Register as a card payment processor.

Kind: static method of pos

Param Type Description f Object Payment processor functions

Example

global.apis.pos.registerCardProcessor({\n    name: \"Demo Card Processor\", // Shown in PostalPoint settings menu\n    init: async function () {\n        // This will run after PostalPoint launches\n        // and before any payments are processed.\n        // In some situations it might run multiple times in a session.\n    },\n    checkout: async function ({amount, capture = true}) {\n        // Charge a credit card using a card reader device.\n        // amount is in pennies (or the equivalent base unit in the local currency).\n\n        // Add a payment to the receipt with the total amount paid, card details, etc.\n        global.apis.pos.addReceiptPayment(\n            new global.apis.pos.ReceiptPayment(\n                global.apis.i18n.currencyMinorToMajor(amount),\n                \"card\", // Payment type. Accepted values are card, ach, crypto, cash,\n                // check, account, and free.  Other types will be displayed as-is.\n                \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n            )\n        );\n\n\n        // Must return boolean false if the payment failed.\n        // Otherwise it will be assumed it succeeded.\n        // If an error is encountered, handle it and return false.\n        // It's recommended to display a short \"payment failed\" error\n        // message via global.apis.alert, and outputting more details\n        // via global.apis.pos.addOnscreenPaymentLog.\n\n        // If capture is false, perform an authorization but don't capture,\n        // and return a value you can use to identify the authorization later\n        // and complete it.  The value will be passed back to finishPayment, below.\n        // This is used mainly for the self-serve kiosk mode, in case the label fails\n        // to be purchased/generated by the carrier.\n    },\n    cancelCheckout: function () {\n        // The user has requested the card transaction be canceled before it completes.\n        // Reset the terminal to its resting state, clear its screen, etc.\n    },\n    finishPayment: async function ({checkoutResponse}) {\n        // Finish a payment that was authorized but not captured\n        // because checkout() was called with capture = false.\n        // If payment was already captured and added\n        // to the receipt, just return true.\n        global.apis.pos.addReceiptPayment(\n            new global.apis.pos.ReceiptPayment(\n                global.apis.i18n.currencyMinorToMajor(amount),\n                \"card\",\n                \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\"\n            )\n        );\n    },\n    updateCartDisplay: function (receipt) {\n        // Show transaction data on the card reader display.\n        // This function is called when the \"cart\" or total changes.\n        // `receipt` is a receipt object, see docs for details.\n    },\n    checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {\n        // Same as checkout() except using a payment method already on file.\n        // customerID and paymentMethodID are provided by getSavedPaymentMethods below.\n\n        // Must return true upon success.\n        // If the payment is not successful, and you didn't throw an Error to show the user,\n        // then `return false` instead and it'll appear that the user's action to start the payment did nothing.\n        return true;\n    },\n    saveCardForOfflineUse: async function ({statusCallback, customerUUID, name,\n             company, street1, street2, city, state, zip, country, email, phone}) {\n        // Use the card reader to capture an in-person card and save it for offline use.\n        // Provided details are the customer's info, which might be empty strings except for the customerUUID.\n        // Saved card details must be tied to the customerUUID, as that's how saved cards are looked up.\n\n        // statusCallback(string, boolean) updates the progress message on the cashier's screen.\n        // If the boolean is true, the progress message is replaced with a confirmation message.\n        statusCallback(\"Saving card details...\", false);\n\n        return true; // Card saved to customer\n        // If an error occurred, you can throw it and the error\n        // message will be displayed to the cashier.\n        // Alternatively, return boolean false and display the error\n        // yourself with global.apis.alert(message, title) or something.\n    },\n    cancelSaveCardForOfflineUse: function () {\n        // Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.\n    },\n    getSavedPaymentMethods: async function ({customerUUID}) {\n        // Return all saved payment methods tied to the provided customer UUID.\n        return [{\n            customer: \"<internal string referencing the customer>\", // Passed to checkoutSavedMethod as customerID\n            customer_uuid: customerUUID,\n            id: \"<card/payment method identifier>\", // Passed to checkoutSavedMethod as paymentMethodID\n            type: \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.\n            label: \"Visa debit x1234 (exp. 12/29)\", // Label for payment method\n            label_short: \"Visa debit x1234\" // Abbreviated label for payment method\n        }];\n    },\n    deleteSavedPaymentMethod: async function ({customerUUID, customerID, paymentMethodID}) {\n        // Delete the payment method identified by paymentMethodID\n        // and tied to the PostalPoint customerUUID and the card processor customerID.\n        // If unable to delete, throw an error and the error message\n        // will be displayed to the cashier.\n    }\n});\n

"},{"location":"Plugin%20API/pos/#posregistercryptoprocessorf","title":"pos.registerCryptoProcessor(f)","text":"

Register as a cryptocurrency payment processor.

Kind: static method of pos

Param Type Description f Object Payment processor functions

Example

global.apis.pos.registerCryptoProcessor({\n    name: \"Demo Crypto\", // Shown in PostalPoint settings menu\n    init: async function () {\n        // This is run after PostalPoint starts,\n        // and before any other crypto functions are called.\n    },\n    checkout: async function ({amount}) {\n        // Run the checkout process.\n        // amount is the amount of fiat currency to collect,\n        // in pennies (or the local equivalent).\n\n        // If an error is encountered during processing,\n        //    display an error message in a dialog and return boolean false.\n        //    If this function returns anything except false or undefined,\n        //    and doesn't throw an error,\n        //    it is assumed the payment was successful.\n\n        // Adds a line of text visible to the cashier\n        global.apis.pos.addOnscreenPaymentLog(\"Getting crypto payment...\");\n\n        // Display a web page (i.e. with a payment QR code)\n        // to the customer on the customer-facing display.\n        global.apis.ui.setCustomerScreen(\"<html></html>\", \"html\");\n        global.apis.ui.setCustomerScreen(\"https://postalpoint.app\", \"raw\");\n\n        // Poll the status of the crypto transaction\n        var paymentComplete = false;\n        do {\n            await global.apis.util.delay(1000);\n            paymentComplete = true;\n        } while (paymentComplete != true);\n\n        global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                        global.apis.i18n.currencyMinorToMajor(amount),\n                        \"crypto\", // Payment type.\n                        \"Bitcoin\\n0.00001234 BTC\" // Additional text for receipt\n                        )\n                );\n        global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n        global.apis.ui.clearCustomerScreen();\n    },\n    cancelCheckout: function () {\n        // The user requested to cancel the payment.\n        // Reset things accordingly.\n        global.apis.ui.clearCustomerScreen();\n    },\n    isConfigured: function () {\n        // Is this plugin properly setup\n        // and able to process payments?\n        // If not, return false.\n        // This determines if the crypto payment method button will be shown.\n        return true;\n    }\n});\n

"},{"location":"Plugin%20API/pos/#posgetshippingsalestax-object","title":"pos.getShippingSalesTax() \u21d2 Object","text":"

Get the sales tax percentage to charge on a shipping service ReceiptItem.

Kind: static method of pos Returns: Object - {type: \"\", percent: 0.15} type is an empty string for taxing the entire price, or \"markup\" for only adding tax to the markup amount. percent is the tax percentage. A value of 0.15 means a 15% tax.

"},{"location":"Plugin%20API/print/","title":"Print","text":""},{"location":"Plugin%20API/print/#print-object","title":"print : object","text":"

Printing to connected printers

Kind: global namespace

"},{"location":"Plugin%20API/print/#printprintlabelimageimage","title":"print.printLabelImage(image)","text":"

Print a 300 DPI image on the shipping label printer, centered on a 4x6 inch label. Image is automatically scaled to 200 DPI if required by the printer.

Kind: static method of print

Param Type Description image ArrayBuffer | Buffer | Uint8Array | string | Jimp image data, as a Jimp image object, raw PNG bytes, or a URL (http/https) string. 1200x1800 or 800x1200 images are scaled to 4x6 inches. Other image sizes are assumed to be 300 DPI and are centered on the shipping label. Image orientation is rotated to match the label orientation.

"},{"location":"Plugin%20API/print/#printgetreceiptprinter-promiseobject","title":"print.getReceiptPrinter() \u21d2 Promise.<Object>","text":"

Get the receipt printer interface. See the ReceiptPrinter docs for available functions.

Kind: static method of print

"},{"location":"Plugin%20API/print/#printprintreceiptdatadata","title":"print.printReceiptData(data)","text":"

Send raw data (generated by the printer interface) to the receipt printer.

Kind: static method of print

Param Type Description data string | Uint8Array | Array.<string> | Array.<Uint8Array> Data to send to printer.

"},{"location":"Plugin%20API/print/#printimagetobitmapjimpimage-dpifrom-dpito-object","title":"print.imageToBitmap(jimpImage, [dpiFrom], [dpiTo]) \u21d2 Object","text":"

Convert a Jimp image object to 1-bit monochrome image data before sending image data to a printer interface. Optionally scales the image to a different DPI before conversion.

Kind: static method of print Returns: Object - - Example: {width: 300, height: 200, img: Uint8Array}. Pass img to drawImage on a printer interface.

Param Type Default Description jimpImage Jimp [dpiFrom] number 300 Original image DPI. [dpiTo] number 300 New image DPI."},{"location":"Plugin%20API/reports/","title":"Reports","text":""},{"location":"Plugin%20API/reports/#reports-object","title":"reports : object","text":"

Define custom reports for the user.

Kind: global namespace

"},{"location":"Plugin%20API/reports/#reportsregisterreportname-onloadstartdateenddate-date","title":"reports.registerReport(name, onload(startDate,endDate), date)","text":"

Kind: static method of reports

Param Type Description name string Report name onload(startDate,endDate) function Called when the report is loaded, with start and end Date objects. See example. date boolean If the report requires a date range be selected.

Example

global.apis.reports.registerReport(\"sample\", function (startDate, endDate) {\n\n // Note about column datatypes:\n // Use \"string\" for the datatype, except for the special cases listed here.\n // Other datatypes may be added in the future, so use \"string\"\n // unless you like unexpected behavior!\n //\n // datetime:   Column is a UNIX timestamp (in seconds).\n //             It is displayed as a formatted date and time string.\n // receiptid:  Column is a PostalPoint receipt ID number. Displayed as a link.\n //             Clicking the ID will fetch and open the receipt in a popup.\n // userid:     Column contains an employee ID number from the PostalPoint database.\n //             It is queried in the database and replaced with the employee's name,\n //             or with an empty string if the ID lookup has no results.\n // money:      Column is a number that will be formatted as currency for display.\n // percent:    Column is a percent value (as 12.3, not .123) and will be formatted\n //             with a trailing % sign and rounded to two decimal places.\n\n // Single-table report\n return {\n   table: {\n     header: [\"Column 1\", \"Column 2\"],\n     datatypes: [\"string\", \"string\"],\n     rows: [\n       [\"Row 1 Col 1\", \"Row 1 Col 2\"],\n       [\"Row 2 Col 1\", \"Row 2 Col 2\"]\n     ]\n   }\n };\n\n // Multiple-table report\n return {\n    multitable: true,\n    table: {\n      titles: [\"Report 1 Title\", \"Report 2 Title\"],\n      header: [[\"Report 1 Column 1\", \"Report 1 Column 2\"], [\"Report 2 Column 1\", ...]],\n      datatypes: [[\"string\", \"string\"], [\"string\", \"string\"]],\n      rows: [\n        [\n          [\"Report 1 Row 1 Col 1\", \"Report 1 Row 1 Col 2\"],\n          [\"Report 1 Row 2 Col 1\", \"Report 1 Row 2 Col 2\"]\n        ],\n        [\n          [\"Report 2 Row 1 Col 1\", \"Report 2 Row 1 Col 2\"],\n          [\"Report 2 Row 2 Col 1\", \"Report 2 Row 2 Col 2\"]\n        ]\n      ]\n    }\n  }\n}, true);\n

"},{"location":"Plugin%20API/settings/","title":"Settings","text":""},{"location":"Plugin%20API/settings/#settings-object","title":"settings : object","text":"

PostalPoint provides a UI for user-configurable plugin settings. See exports.config in examples/basic-demo/plugin.js for details. Settings are typically very short strings. Do not store data in settings. For data storage, see Storing Data. Non-string settings values are transparently converted to/from JSON objects. Use a unique key name prefix for your plugin to prevent key name conflicts. Reverse domain style is recommended (i.e. \"com.example.pluginname.keyname\").

Kind: global namespace

"},{"location":"Plugin%20API/settings/#settingsgetkey-defaultvalue","title":"settings.get(key, defaultValue) \u21d2 *","text":"

Get a setting.

Kind: static method of settings

Param Type Description key string Setting key/ID defaultValue * Value to return if setting has no stored value.

"},{"location":"Plugin%20API/settings/#settingssetkey-value","title":"settings.set(key, value)","text":"

Set a setting.

Kind: static method of settings

Param Type Description key string Setting key/ID value string Value to set."},{"location":"Plugin%20API/shipping/","title":"Shipping","text":""},{"location":"Plugin%20API/shipping/#shipping-object","title":"shipping : object","text":"

Add custom carrier and rates, and adjust markup.

Kind: global namespace

"},{"location":"Plugin%20API/shipping/#shippingaddress","title":"shipping.Address","text":"

Kind: static class of shipping

"},{"location":"Plugin%20API/shipping/#new-address","title":"new Address()","text":"

A class representing an address.

"},{"location":"Plugin%20API/shipping/#shippinggetzipcodezipcode-country-object","title":"shipping.getZIPCode(zipcode, country) \u21d2 Object","text":"

Get data for a ZIP Code.

Kind: static method of shipping Returns: Object - Data about the ZIP code. See example. Fields may be empty if not available. Type may be \"STANDARD\", \"UNIQUE\", \"PO BOX\", or \"MILITARY\".

Param Type Default Description zipcode string ZIP or postal code. country string \"US\" Currently only \"US\" and \"CA\" are supported.

Example

{city: \"NEW YORK\", state: \"NY\", type: \"STANDARD\"}\n

"},{"location":"Plugin%20API/shipping/#shippinggetpackagingbyidid-promiseobject","title":"shipping.getPackagingByID(id) \u21d2 Promise.<Object>","text":"

Get a parcel's packaging type from PostalPoint's internal ID for it.

Kind: static method of shipping Returns: Promise.<Object> - See examples.

Param Type id number

Example

{\n    id: 100,\n    type: \"Parcel\",\n    img: \"box.png\",\n    name: \"Box\",\n    service: \"\",\n    l: -1,\n    w: -1,\n    h: -1,\n    weight: true,\n    hazmat: true,\n    source: \"Customer\"\n}\n
Example
{\n    id: 1,\n    type: \"FlatRateEnvelope\",\n    img: \"pm-fres.png\",\n    name: \"Flat Rate Envelope\",\n    service: \"Priority\",\n    l: -2,\n    w: -2,\n    h: -2,\n    weight: false,\n    hazmat: true,\n    usps_supplied: true,\n    envelope: true,\n    source: \"USPS\",\n    skus: [\"PS00001000014\", \"PS00001000012\", \"PS00001000027\", \"PS00001000064\", \"PS00001001921\", \"PS00001035000\", \"PS00001036014\", \"PS00001128600\", \"https://qr.usps.com/epsspu?p=30\", \"https://qr.usps.com/epsspu?p=8\"]\n}\n
Example
{\n    id: 201,\n    type: \"UPSLetter\",\n    img: \"ups-env.png\",\n    name: \"Envelope\",\n    carrier: \"UPS\",\n    l: -2,\n    w: -2,\n    h: -2,\n    weight: true,\n    hazmat: true,\n    source: \"OtherCarrier\"\n}\n

"},{"location":"Plugin%20API/shipping/#shippinggetretailpricewithmarkupcost-retail-carrier-service-weightoz-packaging-promisenumber","title":"shipping.getRetailPriceWithMarkup(cost, retail, carrier, service, weightOz, packaging) \u21d2 Promise.<number>","text":"

Calculate the retail price for a shipment rate based on the configured margin settings.

Kind: static method of shipping Returns: Promise.<number> - The amount to charge the customer

Param Type Description cost number Cost of shipment to business retail number Default retail price from label provider carrier string Shipment carrier service string Shipment service weightOz number The weight of the shipment in ounces, or null if not available. packaging string An empty string if not available, or \"Letter\", \"FlatRateEnvelope\", etc.

"},{"location":"Plugin%20API/shipping/#shippinggetcarriernamecarrierid-string","title":"shipping.getCarrierName(carrierId) \u21d2 string","text":"

Converts the carrier ID string into a consistent and human-readable name.

Kind: static method of shipping

Param Type carrierId string

"},{"location":"Plugin%20API/shipping/#shippinggetservicenameserviceid-carrier-string","title":"shipping.getServiceName(serviceId, carrier) \u21d2 string","text":"

Converts the service ID string into a consistent and human-readable name. Set the carrier ID for better results.

Kind: static method of shipping

Param Type Default serviceId string carrier string \"USPS\"

"},{"location":"Plugin%20API/shipping/#shippingregisterrateendpointgetrates-purchase-idprefix","title":"shipping.registerRateEndpoint(getRates, purchase, idPrefix)","text":"

Register the plugin as a shipping rate and label provider. See the Shipping example plugin.

Kind: static method of shipping

Param Type Description getRates function A function passed a Parcel object to get rates for. Returns a Promise that resolves to an array of rate objects. purchase function A function passed a rate ID to purchase. Returns a Promise that resolves to the label information. idPrefix string A unique string that will be prefixing all rate IDs from this plugin.

Example

// getRates sample return value:\n[{\n    rateid: `${idPrefix}_${global.apis.util.uuid.v4()}`,\n    carrier: \"CarrierID\",\n    carrierName: \"Carrier Name\",\n    service: \"CARRIER_SERVICE_ID\",\n    cost_rate: 10,\n    retail_rate: 15,\n    delivery_days: 3,\n    delivery_date: null,\n    guaranteed: true,\n    serviceName: \"Service Name\",\n    color: \"green\" // Rate card color\n}]\n
Example
// purchase sample return value:\n{\n    label: labelImageToPrint,\n    labeltype: \"PNG\",\n    receiptItem: ReceiptItem,\n    tracking: \"12345678901234567890\",\n    cost: 10.0,\n    price: 15.0,\n    carrier: \"Carrier Name\",\n    service: \"Service Name\",\n    delivery_days: 3,\n    delivery_date: 1234567890, // UNIX timestamp\n    to: toAddressLines // Array of strings\n}\n

"},{"location":"Plugin%20API/shipping/#shippingregistermarkupcalculatormarkupfn","title":"shipping.registerMarkupCalculator(markupFn)","text":"

Register the plugin to modify PostalPoint's shipping markup calculation during shipment rating.

Kind: static method of shipping Throws:

Param Type Description markupFn function A function that must return either the retail price to charge for this rate, or false to opt-out of setting this particular rate.

Example

global.apis.shipping.registerMarkupCalculator(\n    // Parameters:\n    // cost:       Cost to shipper\n    // retail:     Carrier-suggested retail price\n    // suggested:  PostalPoint-suggested retail (default margin calc)\n    // carrier:    Shipping carrier name\n    // service:    Shipping service code\n    // weightOz:   The weight of the shipment in ounces, or null if not available.\n    // packaging:  An empty string if not available, or \"Letter\", \"FlatRateEnvelope\", etc. See https://docs.easypost.com/docs/parcels#predefined-package\n    // parcel:     The Parcel object for this shipment.  May be null for some rate-only requests without a shipment, such as USPS price calculations.\n    function (cost, retail, suggested, carrier, service, weightOz, packaging, parcel) {\n        if (carrier == \"USPS\") {\n            if (service == \"First-Class Mail\") {\n                // Handle First-Class Mail differently if it's a 1oz letter (i.e. Forever stamp)\n                if (weightOz <= 1 && packaging == \"Letter\") {\n                    return retail + 0.05;\n                } else {\n                    return retail + 0.25;\n                }\n            }\n            // Handle flat rate envelopes differently\n            if (global.apis.shipping.getServiceName(service, carrier) == \"Priority Mail\" && packaging == \"FlatRateEnvelope\") {\n                return retail + 1.0;\n            }\n            return suggested + 2.0; // Charge the PostalPoint-calculated amount plus $2\n        } else {\n            return cost * 2; // Charges the customer double the shipment's cost.\n        }\n    }\n);\n

"},{"location":"Plugin%20API/shipping/#shippingregisterinsuranceproviderid-name-cardtext-maxvalue-getquote-insure","title":"shipping.registerInsuranceProvider(id, name, cardText, maxValue, getQuote, insure)","text":"

Add a shipping insurance provider.

Kind: static method of shipping

Param Type Description id string | null Unique ID for the provider. Will be autogenerated if null. name string Human-readable name for the provider. Shown as the card heading on the Insurance section of the Ship screen. cardText string Text or HTML to display on the Ship screen card for this provider. maxValue number The largest number that will be accepted for the \"Insured for\" value. getQuote function Returns the cost and retail price for insuring the parcel, or a Promise that resolves into the same. See the example for details. insure function Insure the parcel and add the insurance details to the receipt. See example.

Example

async function getQuote(value, parcel, carrier, service, rateObject) {\n    // See shipping rate provider documentation for rateObject structure.\n\n    // Do math, etc\n    var cost = value / 100;\n\n    return {\n        cost: cost,\n        retail: cost * 2\n    };\n    // Or, to remove this shipping rate from the list,\n    // because the shipment/carrier/service combination\n    // is not eligible for insurance:\n    return false;\n}\n\nasync function insure(value, parcel, carrier = \"USPS\", service = \"Priority\", trackingNumber = \"94055...\") {\n    // Purchase the insurance\n    var cost = value / 100;\n    var retailPrice = cost * 2;\n    var costPrice = cost;\n\n    var receiptitem = new global.apis.pos.ReceiptItem(`sampleinsurance_${trackingNumber}`,\n        \"Sample Insurance\",\n        \"Insured for \" + global.apis.i18n.moneyString(value),\n        retailPrice, 1, costPrice, 0\n    );\n    receiptitem.merch = true;\n    receiptitem.category = \"Shipping Insurance\";\n    receiptitem.barcode = trackingNumber;\n    global.apis.pos.addReceiptItem(receiptitem);\n}\n\nglobal.apis.shipping.registerInsuranceProvider(\n     \"sampleproviderid\", \"Sample Insurance\",\n     \"Insurance coverage from Sample Insurance. $1 per $100 of value.\",\n     5000, getQuote, insure);\n

"},{"location":"Plugin%20API/storage/","title":"Storage","text":""},{"location":"Plugin%20API/storage/#storage-object","title":"storage : object","text":"

Get and set data.

Kind: global namespace

"},{"location":"Plugin%20API/storage/#storagegetsmallkey-defaultvalue","title":"storage.getSmall(key, defaultValue) \u21d2 *","text":"

Get a value from the small data storage, using localStorage or a similar mechanism (may change in the future).

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetsmallkey-value","title":"storage.setSmall(key, value)","text":"

Set a value in the small data storage, using localStorage or a similar mechanism (may change in the future).

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store.

"},{"location":"Plugin%20API/storage/#storagegetbigkey-defaultvalue","title":"storage.getBig(key, defaultValue)","text":"

Get a value in the large data storage. Unserialized from a JSON file on disk.

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetbigkey-value","title":"storage.setBig(key, value)","text":"

Set a value in the large data storage. Serialized to JSON and stored on disk as a file.

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store.

"},{"location":"Plugin%20API/storage/#storagegetdbkey-defaultvalue-promise","title":"storage.getDB(key, defaultValue) \u21d2 Promise.<*>","text":"

Get a value from the database storage. Unlike other storage types, values in the database are available on all PostalPoint installations in a single location.

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetdbkey-value-promise","title":"storage.setDB(key, value) \u21d2 Promise","text":"

Set a value in the database storage. Non-string values are serialized to JSON. Unlike other storage types, values in the database are available on all PostalPoint installations in a single location.

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store."},{"location":"Plugin%20API/ui/","title":"Ui","text":""},{"location":"Plugin%20API/ui/#ui-object","title":"ui : object","text":"

Interact with and modify the PostalPoint user interface.

Kind: global namespace

"},{"location":"Plugin%20API/ui/#uiaddtoolspagepage-title-id-description-cardtitle-icon-type","title":"ui.addToolsPage(page, title, id, description, cardTitle, icon, type)","text":"

Add a page to the Tools screen. If type is set to \"function\", the page argument will be run as a function and will not be expected to return a page component. Framework7 Page Component documentation

Kind: static method of ui

Param Type Default Description page string | function Page content as a Framework7 component page. If page is a string ending in .f7 it is treated as a file path and the page content will be loaded from disk. If page is any other string, it is treated as the page content. If page is a function, it will be called and must return the page content (unless type is set to \"function\", see examples) title string Page title. id string Page ID. Make it unique, or pass an empty string to be assigned a random ID. description string Description of this tool for its card on the Tools screen. cardTitle string Title of the card for this page on the Tools screen. icon string FontAwesome icon class, for example, \"fa-solid fa-globe\". FontAwesome Pro solid, regular, light, and duotone icons are available. type string \"page\" What kind of data is supplied by page: \"page\" or \"function\".

Example

// Full plugin that displays an alert when the card is clicked on the Tools page\nexports.init = function () {\n    global.apis.ui.addToolsPage(\n                                 displayMessage,\n                                 \"click me\",\n                                 \"clickmecard\",\n                                 \"Click here to see a message\",\n                                 \"Click This Card\",\n                                 \"fa-solid fa-arrow-pointer\",\n                                 \"function\"\n                               );\n}\n\nfunction displayMessage() {\n    global.apis.alert(\"Card clicked\");\n}\n
Example
// Open a dynamically-generated page\nfunction rollDice() {\n    var randomNumber = Math.round(Math.random() * 6) + 1;\n    return `<div class=\"page\">\n        <div class=\"navbar\">\n            <div class=\"navbar-bg\"></div>\n            <div class=\"navbar-inner\">\n                <a href=\"#\" class=\"link back\">\n                    <i class=\"icon icon-back\"></i>\n                </a>\n                <div class=\"title\">Random Number</div>\n            </div>\n        </div>\n        <div class=\"page-content\">\n            <div class=\"block\">\n                <h1>You rolled ${randomNumber}</h1>\n            </div>\n        </div>\n    </div>`;\n}\nglobal.apis.ui.addToolsPage(\n                             rollDice,\n                             \"Random\",\n                             \"randomnumbercard\",\n                             \"Click here for a random number\",\n                             \"Random Number\",\n                             \"fa-regular fa-dice\",\n                             \"page\"\n                           );\n
Example
// Open a page from a file.\n// See https://framework7.io/docs/router-component#single-file-component\nglobal.apis.ui.addToolsPage(\n                             global.apis.getPluginFolder(\"example-plugin\") + \"/page.f7\",\n                             \"Page\",\n                             \"filepage\",\n                             \"Open page.f7 from the plugin installation folder\",\n                             \"Open Custom Page\",\n                             \"fa-regular fa-file\",\n                             \"page\"\n                           );\n

"},{"location":"Plugin%20API/ui/#uiaddhometabcontent-title-icon-id-undefined","title":"ui.addHomeTab(content, title, icon, id) \u21d2 undefined","text":"

Add a custom tab to the PostalPoint home screen. Works almost the same as addToolsPage.

Kind: static method of ui

Param Type Description content string | function Tab content. It is rendered/processed when the user navigates to the Home screen and clicks the tab; if the user navigates to a different screen and back to Home, it will be re-rendered. If content is a string ending in .f7 it is treated as a file path and the content will be loaded from disk. If content is any other string, it is treated as the content. If content is a function, it will be called and must return the content. title string Tab title. Keep it short; depending on screen size and tab count, you have as little as 150px of space. icon string FontAwesome icon displayed above the tab title. id string Tab ID. Make it unique, or pass an empty string to be assigned a random ID. If addHomeTab is called with a tab ID that is already registered, it will be overwritten.

Example

global.apis.ui.addHomeTab(\"<div class='block'>Hello</div>\", \"Hello Tab\", \"fa-duotone fa-hand-wave\", \"hellotab\");\n
Example
function renderTab() {\n    return \"<template><div><h1>${hellovar}</h1></div></template><script>export default (props, {$on, $update, $f7}) => {var hellovar = 'hello world'; return $render;}</script>\";\n}\nglobal.apis.ui.addHomeTab(renderTab, \"Hello Template\", \"fa-duotone fa-file-code\", \"hellotemplatetab\");\n

"},{"location":"Plugin%20API/ui/#uishowprogressspinnertitle-text-subtitle-undefined","title":"ui.showProgressSpinner(title, text, subtitle) \u21d2 undefined","text":"

Show a notification with a loading icon.

Kind: static method of ui

Param Type Description title string The message to show on the spinner. text string Optional. Body text on the spinner. subtitle string Optional. Sub-heading under the title.

"},{"location":"Plugin%20API/ui/#uihideprogressspinner","title":"ui.hideProgressSpinner()","text":"

Close the notification opened by showProgressSpinner.

Kind: static method of ui

"},{"location":"Plugin%20API/ui/#uiopensystemwebbrowserurl","title":"ui.openSystemWebBrowser(url)","text":"

Open the native OS default browser to the URL given.

Kind: static method of ui

Param Type url string

"},{"location":"Plugin%20API/ui/#uiopeninternalwebbrowserurl","title":"ui.openInternalWebBrowser(url)","text":"

Open a web browser inside PostalPoint. The browser has forward/back/close buttons.

Kind: static method of ui

Param Type url string

Example

global.apis.ui.openInternalWebBrowser(\"https://postalpoint.app\");\nglobal.apis.eventbus.on(\"browserNavigate\", function (newUrl) {\n    global.apis.alert(`New URL: ${newUrl}`, \"Browser Navigated\");\n    if (newUrl == \"https://closeme.com\") {\n        global.apis.eventbus.emit(\"browserCloseRequest\");\n    }\n});\n

"},{"location":"Plugin%20API/ui/#uiclearcustomerscreen","title":"ui.clearCustomerScreen()","text":"

Clear any custom content on the customer-facing display, defaulting back to blank/receipt/shipping rates, as applicable.

Kind: static method of ui

"},{"location":"Plugin%20API/ui/#uisetcustomerscreencontent-type-displayincard-cardsize-displaystatusbar","title":"ui.setCustomerScreen(content, type, displayInCard, cardSize, displayStatusBar)","text":"

Render content on the customer-facing display. Encodes content as a data URI (example: data:text/html;charset=utf-8,${content}) and renders on the customer-facing display. If type is html, renders the string as HTML. If type is pdf, displays a PDF viewer. If type is raw, functions like setting an iframe's src to content. All other type values are rendered as text/plain. To display the iframe in a card centered on the screen, set displayInCard to true and pass the desired dimensions (w, h) of the card in px. If the requested size is larger than the available screen space, the card will instead fill the available space. Warning: Do not load third-party websites, this is a security risk. Wrap it in a tag if you need to display one.

Kind: static method of ui

Param Type Default Description content string Page content. type string \"html\" Format of the content. One of \"html\", \"pdf\", \"raw\", \"body\", or \"text\". displayInCard boolean false Set to true to wrap the content in a card UI. cardSize Array.<number> [300,300 Size of the card UI if displayInCard == true. displayStatusBar boolean true Whether the bar on the bottom of the screen should be visible, containing the store logo and scale weight.

Example

// How content is converted by PostalPoint before display:\nif (type == \"html\") {\n    customerScreenContent = `data:text/html;charset=utf-8,${content}`;\n} else if (type == \"pdf\") {\n    customerScreenContent = `data:application/pdf,${content}`;\n} else if (type == \"raw\") {\n    global.customerScreenContent = `${content}`;\n} else if (type == \"body\") {\n    customerScreenContent = `data:text/html;charset=utf-8,<!DOCTYPE html>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n        html, body {margin: 0; padding: 0; font-family: Roboto, Ubuntu, Arial, sans-serif;}\n        h1, h2, h3 {margin: 0;}\n    </style>\n    <div id=\"maindiv\">${content}</div>`;\n} else {\n    customerScreenContent = `data:text/plain;charset=utf-8,${content}`;\n}\n

"},{"location":"Plugin%20API/ui/#uicollectsignaturefromcustomerscreentitle-terms-termstype","title":"ui.collectSignatureFromCustomerScreen(title, terms, termstype)","text":"

Show a signature pad on the customer-facing display. When the customer indicates the signature is finished, the customerSignatureCollected event is emitted with the data {\"svg\": \"data:image/svg+xml;base64,...\", \"png\": \"data:image/png;base64,...\"}

Kind: static method of ui

Param Type Default Description title string null Display some title/header text on the customer screen near the signature box. Not shown if terms are set. terms string | boolean false Set to a string to display terms and conditions or other text content next to the signature pad. termstype string \"body\" \"html\", \"pdf\", \"raw\", or \"body\". See setCustomerScreen()

Example

global.apis.ui.collectSignatureFromCustomerScreen(\"\", \"<p>By signing, you agree to pay us lots of money</p>\", \"body\");\nglobal.apis.eventbus.on(\"customerSignatureCollected\", function (sigdata) {\n    const pngDataURL = sigdata.png;\n    const svgDataURL = sigdata.svg;\n});\n

"},{"location":"Plugin%20API/ui/#uicancelsignaturecollection","title":"ui.cancelSignatureCollection()","text":"

Cancels customer signature collection and returns the customer-facing display to normal operation.

Kind: static method of ui Example

global.apis.ui.cancelSignatureCollection();\n

"},{"location":"Plugin%20API/ui/#uiclearsignaturepad","title":"ui.clearSignaturePad()","text":"

Erase the signature drawn on the customer-facing display. Note that the customer is also provided a button to do this.

Kind: static method of ui Example

global.apis.ui.clearSignaturePad();\n

"},{"location":"Plugin%20API/ui/#uigetcustomerdisplayinfo-object","title":"ui.getCustomerDisplayInfo() \u21d2 Object","text":"

Describes if the customer-facing display is currently enabled, and if it supports customer touch interaction.

Kind: static method of ui Returns: Object - {\"enabled\": true, \"touch\": true} Example

var info = global.apis.ui.getCustomerDisplayInfo();\n

"},{"location":"Plugin%20API/user/","title":"User","text":""},{"location":"Plugin%20API/user/#user-object","title":"user : object","text":"

Access data about employees.

Kind: global namespace

"},{"location":"Plugin%20API/user/#useruser","title":"user.User","text":"

Kind: static class of user Properties

Name Type Description id number name string pass string barcode string enabled boolean hasPassword() function Returns true if the user has a password set, else false. checkPassword(string) function Returns true if the provided password matches the user's password, or if there is no password set. icon(number) function Returns a SVG data URI with a procedurally-generated icon for the user. Size defaults to 50px if not specified.

"},{"location":"Plugin%20API/user/#new-userid-name-password-barcode-enabled","title":"new User(id, name, password, barcode, enabled)","text":"

A User object.

Param Type id number name string password string barcode string enabled boolean

"},{"location":"Plugin%20API/user/#usergetuser-user","title":"user.getUser() \u21d2 User","text":"

Get the user currently logged in.

Kind: static method of user

"},{"location":"Plugin%20API/user/#usergetuserid-number","title":"user.getUserID() \u21d2 number","text":"

Get the current user's ID number.

Kind: static method of user

"},{"location":"Plugin%20API/user/#usergetuserbyid-promiseuser","title":"user.getUserByID() \u21d2 Promise.<User>","text":"

Look up the User for an ID number.

Kind: static method of user

"},{"location":"Plugin%20API/user/#userlistusersmanagermode-promisearrayuser","title":"user.listUsers([managerMode]) \u21d2 Promise.<Array.<User>>","text":"

Get a list of all users in the system.

Kind: static method of user

Param Type Default Description [managerMode] boolean false If false, list only active/enabled users, and if no users, return a default user account (user ID -1). If true, return all users in the database, and don't return a default account if the list is empty (return an empty list instead)."},{"location":"Plugin%20API/util/","title":"Util","text":""},{"location":"Plugin%20API/util/#util-object","title":"util : object","text":"

Various utility functions: HTTP, time/date, barcode creation, clipboard, etc.

Kind: global namespace

"},{"location":"Plugin%20API/util/#utiluuid-object","title":"util.uuid : object","text":"

Unique ID generators.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#uuidv4-string","title":"uuid.v4() \u21d2 string","text":"

Generate a UUID string

Kind: static method of uuid Returns: string - UUID v4 with dashes: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

"},{"location":"Plugin%20API/util/#uuidshortlength-string","title":"uuid.short([length]) \u21d2 string","text":"

Generate a short random alphanumeric string.

Kind: static method of uuid Returns: string - A string of length length, from the character set \"acdefhjkmnpqrtuvwxy0123456789\".

Param Type Default Description [length] number 16 String character count.

"},{"location":"Plugin%20API/util/#utilhttp-object","title":"util.http : object","text":"

HTTP requests and webhooks.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#httpwebhook-object","title":"http.webhook : object","text":"

Use webhooks via a PostalPoint cloud relay service.

Kind: static namespace of http

"},{"location":"Plugin%20API/util/#webhookgeturlsourcename-promisestring","title":"webhook.geturl(sourcename) \u21d2 Promise.<string>","text":"

geturl - Returns a public URL that can be used as a webhook target/endpoint for third-party integrations.

Kind: static method of webhook Returns: Promise.<string> - A URL for the webhook.

Param Type Description sourcename string Unique identifier for the webhook

"},{"location":"Plugin%20API/util/#webhookpollsourcename-promisearrayobject","title":"webhook.poll(sourcename) \u21d2 Promise.<Array.<Object>>","text":"

poll - Returns an array of webhook payloads received by the webhook identified by sourcename.

Kind: static method of webhook Returns: Promise.<Array.<Object>> - Payloads as received by the webhook relay service.

Param Type Description sourcename string Unique identifier for the webhook

Example

[\n     {\n         // Unique ID. Used for ack(webhookid).\n         id: 123,\n\n         // UNIX timestamp (in seconds) of when the data was received by the webhook URL.\n         timestamp: 1234567890,\n\n         // Source name set in geturl()\n         source: \"sourcename\",\n\n         // JSON string of all the HTTP headers sent to the webhook URL.\n         headers: \"{'Content-Type': 'application/json'}\",\n\n         // Entire HTTP request body sent to the webhook URL.\n         body: \"\"\n     }\n]\n

"},{"location":"Plugin%20API/util/#webhookackwebhookid","title":"webhook.ack(webhookid)","text":"

Acknowledge receipt of a webhook payload, deleting it from the relay server. Webhook payload is only queued for deletion, so polling may still return it for a short time.

Kind: static method of webhook

Param Type Description webhookid number Numeric unique ID received with the payload. See poll.

"},{"location":"Plugin%20API/util/#httpposturl-data-responsetype-headers-method-continueonbadstatuscode-timeoutseconds-promisestringblobarraybufferobject","title":"http.post(url, data, [responseType], [headers], [method], [continueOnBadStatusCode], [timeoutSeconds]) \u21d2 Promise.<(string|Blob|ArrayBuffer|Object)>","text":"

post - Fetch a HTTP POST request.

Kind: static method of http Returns: Promise.<(string|Blob|ArrayBuffer|Object)> - The server response body. See responseType parameter.

Param Type Default Description url string data Object.<string, string> POST data key/value list [responseType] string \"text\" \"text\", \"blob\", \"buffer\", or \"json\" [headers] Object.<string, string> HTTP headers to send. Defaults to {\"Content-Type\": \"application/json\"}. [method] string \"POST\" [continueOnBadStatusCode] boolean false If false, throws an Error when the HTTP response code is not 2XX. If true, ignores the response code and proceeds as normal. [timeoutSeconds] number 15 Aborts the request (timeout) after this many seconds.

"},{"location":"Plugin%20API/util/#httpfetchurl-responsetype-timeoutseconds-promisestringblobarraybufferobject","title":"http.fetch(url, [responseType], [timeoutSeconds]) \u21d2 Promise.<(string|Blob|ArrayBuffer|Object)>","text":"

fetch - Fetch a HTTP GET request.

Kind: static method of http Returns: Promise.<(string|Blob|ArrayBuffer|Object)> - The server response body. See responseType parameter.

Param Type Default Description url string [responseType] string \"text\" \"text\", \"blob\", \"buffer\", or \"json\" [timeoutSeconds] number 15 Aborts the request (timeout) after this many seconds.

"},{"location":"Plugin%20API/util/#utilstring-object","title":"util.string : object","text":"

String manipulation functions.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#stringsplitinput-separator-limit-arraystring","title":"string.split(input, separator, [limit]) \u21d2 Array.<string>","text":"

Split a string with a separator regex.

Kind: static method of string

Param Type Description input string Input string separator string Passed to new RegExp(separator, 'g') [limit] number Maximum number of splits to perform

"},{"location":"Plugin%20API/util/#stringchunkinput-chunksize-arraystring","title":"string.chunk(input, chunksize) \u21d2 Array.<string>","text":"

Split a string into chunks of length chunksize.

Kind: static method of string

Param Type Description input string Input string chunksize string Number of characters per chunk

"},{"location":"Plugin%20API/util/#utiltime-object","title":"util.time : object","text":"

Date and time functions

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#timenow-number","title":"time.now() \u21d2 number","text":"

Get the current UNIX timestamp in seconds.

Kind: static method of time

"},{"location":"Plugin%20API/util/#timediffcompareto-number","title":"time.diff(compareto) \u21d2 number","text":"

Get the number of seconds between now and the given Date or UNIX timestamp in seconds.

Kind: static method of time

Param Type compareto number | Date

"},{"location":"Plugin%20API/util/#timestrtotimestr-number","title":"time.strtotime(str) \u21d2 number","text":"

Parse a string date and return UNIX timestamp (in seconds).

Kind: static method of time

Param Type str string

"},{"location":"Plugin%20API/util/#timeformatformat-timestamp-string","title":"time.format(format, [timestamp]) \u21d2 string","text":"

Take a Date or UNIX timestamp in seconds and format it to a string. Mostly compatible with the PHP date format codes.

Kind: static method of time

Param Type Default Description format string \"Y-m-d H:i:s\", etc [timestamp] number | Date now()

"},{"location":"Plugin%20API/util/#timetodatestringtimestamp-string","title":"time.toDateString(timestamp) \u21d2 string","text":"

Format a UNIX timestamp (in seconds) as a localized date string.

Kind: static method of time

Param Type timestamp number

"},{"location":"Plugin%20API/util/#timetotimestringtimestamp-string","title":"time.toTimeString(timestamp) \u21d2 string","text":"

Format a UNIX timestamp (in seconds) as a localized time string.

Kind: static method of time

Param Type timestamp number

"},{"location":"Plugin%20API/util/#utilclipboard-object","title":"util.clipboard : object","text":"

OS clipboard

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#clipboardcopytext-shownotification-promiseboolean","title":"clipboard.copy(text, [showNotification]) \u21d2 Promise.<boolean>","text":"

Copy a string to the system clipboard.

Kind: static method of clipboard Returns: Promise.<boolean> - True if the copy succeeded, else false.

Param Type Default Description text string [showNotification] boolean false If true, a \"Copied\" notification will appear onscreen briefly.

"},{"location":"Plugin%20API/util/#utilbarcode-object","title":"util.barcode : object","text":"

Barcode image generation functions.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#barcodegetbufferdata-type-height-scale-includetext-promisebuffer","title":"barcode.getBuffer(data, [type], [height], [scale], [includetext]) \u21d2 Promise.<Buffer>","text":"

Get a PNG image buffer of a barcode. Uses library \"bwip-js\".

Kind: static method of barcode Returns: Promise.<Buffer> - PNG data for the barcode.

Param Type Default Description data string [type] string \"\\\"code128\\\"\" [height] number 10 [scale] number 2 [includetext] boolean false Set true to render the barcode's content as text below the code.

"},{"location":"Plugin%20API/util/#barcodegetbase64data-type-height-scale-includetext-promisestring","title":"barcode.getBase64(data, [type], [height], [scale], [includetext]) \u21d2 Promise.<string>","text":"

Get a PNG image of a barcode as a base64 data URI. Uses library \"bwip-js\".

Kind: static method of barcode Returns: Promise.<string> - \"data:image/png;base64,...\"

Param Type Default Description data string [type] string \"\\\"code128\\\"\" [height] number 10 [scale] number 2 [includetext] boolean false Set true to render the barcode's content as text below the code.

"},{"location":"Plugin%20API/util/#utilgeography-object","title":"util.geography : object","text":"

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#geographyisotocountrynameiso-string","title":"geography.isoToCountryName(iso) \u21d2 string","text":"

Get a human-readable country name from an ISO country code.

Kind: static method of geography

Param Type Description iso string | number 2 or 3 letter country code, or numeric country code.

"},{"location":"Plugin%20API/util/#utilobjectequalsa-b-boolean","title":"util.objectEquals(a, b) \u21d2 boolean","text":"

Compare two objects for equality. See https://stackoverflow.com/a/16788517

Kind: static method of util Returns: boolean - True if equal, else false.

Param Type a * b *

"},{"location":"Plugin%20API/util/#utildelayms-promise","title":"util.delay([ms]) \u21d2 Promise","text":"

Pause execution for some amount of time in an async function, i.e., returns a Promise that resolves in some number of milliseconds.

Kind: static method of util

Param Type Default Description [ms] number 1000 Number of milliseconds to pause."}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Overview","text":"

PostalPoint\u00ae supports JavaScript plugin extensions. Plugins can hook into PostalPoint to add features and integrations.

"},{"location":"#what-plugins-can-do","title":"What plugins can do","text":""},{"location":"#postalpoint-devtools-and-testing-builds","title":"PostalPoint DevTools and Testing Builds","text":"

The PostalPoint build server creates installers from the latest prerelease changes. It is not recommended to use these builds for production purposes, but they contain the latest changes to plugin APIs.

For Windows developers, you'll want to download a \"postalpoint-retail-sdk_x.xx.exe\" installer, as it contains the Chromium DevTools.

To enable DevTools on Linux, simply run sudo apt install nw.js-sdk and restart PostalPoint.

To access the DevTools, press F12 or right-click anywhere inside PostalPoint and click Inspect. Depending on various factors, some plugin console output may go to the \"background page\"; right-click and click \"Inspect background page\" to view that console.

"},{"location":"#plugin-package-structure","title":"Plugin Package Structure","text":"

A plugin is distributed as a simple ZIP file, containing a folder. The folder then has at least one file, named plugin.js. The exports.init function in plugin.js is executed when PostalPoint launches, allowing the plugin to request involvement with various events in PostalPoint.

PostalPoint installs plugin packages by unzipping their contents into a plugins folder. Plugins are uninstalled by deleting their folder.

"},{"location":"#postalpoint-plugin-api","title":"PostalPoint Plugin API","text":"

The PostalPoint plugin API is a globally-available object named global.apis. It contains many useful functions for integrating with PostalPoint. All the APIs listed under the Plugin API section must be prefixed with global.apis. in order to work.

"},{"location":"#minimal-plugin-code","title":"Minimal Plugin Code","text":"plugin-name/plugin.js
exports.init = function () {\n    global.apis.alert(\"This message appears when PostalPoint launches.\", \"Hello!\");\n};\n

Yes, the smallest plugin really is just two lines of code, and accessing PostalPoint features really is that easy.

"},{"location":"#plugin-metadata-file","title":"Plugin Metadata File","text":"

While not strictly required, a package.json is encouraged, and allows specifying the plugin's display name, PostalPoint version compatibility, and other metadata.

Sample:

{\n    \"name\": \"plugin-id-here\",\n    \"main\": \"plugin.js\",\n    \"description\": \"Human-readable description of the plugin\",\n    \"version\": \"1.0.0\",\n    \"author\": \"Your Name\",\n    \"license\": \"Code license name\",\n    \"postalpoint\": {\n        \"pluginname\": \"Display Name for Plugin\",\n        \"minVersion\": \"000034\",\n        \"maxVersion\": \"001000\"\n    }\n}\n

PostalPoint version codes are MMMnnn where MMM is the major version and nnn is the minor version, zero-padded. So version 0.35 is \"000035\", and 1.23 is \"001023\".

"},{"location":"Docs/Address/","title":"Address object","text":"
export default class Address {\n    constructor(uuid = \"\", name = \"\", company = \"\", street1 = \"\", street2 = \"\", zip = \"\", city = \"\", state = \"\", country = \"\", phone = \"\", email = \"\", taxid = \"\") {\n        this.uuid = uuid;\n        this.name = name;\n        this.company = company;\n        this.street1 = street1;\n        this.street2 = street2;\n        this.zip = zip;\n        this.city = city;\n        this.state = state;\n        this.country = country;\n        this.phone = phone;\n        this.email = email;\n        this.taxid = taxid;\n        this.residential = null;\n    }\n\n    static fromObject(address) {\n        if (address instanceof Address) {\n            return address;\n        }\n        var a = new Address(address.uuid ?? \"\", address.name, address.company, address.street1,\n                address.street2, address.zip, address.city, address.state, address.country,\n                address.phone, address.email, address.taxid);\n        return a;\n    }\n\n    toStringArray(expandCountry = false) {\n        var citystatezipLine = [this.city, this.state, this.zip].filter(Boolean);\n        var country = this.country == defaultCountryCode() ? \"\" : this.country;\n        if (expandCountry && country != \"\") {\n            country = getCountryNameForISO(country);\n        }\n        return [this.name, this.company, this.street1, this.street2, `${citystatezipLine.join(\" \")}`, country, (this.taxid ? \"Tax ID \" + this.taxid : \"\")].filter(Boolean);\n    }\n\n    /**\n     * Test if the address provided is the same as this address.\n     */\n    equals(address, checkUUID = false) {\n        if (\n                (checkUUID ? this.uuid == address.uuid : true)\n                && this.name == address.name\n                && this.company == address.company\n                && this.street1 == address.street1\n                && this.street2 == address.street2\n                && this.city == address.city\n                && this.state == address.state\n                && this.zip == address.zip\n                && this.country == address.country\n                && this.taxid == address.taxid) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Test if an address is the same delivery point as this address.\n     */\n    dpEquals(address) {\n        if (\n                this.street1 == address.street1\n                && this.street2 == address.street2\n                && this.city == address.city\n                && this.state == address.state\n                && this.zip == address.zip\n                && this.country == address.country) {\n            return true;\n        }\n        return false;\n    }\n}\n
"},{"location":"Docs/Carrier_Service/","title":"getCarrierName and getServiceName","text":"

This is the source code for global.apis.shipping.getCarrierName and global.apis.shipping.getServiceName.

"},{"location":"Docs/Carrier_Service/#getcarriername","title":"getCarrierName","text":"
export function getCarrierName(carrier) {\n    switch (carrier) {\n        case \"USPS\":\n        case \"USPSReturns\":\n        case \"usps\":\n            return \"USPS\";\n        case \"UPS\":\n        case \"UPSDAP\":\n        case \"ups\":\n            return \"UPS\";\n        case \"FedExDefault\":\n        case \"FedExSmartPost\":\n        case \"fedex\":\n            return \"FedEx\";\n        case \"DHLExpress\":\n        case \"dhl_express\":\n            return \"DHL Express\";\n        case \"DhlEcs\":\n            return \"DHL eCommerce\";\n        case \"SpeedeeAccount\":\n            return \"Spee-Dee\";\n        case \"globalpost\":\n            return \"GlobalPost\";\n        case \"AccurateAccount\":\n            return \"Accurate Courier Express\";\n        case \"ApcAccount\":\n            return \"APC\";\n        case \"AsendiaUsaAccount\":\n            return \"Asendia\";\n        case \"BetterTrucksAccount\":\n            return \"Better Trucks\";\n        case \"BluestreakAccount\":\n            return \"Blue Streak\";\n        case \"CanadaPostAccount\":\n        case \"CanadaPost\":\n            return \"Canada Post\";\n        case \"GsoAccount\":\n            return \"GLS\";\n        case \"LsoAccount\":\n            return \"LSO\";\n        case \"OntracV3Account\":\n            return \"OnTrac\";\n        case \"USAExportPBA\":\n            return \"Asendia\";\n        case \"FirstChoiceAccount\":\n            return \"1st Choice Delivery\";\n        case \"PurolatorAccount\":\n            return \"Purolator\";\n        case \"CanparAccount\":\n            return \"Canpar\";\n        case \"MaerskAccount\":\n            return \"Maersk\";\n    }\n    if (carrier.endsWith(\"Account\")) {\n        // A lot of EasyPost carriers are the carrier name and the word Account\n        return carrier.replace(\"Account\", \"\");\n    }\n    return carrier;\n}\n
"},{"location":"Docs/Carrier_Service/#getservicename","title":"getServiceName","text":"
export function getServiceName(serviceId, carrierId = \"USPS\") {\n    var carrier = getCarrierName(carrierId);\n    switch (serviceId) {\n        // Ambiguous\n        case \"\":\n            // Used internally if no service ID defined by a plugin or whatever\n            return \"\";\n        case \"Ground\":\n            return \"Ground\";\n        case \"Express\":\n            if (carrier == \"UPS\") {\n                return \"Worldwide Express\";\n            } else if (carrier == \"USPS\") {\n                return \"Priority Mail Express\";\n            } else {\n                return \"Express\";\n            }\n        case \"Priority\":\n            if (carrier == \"USPS\") {\n                return \"Priority Mail\";\n            }\n            return \"Priority\";\n\n\n            // USPS\n        case \"First\":\n        case \"usps_first_class_mail\":\n        case \"First-Class Mail\":\n            return \"First-Class Mail\";\n        case \"FirstClassMailInternational\":\n        case \"usps_first_class_mail_international\":\n        case \"First-Class Mail International\":\n            return \"First-Class Mail International\";\n        case \"GroundAdvantage\":\n        case \"usps_ground_advantage\":\n        case \"Ground Advantage\":\n            return \"Ground Advantage\";\n        case \"MediaMail\":\n        case \"usps_media_mail\":\n        case \"Media Mail\":\n            return \"Media Mail\";\n        case \"LibraryMail\":\n        case \"usps_library_mail\":\n        case \"Library Mail\":\n            return \"Library Mail\";\n        case \"usps_priority_mail\":\n        case \"Priority Mail\":\n            return \"Priority Mail\";\n        case \"PriorityMailInternational\":\n        case \"usps_priority_mail_international\":\n        case \"Priority Mail International\":\n            return \"Priority Mail International\";\n        case \"usps_priority_mail_express\":\n        case \"Priority Mail Express\":\n            return \"Priority Mail Express\";\n        case \"ExpressMailInternational\":\n        case \"usps_priority_mail_express_international\":\n        case \"Priority Mail Express International\":\n            return \"Priority Mail Express International\";\n        case \"FirstClassPackageInternationalService\":\n        case \"First-Class Pkg International\":\n            return \"First-Class Pkg International\";\n\n\n            // UPS\n        case \"UPSWorldwideExpress\":\n        case \"ups_worldwide_express\":\n            return \"Worldwide Express\";\n        case \"ExpressPlus\":\n        case \"UPSExpressPlus\":\n        case \"ups_worldwide_express_plus\":\n            return \"Worldwide Express Plus\";\n        case \"Expedited\":\n        case \"UPSWorldwideExpedited\":\n        case \"ups_worldwide_expedited\":\n            return \"Worldwide Expedited\";\n        case \"UPSWorldwideEconomyDDU\":\n            return \"Worldwide Economy DDU\";\n        case \"NextDayAir\":\n        case \"ups_next_day_air\":\n            return \"Next Day Air\";\n        case \"NextDayAirSaver\":\n        case \"ups_next_day_air_saver\":\n            return \"Next Day Air Saver\";\n        case \"NextDayAirEarlyAM\":\n        case \"ups_next_day_air_early\":\n            return \"Next Day Air A.M.\";\n        case \"2ndDayAir\":\n        case \"ups_2nd_day_air\":\n            return \"2nd Day Air\";\n        case \"2ndDayAirAM\":\n        case \"ups_2nd_day_air_am\":\n            return \"2nd Day Air A.M.\";\n        case \"3DaySelect\":\n        case \"ups_3_day_select\":\n            return \"3-Day Select\";\n        case \"ups_ground\":\n            return \"Ground\";\n        case \"UPSGroundsaverGreaterThan1lb\":\n            return \"Ground Saver\";\n        case \"UPSStandard\":\n        case \"ups_standard\":\n            return \"Standard\";\n        case \"UPSSaver\":\n        case \"ups_worldwide_saver\":\n            return \"Worldwide Saver\";\n\n\n            // FedEx\n        case \"FEDEX_GROUND\":\n        case \"fedex_ground\":\n            return \"Ground\";\n        case \"FEDEX_2_DAY\":\n        case \"fedex_2day\":\n            return \"2 Day\";\n        case \"FEDEX_2_DAY_AM\":\n        case \"fedex_2day_am\":\n            return \"2 Day AM\";\n        case \"FEDEX_EXPRESS_SAVER\":\n        case \"fedex_express_saver\":\n            return \"Express Saver\";\n        case \"STANDARD_OVERNIGHT\":\n        case \"fedex_standard_overnight\":\n            return \"Standard Overnight\";\n        case \"FIRST_OVERNIGHT\":\n        case \"fedex_first_overnight\":\n            return \"First Overnight\";\n        case \"PRIORITY_OVERNIGHT\":\n        case \"fedex_priority_overnight\":\n            return \"Priority Overnight\";\n        case \"INTERNATIONAL_ECONOMY\":\n        case \"fedex_international_economy\":\n            return \"International Economy\";\n        case \"INTERNATIONAL_FIRST\":\n        case \"fedex_international_first\":\n            return \"International First\";\n        case \"FEDEX_INTERNATIONAL_PRIORITY\":\n        case \"INTERNATIONAL_PRIORITY\":\n        case \"fedex_international_priority\":\n            return \"International Priority\";\n        case \"FEDEX_INTERNATIONAL_PRIORITY_EXPRESS\":\n        case \"INTERNATIONAL_PRIORITY_EXPRESS\":\n            return \"International Priority Express\";\n        case \"FEDEX_INTERNATIONAL_CONNECT_PLUS\":\n        case \"INTERNATIONAL_CONNECT_PLUS\":\n            return \"International Connect Plus\";\n        case \"GROUND_HOME_DELIVERY\":\n        case \"fedex_home_delivery\":\n            return \"Ground Home Delivery\";\n        case \"SMART_POST\":\n            return \"Ground Economy\";\n        case \"FEDEX_FIRST_FREIGHT\":\n            return \"First Overnight Freight\";\n        case \"FEDEX_1_DAY_FREIGHT\":\n            return \"1-Day Freight\";\n        case \"FEDEX_2_DAY_FREIGHT\":\n            return \"2-Day Freight\";\n        case \"FEDEX_3_DAY_FREIGHT\":\n            return \"3-Day Freight\";\n        case \"INTERNATIONAL_PRIORITY_FREIGHT\":\n            return \"International Priority Freight\";\n        case \"INTERNATIONAL_ECONOMY_FREIGHT\":\n            return \"International Economy Freight\";\n\n\n            // USPS Returns\n        case \"GroundAdvantageReturn\":\n            return \"Ground Advantage Return\";\n        case \"PriorityMailReturn\":\n            return \"Priority Mail Return\";\n        case \"PriorityMailExpressReturn\":\n            return \"Priority Mail Express Return\";\n\n\n            // DHL Express\n        case \"DomesticEconomySelect\":\n            return \"Domestic Economy Select\";\n        case \"DomesticExpress\":\n            return \"Domestic Express\";\n        case \"DomesticExpress1030\":\n            return \"Domestic Express 10:30\";\n        case \"DomesticExpress1200\":\n            return \"Domestic Express 12:00\";\n        case \"EconomySelect\":\n            return \"Economy Select Document\";\n        case \"EconomySelectNonDoc\":\n            return \"Economy Select Non-Document\";\n        case \"EuroPack\":\n            return \"EuroPack Document\";\n        case \"EuropackNonDoc\":\n            return \"EuroPack Non-Document\";\n        case \"Express1030\":\n            return \"10:30 Document\";\n        case \"Express1030NonDoc\":\n            return \"10:30 Non-Document\";\n        case \"Express1200NonDoc\":\n            return \"12:00 Non-Document\";\n        case \"Express1200\":\n            return \"12:00 Document\";\n        case \"Express900\":\n            return \"9:00 Document\";\n        case \"Express900NonDoc\":\n            return \"9:00 Non-Doc\";\n        case \"ExpressEasy\":\n            return \"Easy Document\";\n        case \"ExpressEasyNonDoc\":\n            return \"Easy Non-Document\";\n        case \"ExpressEnvelope\":\n            return \"Envelope\";\n        case \"ExpressWorldwide\":\n        case \"dhl_express_worldwide\":\n            return \"Worldwide Document\";\n        case \"ExpressWorldwideB2C\":\n            return \"Worldwide B2C\";\n        case \"ExpressWorldwideB2CNonDoc\":\n            return \"Worldwide B2C Non-Document\";\n        case \"ExpressWorldwideECX\":\n            return \"Worldwide ECX\";\n        case \"ExpressWorldwideNonDoc\":\n            return \"Worldwide Non-Document\";\n\n\n            // DHL eCommerce\n        case \"DHLParcelExpedited\":\n            return \"Parcel Expedited\";\n        case \"DHLParcelExpeditedMax\":\n            return \"Parcel Expedited Max\";\n        case \"DHLParcelGround\":\n            return \"Parcel Ground\";\n        case \"DHLBPMExpedited\":\n            return \"PM Expedited\";\n        case \"DHLBPMGround\":\n            return \"PM Ground\";\n        case \"DHLParcelInternationalDirect\":\n            return \"Parcel International Direct\";\n        case \"DHLParcelInternationalStandard\":\n            return \"Parcel International Standard\";\n        case \"DHLParcelInternationalDirectSMB\":\n            return \"Parcel International Direct\";\n        case \"DHLParcelInternationalStandardSMB\":\n            return \"Parcel International Standard\";\n        case \"DHLPacketInternational\":\n            return \"Packet International\";\n        case \"DHLParcelInternationalDirectPriority\":\n            return \"Parcel International Direct Priority\";\n        case \"DHLParcelInternationalDirectStandard\":\n            return \"Parcel International Direct Standard\";\n\n            // Asendia USA Export\n        case \"USAExportStandard\":\n            return \"USA Export Standard\";\n        case \"USAExportPlus\":\n            return \"USA Export Plus\";\n        case \"USAExportSelect\":\n            return \"USA Export Select\";\n\n            // Spee-Dee\n        case \"SpeeDeeDelivery\":\n            return \"Delivery\";\n\n            // GlobalPost\n        case \"globalpost_standard_international\":\n            return \"Standard International\";\n        case \"globalpost_economy_international\":\n            return \"Economy International\";\n        case \"globalpost_plus\":\n            return \"Plus\";\n        case \"globalpost_first_class_international\":\n            return \"First-Class International\";\n\n            // Canada Post\n        case \"RegularParcel\":\n            return \"Regular Parcel\";\n        case \"ExpeditedParcel\":\n            return \"Expedited Parcel\";\n        case \"Xpresspost\":\n            return \"Xpresspost\";\n        case \"ExpeditedParcelUSA\":\n            return \"Expedited Parcel USA\";\n        case \"SmallPacketUSAAir\":\n            return \"Small Packet USA Air\";\n        case \"TrackedPacketUSA\":\n            return \"Tracked Packet USA\";\n        case \"TrackedPacketUSALVM\":\n            return \"Tracked Packet USA LVM\";\n        case \"XpresspostUSA\":\n            return \"Xpresspost USA\";\n        case \"XpresspostInternational\":\n            return \"Xpresspost Int'l\";\n        case \"InternationalParcelAir\":\n            return \"Int'l Parcel Air\";\n        case \"InternationalParcelSurface\":\n            return \"Int'l Parcel Surface\";\n        case \"SmallPacketInternationalAir\":\n            return \"Small Packet Int'l Air\";\n        case \"SmallPacketInternationalSurface\":\n            return \"Small Packet Int'l Surface\";\n        case \"TrackedPacketInternational\":\n            return \"Tracked Packet Int'l\";\n        case \"ExpeditedParcelPlus\":\n            return \"Expedited Parcel Plus\";\n\n\n            // GLS US\n        case \"EarlyPriorityOvernight\":\n            return \"Early Priority Overnight\";\n        case \"PriorityOvernight\":\n            return \"Priority Overnight\";\n        case \"CaliforniaParcelService\":\n            return \"California Parcel\";\n        case \"SaturdayDeliveryService\":\n            return \"Saturday Delivery\";\n        case \"EarlySaturdayService\":\n            return \"Early Saturday\";\n        case \"NoonPriorityService\":\n            return \"Noon Priority\";\n\n            // OnTrac\n        case \"GRND\":\n            return \"Ground\";\n\n            // LSO\n        case \"PriorityEarly\":\n            return \"Priority Early\";\n        case \"PriorityBasic\":\n            return \"Priority Basic\";\n        case \"Priority2ndDay\":\n            return \"Priority 2nd Day\";\n        case \"GroundEarly\":\n            return \"Ground Early\";\n        case \"GroundBasic\":\n            return \"Ground Basic\";\n        case \"ECommerce\":\n            return \"ECommerce\";\n\n            // Canpar\n        case \"USA\":\n            return \"USA\";\n        case \"SelectLetter\":\n            return \"Select Letter\";\n        case \"SelectPak\":\n            return \"Select Pak\";\n        case \"Select\":\n            return \"Select\";\n        case \"OvernightLetter\":\n            return \"Overnight Letter\";\n        case \"Overnight\":\n            return \"Overnight\";\n        case \"USALetter\":\n            return \"USA Letter\";\n        case \"USAPak\":\n            return \"USA Pak\";\n        case \"SelectUSA\":\n            return \"Select USA\";\n        case \"International\":\n            return \"International\";\n\n            // Purolator\n        case \"PurolatorExpress\":\n            return \"Express\";\n        case \"PurolatorExpress12PM\":\n            return \"Express 12PM\";\n        case \"PurolatorExpressPack12PM\":\n            return \"Express Pack 12PM\";\n        case \"PurolatorExpressBox12PM\":\n            return \"Express Box 12PM\";\n        case \"PurolatorExpressEnvelope12PM\":\n            return \"Express Envelope 12PM\";\n        case \"PurolatorExpress1030AM\":\n            return \"Express 10:30AM\";\n        case \"PurolatorExpress9AM\":\n            return \"Express 9AM\";\n        case \"PurolatorExpressBox\":\n            return \"Express Box\";\n        case \"PurolatorExpressBox1030AM\":\n            return \"Express Box 10:30AM\";\n        case \"PurolatorExpressBox9AM\":\n            return \"Express Box 9AM\";\n        case \"PurolatorExpressBoxEvening\":\n            return \"Express Box Evening\";\n        case \"PurolatorExpressBoxInternational\":\n            return \"Express Box International\";\n        case \"PurolatorExpressBoxUS\":\n            return \"Express Box US\";\n        case \"PurolatorExpressEnvelope\":\n            return \"Express Envelope\";\n        case \"PurolatorExpressEnvelope1030AM\":\n            return \"Express Envelope 10:30AM\";\n        case \"PurolatorExpressEnvelope9AM\":\n            return \"Express Envelope 9AM\";\n        case \"PurolatorExpressEnvelopeEvening\":\n            return \"Express Envelope Evening\";\n        case \"PurolatorExpressEnvelopeInternational\":\n            return \"Express Envelope International\";\n        case \"PurolatorExpressEnvelopeUS\":\n            return \"Express Envelope US\";\n        case \"PurolatorExpressEvening\":\n            return \"Express Evening\";\n        case \"PurolatorExpressInternational\":\n            return \"Express International\";\n        case \"PurolatorExpressInternational1030AM\":\n            return \"Express International 10:30AM\";\n        case \"PurolatorExpressInternational1200\":\n            return \"Express International 12:00\";\n        case \"PurolatorExpressInternational9AM\":\n            return \"Express International 9AM\";\n        case \"PurolatorExpressBoxInternational1030AM\":\n            return \"Express Box International 10:30AM\";\n        case \"PurolatorExpressBoxInternational1200\":\n            return \"Express Box International 12:00\";\n        case \"PurolatorExpressBoxInternational9AM\":\n            return \"Express Box International 9AM\";\n        case \"PurolatorExpressEnvelopeInternational1030AM\":\n            return \"Express Envelope International 10:30AM\";\n        case \"PurolatorExpressEnvelopeInternational1200\":\n            return \"Express Envelope International 12:00\";\n        case \"PurolatorExpressEnvelopeInternational9AM\":\n            return \"Express Envelope International 9AM\";\n        case \"PurolatorExpressPackInternational1030AM\":\n            return \"Express Pack International 10:30AM\";\n        case \"PurolatorExpressPackInternational1200\":\n            return \"Express Pack International 12:00\";\n        case \"PurolatorExpressPackInternational9AM\":\n            return \"Express Pack International 9AM\";\n        case \"PurolatorExpressPack\":\n            return \"Express Pack\";\n        case \"PurolatorExpressPack1030AM\":\n            return \"Express Pack 10:30AM\";\n        case \"PurolatorExpressPack9AM\":\n            return \"Express Pack 9AM\";\n        case \"PurolatorExpressPackEvening\":\n            return \"Express Pack Evening\";\n        case \"PurolatorExpressPackInternational\":\n            return \"Express Pack International\";\n        case \"PurolatorExpressPackUS\":\n            return \"Express Pack US\";\n        case \"PurolatorExpressUS\":\n            return \"Express US\";\n        case \"PurolatorExpressUS1030AM\":\n            return \"Express US 10:30AM\";\n        case \"PurolatorExpressUS1200\":\n            return \"Express US 12:00\";\n        case \"PurolatorExpressUS9AM\":\n            return \"Express US 9AM\";\n        case \"PurolatorExpressBoxUS1030AM\":\n            return \"Express Box US 10:30AM\";\n        case \"PurolatorExpressBoxUS1200\":\n            return \"Express Box US 12:00\";\n        case \"PurolatorExpressBoxUS9AM\":\n            return \"Express Box US 9AM\";\n        case \"PurolatorExpressEnvelopeUS1030AM\":\n            return \"Express Envelope US 10:30AM\";\n        case \"PurolatorExpressEnvelopeUS1200\":\n            return \"Express Envelope US 12:00\";\n        case \"PurolatorExpressEnvelopeUS9AM\":\n            return \"Express Envelope US 9AM\";\n        case \"PurolatorExpressPackUS1030AM\":\n            return \"Express Pack US 10:30AM\";\n        case \"PurolatorExpressPackUS1200\":\n            return \"Express Pack US 12:00\";\n        case \"PurolatorExpressPackUS9AM\":\n            return \"Express Pack US 9AM\";\n        case \"PurolatorGround\":\n            return \"Ground\";\n        case \"PurolatorGround1030AM\":\n            return \"Ground 10:30AM\";\n        case \"PurolatorGround9AM\":\n            return \"Ground 9AM\";\n        case \"PurolatorGround12PM\":\n            return \"Ground 12PM\";\n        case \"PurolatorGroundDistribution\":\n            return \"Ground Distribution\";\n        case \"PurolatorGroundEvening\":\n            return \"Ground Evening\";\n        case \"PurolatorGroundUS\":\n            return \"Ground US\";\n        case \"PurolatorQuickShip\":\n            return \"QuickShip\";\n        case \"PurolatorQuickShipEnvelope\":\n            return \"QuickShip Envelope\";\n        case \"PurolatorQuickShipPack\":\n            return \"QuickShip Pack\";\n        case \"PurolatorQuickShipBox\":\n            return \"QuickShip Box\";\n\n\n            // Maersk Parcel\n        case \"Maersk-3-Day\":\n            return \"3 Day\";\n\n    }\n\n    console.warn(\"Unknown shipping service ID: \", serviceId, \"Carrier ID:\", carrierId);\n\n    // Snipped: code that attempts to format the service ID nicely based on common naming patterns found in the wild.\n\n    return serviceName;\n}\n
"},{"location":"Docs/Database/","title":"Database Drivers","text":"

global.apis.database.getConnection() returns one of these, depending on which database is in use.

"},{"location":"Docs/Database/#sqlite","title":"SQLite","text":"
export class SQLiteAdapter {\n    constructor(db) {\n        this.type = \"sqlite\";\n        this.db = db;\n    }\n\n    async query(query, replace) {\n        if (global.devMode) {\n            console.info(query, replace);\n        }\n        return await this.db.all(query, replace);\n    }\n\n    async run(statement, replace) {\n        if (global.devMode) {\n            console.info(statement, replace);\n        }\n        return await this.db.run(statement, replace);\n    }\n\n    async exec(statement) {\n        if (global.devMode) {\n            console.info(statement);\n        }\n        return await this.db.exec(statement);\n    }\n\n    async exists(table, where, replace) {\n        const q = await this.db.all(\"SELECT EXISTS(SELECT 1 FROM \" + table + \" WHERE \" + where + \") as n\", replace);\n        if (q[0].n > 0) {\n            return true;\n        }\n        return false;\n    }\n\n    async close() {\n\n    }\n\n    async tableExists(table) {\n        return (await this.db.get(`SELECT count(name) AS cnt FROM sqlite_master WHERE type='table' AND name=?`, table)).cnt > 0;\n    }\n\n    /**\n     * Get the version code set in the database by setSchemaVersion().\n     */\n    async getSchemaVersion() {\n        var res = await this.db.all(`PRAGMA user_version`);\n        return res[0].user_version;\n    }\n\n    /**\n     * Set the database version, using PRAGMA user_version.  Must be an integer.\n     */\n    async setSchemaVersion(version) {\n        await this.db.exec(`PRAGMA user_version = ${version}`);\n    }\n\n}\n
"},{"location":"Docs/Database/#remote-hostmaster","title":"Remote host/master","text":"
export class RemoteDatabaseAdapter {\n    constructor() {\n        this.type = \"remote\";\n    }\n\n    async apirequest(args) {\n        var resp = await sendToPostalPointHTTPServer(args, \"database\");\n        if (typeof resp.status == \"string\" && resp.status == \"OK\") {\n            return resp.result;\n        } else if (typeof resp.status == \"string\" && resp.status == \"ERR\") {\n            if (typeof resp.message == \"string\") {\n                throw new Error(resp.message);\n            } else {\n                throw new Error(resp);\n            }\n        } else {\n            throw new Error(resp);\n        }\n    }\n\n    async query(query, replace = []) {\n        return await this.apirequest({type: \"query\", query: query, replace: replace});\n    }\n\n    async run(statement, replace = []) {\n        return await this.apirequest({type: \"run\", query: statement, replace: replace});\n    }\n\n    async exec(statement) {\n        return await this.apirequest({type: \"exec\", query: statement});\n    }\n\n    async exists(table, where, replace = []) {\n        return await this.apirequest({type: \"exists\", table: table, where: where, replace: replace});\n    }\n\n    async close() {\n        // NOOP: We don't care about this\n    }\n\n    async tableExists(table) {\n        return await this.apirequest({type: \"tableExists\", table: table});\n    }\n\n    async getSchemaVersion() {\n        return await this.apirequest({type: \"getSchemaVersion\"});\n    }\n\n    async setSchemaVersion(version) {\n        // NOOP: Don't upgrade server's installation, it can do that itself\n    }\n}\n
"},{"location":"Docs/Database/#mariadbmysql","title":"MariaDB/MySQL","text":"
export class MariaDBAdapter {\n    constructor(connection) {\n        this.type = \"mariadb\";\n        this.conn = connection;\n    }\n\n    async query(query, replace) {\n        if (global.devMode) {\n            console.info(query, replace);\n        }\n        return await this.conn.query(query, replace);\n    }\n\n    async run(statement, replace) {\n        if (global.devMode) {\n            console.info(statement, replace);\n        }\n        return await this.query(statement, replace);\n    }\n\n    async exec(statement) {\n        if (global.devMode) {\n            console.info(statement);\n        }\n        return await this.run(statement);\n    }\n\n    async exists(table, where, replace) {\n        const q = await this.query(\"SELECT EXISTS(SELECT 1 FROM \" + table + \" WHERE \" + where + \") as n\", replace);\n        if (q[0].n > 0) {\n            return true;\n        }\n        return false;\n    }\n\n    async close() {\n        await this.conn.release();\n    }\n\n    async tableExists(table) {\n        return (await this.query(\"SHOW TABLES LIKE ?\", table)).length > 0;\n    }\n\n    /**\n     * Get the version code set in the database by setSchemaVersion().  Returns zero if not set.\n     */\n    async getSchemaVersion() {\n        if (await this.tableExists(\"database_metadata\")) {\n            var res = await this.query(\"SELECT `value` FROM database_metadata WHERE `key`='schema_version' LIMIT 1\");\n            console.log(res);\n            console.log(res[0].value);\n            if (res.length == 1) {\n                return res[0].value;\n            }\n        }\n        return 0;\n    }\n\n    /**\n     * Set a version number for the database schema.\n     * Must be an integer to maintain code compatibility with SQLite driver.\n     * Will create a \"database_metadata\" table if required to store the version number.\n     */\n    async setSchemaVersion(version) {\n        if (await this.tableExists(\"database_metadata\")) {\n            await this.query(\"REPLACE INTO `database_metadata` (`key`, `value`) VALUES (?, ?)\", [\"schema_version\", version]);\n        } else {\n            await this.exec(\"CREATE TABLE IF NOT EXISTS `database_metadata` ( `key` VARCHAR(50) NOT NULL, `value` VARCHAR(255) NOT NULL DEFAULT '', PRIMARY KEY (`key`))\");\n            await this.setSchemaVersion(version);\n        }\n    }\n}\n
"},{"location":"Docs/Events/","title":"Event Bus","text":"

Plugins can use global.apis.eventbus to receive events when certain actions happen in PostalPoint.

"},{"location":"Docs/Events/#event-list","title":"Event List","text":""},{"location":"Docs/Events/#example-code","title":"Example Code","text":"
// Handle a barcode scan.\n// Remember that PostalPoint will probably also be doing something in response to the barcode.\nglobal.apis.eventbus.on(\"barcodeScanned\", function (barcodedata) {\n    // do something with the barcode\n});\n\n// Close the embedded web browser, returning the user to whatever was onscreen before it opened\nglobal.apis.eventbus.emit(\"browserCloseRequest\");\n
"},{"location":"Docs/Events/#event-data-objects","title":"Event Data Objects","text":"

For events that return an object instead of a single value.

"},{"location":"Docs/Events/#transactionfinished","title":"transactionFinished","text":"

See Receipt

"},{"location":"Docs/Events/#customersignaturecollected","title":"customerSignatureCollected","text":"
{\n    \"svg\": \"data:image/svg+xml;base64,...\",\n    \"png\": \"data:image/png;base64,...\"\n}\n
"},{"location":"Docs/Events/#packagecheckin","title":"packageCheckIn","text":"
{\n    tag: \"abcxyz123456\", // Unique ID for the package, also found in the shelf label barcode.\n    tracking: \"94001...\", // Package tracking number. May be an empty string for items without tracking.\n    carrier: \"FedEx\", // Package carrier name, if detectable from the tracking number.  Otherwise an empty string.\n    mailbox: \"123\", // Mailbox number. Will be \"HAL\" for Hold At Location packages.\n    isHAL: false, // True if package is for Hold At Location.\n    recipient: \"\", // Hold At Location recipient name, or empty string if not HAL.\n    toLocker: \"5\", // Parcel locker number, or false if not going to a parcel locker.\n    shelfLabelPrinted: true // Indicates if a shelf label was printed for this package.  Will be false if going to a locker, if the user requested no label, or if the label failed to print.\n}\n
"},{"location":"Docs/FormPS1583/","title":"FormPS1583 object","text":"
export class FormPS1583 {\n    constructor() {\n        this.formRevision = LATEST_FORM_REVISION; // Currently \"June2024\"\n        this.pmbOpenedDate = new Date();\n        this.pmbClosedDate = null;\n        this.cmraStreetAddress = getSetting(\"origin_street1\");\n        this.pmbNumber = \"\";\n        this.cmraZIP = getSetting(\"origin_zip\");\n        var cmraZIPData = getZIP(this.cmraZIP);\n        if (cmraZIPData) {\n            this.cmraCity = cmraZIPData.city;\n            this.cmraState = cmraZIPData.state;\n        } else {\n            this.cmraCity = getSetting(\"origin_city\", \"\");\n            this.cmraState = getSetting(\"origin_state\", \"\");\n        }\n        this.serviceTypeBusiness = false; // true for business PMB, false for residential\n        this.applicant = {\n            firstName: \"\",\n            lastName: \"\",\n            middleName: \"\",\n            phone: \"\",\n            email: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            courtProtected: false,\n            photoID: {\n                name: \"\",\n                number: \"\",\n                issuer: \"\",\n                expirationDate: null,\n                type: null  // \"DL/ID\", \"UniformedService\", \"USAccess\", \"USUni\",\n                        // \"Passport\", \"Matricula\", \"NEXUS\",\n                        // \"CertOfNaturalization\", \"USPermResident\"\n            },\n            addressID: {\n                name: \"\",\n                streetAddress: \"\",\n                city: \"\",\n                state: \"\",\n                zip: \"\",\n                country: \"\",\n                type: null, // \"DL/ID\", \"Lease\", \"Mortgage\", \"Insurance\", \"VehicleReg\", \"Voter\"\n                expirationDate: null // Optional currently but must be kept current - Oct 2025\n            }\n        };\n        this.authorizedIndividual = {\n            firstName: \"\",\n            lastName: \"\",\n            middleName: \"\",\n            phone: \"\",\n            email: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            photoID: {\n                name: \"\",\n                number: \"\",\n                issuer: \"\",\n                expirationDate: null,\n                type: null  // \"DL/ID\", \"UniformedService\", \"USAccess\", \"USUni\",\n                        // \"Passport\", \"Matricula\", \"NEXUS\",\n                        // \"CertOfNaturalization\", \"USPermResident\"\n            },\n            addressID: {\n                name: \"\",\n                streetAddress: \"\",\n                city: \"\",\n                state: \"\",\n                zip: \"\",\n                country: \"\",\n                type: null, // \"DL/ID\", \"Lease\", \"Mortgage\", \"Insurance\", \"VehicleReg\", \"Voter\"\n                expirationDate: null // Optional currently but must be kept current - Oct 2025\n            }\n        };\n        this.mailTransferredTo = {\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            phone: \"\",\n            email: \"\"\n        };\n        this.business = {\n            name: \"\",\n            type: \"\",\n            streetAddress: \"\",\n            city: \"\",\n            state: \"\",\n            zip: \"\",\n            country: \"\",\n            phone: \"\",\n            placeOfRegistration: \"\"\n        };\n        this.additionalRecipients = []; // Array of strings containing names\n        this.applicantSignature = \"\"; // PNG image data URI\n        this.applicantSignatureDate = null;\n        this.cmraSignature = \"\"; // PNG image data URI\n        this.cmraSignatureDate = null;\n        this.hasForwardingAddress = false;\n    }\n\n    getTermsAndConditions() {\n        return DEFAULT_TERMS_CONDITIONS[this.formRevision];\n    }\n\n    getApplicantForwardingAddress() {\n        if (this.mailTransferredTo.streetAddress != \"\") {\n            return new Address(null,\n                    [this.applicant.firstName, this.applicant.lastName].filter(Boolean).join(\" \"),\n                    this.business.name ?? \"\",\n                    this.mailTransferredTo.streetAddress,\n                    \"\",\n                    this.mailTransferredTo.zip,\n                    this.mailTransferredTo.city,\n                    this.mailTransferredTo.state,\n                    this.mailTransferredTo.country ?? \"US\",\n                    this.mailTransferredTo.phone ?? \"\",\n                    this.mailTransferredTo.email ?? \"\"\n                    );\n        }\n        return new Address(null,\n                [this.applicant.firstName, this.applicant.lastName].filter(Boolean).join(\" \"),\n                this.business.name ?? \"\",\n                this.applicant.streetAddress,\n                \"\",\n                this.applicant.zip,\n                this.applicant.city,\n                this.applicant.state,\n                this.applicant.country ?? \"US\",\n                this.applicant.phone ?? \"\",\n                this.applicant.email ?? \"\"\n                );\n    }\n\n    getFormFields() {\n        var fields = FORM_FIELDS[this.formRevision];\n        function getNestedValue(obj, path) {\n            return path.split('.').reduce((o, key) => (o ? o[key] : \"\"), obj);\n        }\n        var outfields = [];\n        var groupheading = {};\n        var groupfields = [];\n        for (var prop in fields) {\n            if (fields[prop].t == \"heading\") {\n                if (groupfields.length > 0) {\n                    groupheading.fields = groupfields;\n                    outfields.push(groupheading);\n                    groupfields = [];\n                }\n                groupheading = {\n                    heading: fields[prop].l,\n                    groupid: fields[prop].group ?? null,\n                    fields: []\n                };\n            }\n            fields[prop].n = prop;\n            fields[prop].v = getNestedValue(this, prop);\n            if (typeof fields[prop].v == \"undefined\" || fields[prop].v == null) {\n                fields[prop].v = \"\";\n            }\n            if (fields[prop].t == \"date\") {\n                if (fields[prop].v instanceof Date) {\n                    // Cancel out the timezone in the date object\n                    // If we don't do this, the dates will be subtracted by one day each time we load\n                    // https://stackoverflow.com/a/17329571\n                    fields[prop].v.setTime(fields[prop].v.getTime() + fields[prop].v.getTimezoneOffset() * 60 * 1000);\n                }\n                fields[prop].v = formatTimestamp(\"Y-m-d\", fields[prop].v);\n                if (fields[prop].v == \"1969-12-31\" || fields[prop].v == \"1970-01-01\") {\n                    fields[prop].v = \"\";\n                }\n            }\n            if (fields[prop].t == \"select\" && typeof fields[prop].b == \"boolean\") {\n                fields[prop].v = fields[prop].v ? \"true\" : \"\";\n            }\n            if (fields[prop].t != \"heading\") {\n                groupfields.push(fields[prop]);\n            }\n        }\n        if (groupfields != []) {\n            groupheading.fields = groupfields;\n            outfields.push(groupheading);\n        }\n        return outfields;\n    }\n\n    static fromHTMLFormData(formdata, revision = LATEST_FORM_REVISION) {\n        var f = new FormPS1583();\n\n        function setNestedValue(obj, path, value) {\n            const keys = path.split('.');\n            const lastKey = keys.pop();\n            const target = keys.reduce((o, key) => {\n                if (o[key] === undefined)\n                    o[key] = {};\n                return o[key];\n            }, obj);\n            if (typeof FORM_FIELDS[revision][path].b == \"boolean\") {\n                target[lastKey] = (value == \"true\" || value == true);\n            } else {\n                target[lastKey] = value;\n            }\n        }\n\n        for (var prop in formdata) {\n            setNestedValue(f, prop, formdata[prop]);\n        }\n\n        return f;\n    }\n\n    static fromJSON(o) {\n        var f = new FormPS1583();\n        f.formRevision = o.formRevision ?? LATEST_FORM_REVISION;\n        f.pmbOpenedDate = new Date(o.pmbOpenedDate);\n        f.pmbClosedDate = o.pmbClosedDate ? new Date(o.pmbClosedDate) : null;\n        f.cmraStreetAddress = o.cmraStreetAddress;\n        f.pmbNumber = o.pmbNumber;\n        f.cmraCity = o.cmraCity;\n        // snip, see constructor for full data structure\n        return f;\n    }\n\n    toJSON() {\n        return {\n            formRevision: this.formRevision,\n            pmbOpenedDate: this.pmbOpenedDate,\n            pmbClosedDate: this.pmbClosedDate,\n            cmraStreetAddress: this.cmraStreetAddress,\n            pmbNumber: this.pmbNumber,\n            cmraCity: this.cmraCity,\n            // snip, see constructor for full data structure\n        };\n    }\n\n    /**\n     * Render this form to PDF\n     * @returns PDF bytes\n     */\n    async getPDF() {\n        // snip, it draws the form contents onto a PDF using the pdf-lib library\n        // If you really want to see how, email us for the code\n        return await document.save();\n    }\n}\n
"},{"location":"Docs/HTTP_API_Server/","title":"HTTP API Server","text":"

PostalPoint runs a local HTTP server to allow communication with other devices on the LAN.

With the httpserver plugin API, plugins can add their own local API endpoints.

Valid HTTP API requests are POST requests with either a JSON request body, or an entirely empty body.

The default HTTP server settings are to bind to all addresses on port 7678. There is a basic API web client accessible on / for manual API access, for example, at http://localhost:7678/.

"},{"location":"Docs/HTTP_API_Server/#security","title":"Security","text":"

API requests are authenticated with a \"Network Connection Key\". Each installation of PostalPoint maintains a user-configurable list of keys which are allowed to connect to that installation's API server.

This means that, in order for a remote client to connect to PostalPoint, it must generate a random alphanumeric ID, which must be saved in the PostalPoint settings.

"},{"location":"Docs/HTTP_API_Server/#adding-an-api-endpoint","title":"Adding an API endpoint","text":"

This code adds an endpoint reachable by POST to http://[local hostname]:[port]/testendpointname.

global.apis.httpserver.addEndpoint(\"testendpointname\", async function (request) {\n    // `request` is an object parsed from the request body.\n    if (request.abc == \"123\") {\n        // A non-string `body` is converted to JSON before the HTTP reply is sent.\n        return {body: {json: true, abc: 123}, httpcode: 200, contentType: \"application/json\"};\n    }\n    // A string `body` is sent to the client as-is using whatever contentType you specify.\n    return {body: \"abc\", httpcode: 200, contentType: \"text/plain\"};\n});\n
"},{"location":"Docs/HTTP_API_Server/#connecting-to-a-remote-endpoint","title":"Connecting to a remote endpoint","text":"

By default, sendRequestToRemote uses the configured \"Host PC\", but a different hostname/IP may be specified.

This code hits the endpoint added above.

try {\n    const responseObject = await global.apis.httpserver.sendRequestToRemote({abc: \"123\"}, \"testendpointname\");\n    // responseObject will be {json: true, abc: 123}\n    console.log(responseObject);\n} catch (ex) {\n    global.apis.alert(ex.message, \"Request Error\");\n}\n
"},{"location":"Docs/HiddenConfigs/","title":"Configuration Flags","text":"

In the Advanced settings of PostalPoint, there is a tool to manually get and set settings by ID.

Most PostalPoint settings are available in the Settings screen, and their IDs can be found in the JSON file created by running a database backup.

However, there are some special \"hidden\" setting options that can override default behavior.

Warning: PostalPoint is tested with these settings at their default values (usually, unset). There is a small chance of bugs being created by changing a value.

"},{"location":"Docs/Parcel/","title":"Parcel/Package Object","text":"

This object is supplied a plugin registered with registerRateEndpoint when PostalPoint requests shipping rates from the plugin.

export class Package {\n    constructor(isPrepaid = false) {\n        this.prepaid = isPrepaid;\n        this.packaging = {\n            type: \"Parcel\",\n            service: \"\",\n            carrier: \"\",\n            length: 999999,\n            width: 999999,\n            height: 999999,\n            weightOz: 999999,\n            nonmachinable: false,\n            additionalHandling: false,\n            internalid: 100,\n            oversizeFlag: false\n        };\n        this.extraServices = {\n            certifiedMail: false,\n            barcode3800: \"\",\n            registeredMail: false,\n            registeredMailAmount: false, // can be a number in USD\n            returnReceipt: false,\n            returnReceiptElectronic: false,\n            insurance: false, // can be a number in USD\n            signature: false, // can be false, \"SIGNATURE\", or \"SIGNATURE_RESTRICTED\"\n            hazmat: false,\n            perishable: false,\n            crematedRemains: false,\n            liveAnimal: false, // BEES, DAY_OLD_POULTRY, ADULT_BIRDS, OTHER_LIVES\n            cod: false, // Collect on Delivery\n            codAmount: false,\n            endorsement: \"\", // ADDRESS_SERVICE_REQUESTED, CHANGE_SERVICE_REQUESTED, FORWARDING_SERVICE_REQUESTED, LEAVE_IF_NO_RESPONSE, RETURN_SERVICE_REQUESTED\n            carrier_billing_account: {// Bill a third party's account number for the label\n                type: \"\", // \"\" (ignores this entire option), \"SENDER\" (EasyPost default), \"THIRD_PARTY\", \"RECEIVER\", \"COLLECT\"\n                carrier: \"\", // Carrier ID (should be used to filter rates)\n                account_number: \"\", // Carrier account number to bill\n                country: \"\", // Country account is based in\n                postal_code: \"\" // Postal code of account\n            },\n            dryIce: false,\n            dryIceWeight: 0,\n            dryIceMedical: false\n        };\n        this.description = \"\"; // Fillable on customs form, or generated before rating call using customs items\n        this.specialRateEligibility = false;\n        this.customs = {\n            contents: \"\",\n            contentsExplanation: \"\", // needed if contents is \"other\", will be copied from this.description if blank for maximum carrier compatibility\n            signature: \"\",\n            restriction: \"\",\n            restrictionComments: \"\", // needed if restriction is \"other\"\n            nonDelivery: \"return\", // \"return\" or \"abandon\",\n            eel_pfc: \"\",\n            items: [] // {index: 0, description: \"\", qty: \"\", lbs: \"\", oz: \"\", value: \"\", hscode: \"\", origin: US\"}\n        };\n        this.toAddress = new Address();\n        this.returnAddress = new Address();\n        this.originAddress = new Address();\n        this.trackingNumber = \"\";\n    }\n\n    /**\n     * Format as EasyPost shipment object\n     * @returns {Package.toEasyPostShipment.shipment}\n     */\n    async toEasyPostShipment() {\n        // Not relevant to plugins\n    }\n\n    /**\n     * Format as Endicia shipment object\n     * @returns {Package.toSERAShipment.shipment}\n     */\n    async toSERAShipment() {\n        // Not relevant to plugins\n    }\n\n    /**\n     * Get a human-readable summary of size and options.\n     * Does not include address data.\n     * @returns {String}\n     */\n    async toString() {\n        let summary = [];\n        let packaging = await getPackagingByID(this.packaging.internalid);\n        let weight = ozToLbsOz(this.packaging.weightOz);\n        let weightStr = this.packaging.weightOz >= 16 ? `${weight[0]} lbs ${weight[1]} oz` : `${weight[1]} oz`;\n        if (packaging != false) {\n            if (packaging.irregular) {\n                if (packaging.weight === false) {\n                    summary.push(\"Parcel\");\n                } else {\n                    summary.push(`${weightStr} Parcel`);\n                }\n                summary.push(\"Additional Handling\");\n            } else {\n                if (packaging.weight === false) {\n                    summary.push(packaging.name);\n                } else {\n                    summary.push(`${weightStr} ${packaging.name}`);\n                }\n            }\n        } else {\n            summary.push(weightStr);\n        }\n        if (this.extraServices.hazmat) {\n            summary.push(\"HAZMAT\");\n        }\n        if (this.extraServices.liveAnimal === true) {\n            summary.push(\"Live Animals\");\n        } else if (typeof this.extraServices.liveAnimal == \"string\") {\n            switch (this.extraServices.liveAnimal) {\n                case \"BEES\":\n                    summary.push(\"Live Bees\");\n                    break;\n                case \"DAY_OLD_POULTRY\":\n                    summary.push(\"Day-old Poultry\");\n                    break;\n                case \"ADULT_BIRDS\":\n                    summary.push(\"Live Adult Birds\");\n                    break;\n                case \"OTHER_LIVES\":\n                default:\n                    summary.push(\"Live Animals\");\n                    break;\n            }\n        }\n        if (this.extraServices.perishable) {\n            summary.push(\"Perishable\");\n        }\n        if (this.extraServices.crematedRemains) {\n            summary.push(\"Cremated Remains\");\n        }\n        if (this.extraServices.certifiedMail) {\n            summary.push(\"Certified Mail\");\n        } else if (this.extraServices.registeredMail) {\n            summary.push(\"Registered Mail\");\n            summary.push(\"Registered for $\" + (this.extraServices.registeredMailAmount * 1.0).toFixed(2));\n        } else if (this.extraServices.signature == \"SIGNATURE\") {\n            summary.push(\"Signature Required\");\n        }\n        if (this.extraServices.signature == \"ADULT_SIGNATURE\") {\n            summary.push(\"Adult Signature Required\");\n        }\n        if (this.extraServices.signature == \"SIGNATURE_RESTRICTED\") {\n            summary.push(\"Restricted Delivery\");\n        }\n        if (this.extraServices.returnReceiptElectronic) {\n            summary.push(\"Return Receipt Electronic\");\n        }\n        if (this.extraServices.returnReceipt) {\n            summary.push(\"Return Receipt\");\n        }\n        if (this.extraServices.insurance) {\n            summary.push(\"Insured for $\" + (this.extraServices.insurance * 1.0).toFixed(2));\n        }\n        if (this.extraServices.cod) {\n            summary.push(\"Collect on Delivery: $\" + (this.extraServices.codAmount * 1.0).toFixed(2));\n        }\n        if (this.extraServices.dryIce && this.extraServices.dryIceWeight > 0) {\n            summary.push(\"Dry Ice: \" + (this.extraServices.dryIceWeight * 1).toFixed(0) + \" oz\");\n        }\n        if (this.extraServices.carrier_billing_account?.type) {\n            if (this.extraServices.carrier_billing_account.type != \"\") {\n                var accountNumber = this.extraServices.carrier_billing_account.account_number;\n                var accountNumberCensored = accountNumber.substring(accountNumber.length - 4).padStart(accountNumber.length, \"X\");\n                var carrierName = this.extraServices.carrier_billing_account.carrier;\n                switch (this.extraServices.carrier_billing_account.type) {\n                    case \"SENDER\":\n                        summary.push(`Bill to sender ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"THIRD_PARTY\":\n                        summary.push(`Bill to third party ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"RECEIVER\":\n                        summary.push(`Bill to receiver ${carrierName} account #${accountNumberCensored}`);\n                        break;\n                    case \"COLLECT\":\n                        if (accountNumber.length > 0) {\n                            summary.push(`Bill collect ${carrierName} account #${accountNumberCensored}`);\n                        } else {\n                            summary.push(`Bill collect`);\n                        }\n                        break;\n                }\n            }\n        }\n        return summary.join(\"\\n\");\n    }\n\n    async needsHAZMATPrompt() {\n        try {\n            let packagingInfo = await getPackagingByID(this.packaging.internalid);\n            if (packagingInfo.hazmat) {\n                return true;\n            }\n            if (this.packaging.weight > 10) {\n                return true;\n            }\n            if (packagingInfo.l >= -1 && Math.max(this.packaging.length, this.packaging.width, this.packaging.height) > 0.5) {\n                return true;\n            }\n            switch (packagingInfo.type) {\n                case \"Letter\":\n                case \"Card\":\n                    return false;\n            }\n            return true;\n        } catch (ex) {\n            return true;\n        }\n    }\n\n    get isPrepaid() {\n        return this.prepaid == true;\n    }\n\n    setCustomsInfo(contents, contentsExplanation, signature, restriction, restrictionComments, nonDelivery) {\n        let items = this.customs.items; // Save this and copy it back in so we don't overwrite it\n        this.customs = {\n            contents: contents,\n            contentsExplanation: contentsExplanation, // needed if contents is \"other\"\n            signature: signature,\n            restriction: restriction,\n            restrictionComments: restrictionComments, // needed if restriction is \"other\"\n            nonDelivery: nonDelivery, // \"return\" or \"abandon\",\n            items: items\n        };\n    }\n\n    /**\n     * Get the customs items, ignoring any that are blank.\n     * @returns {Array}\n     */\n    getCustomsItems() {\n        let items = [];\n        for (let i = 0; i < this.customs.items.length; i++) {\n            let item = this.customs.items[i];\n            if (item.description == \"\" && (item.qty == \"\" || item.qty == 0) && (item.weight == \"\" || item.weight == 0) && (item.value == \"\" || item.value == 0)) {\n                continue;\n            }\n            items.push(item);\n        }\n        return items;\n    }\n\n    setCustomsItems(items) {\n        this.customs.items = items;\n    }\n\n    getCustoms() {\n        this.customs.items = this.getCustomsItems();\n        return this.customs;\n    }\n\n    /**\n     * Attempt to automatically fix simple issues like overweight letters.\n     * @returns {undefined}\n     */\n    async fixIssues() {\n        if (this.packaging.type == \"Letter\" && this.packaging.weightOz > 3.5) {\n            if (this.packaging.nonmachinable) {\n                return; // Has to be a parcel, can't fix without dimensions\n            }\n            this.packaging.type = \"Flat\";\n            this.packaging.internalid = 104;\n        }\n    }\n\n    /**\n     * Do some basic checks to see if this package is even remotely shippable\n     * @param {boolean} kioskMode If true, returned strings are suitable for display in kiosk mode.\n     * @returns {boolean|string} true if okay, human-readable error message and instructions if not okay\n     */\n    async isValid(kioskMode = false) {\n        // Removed from docs for brevity. Just a bunch of if statements to catch problems.\n    }\n\n    /**\n     * Set package characteristics\n     * @param {string} type \"Parcel\", \"Letter\", \"Flat\", \"Card\"\n     * @param {type} service\n     * @param {type} carrier\n     * @param {type} length\n     * @param {type} width\n     * @param {type} height\n     * @param {type} weightOz\n     * @returns {undefined}\n     */\n    setPackaging(type, service, carrier, length, width, height, weightOz, nonmachinable) {\n        if (typeof nonmachinable == \"undefined\") {\n            nonmachinable = false;\n        }\n        if (type == \"Card\") {\n            // Postcards\n            weightOz = 1;\n            this.packaging.internalid = 105;\n        } else if (type == \"Flat\") {\n            this.packaging.internalid = 104;\n        } else if (type == \"Letter\") {\n            this.packaging.internalid = 102;\n            if (nonmachinable) {\n                this.packaging.internalid = 103;\n            }\n        }\n        this.packaging.type = type;\n        this.packaging.service = service;\n        this.packaging.carrier = carrier;\n        this.packaging.weightOz = weightOz;\n        this.packaging.nonmachinable = nonmachinable;\n\n        // Enforce Length > Width > Height\n        let size = [length, width, height];\n        size.sort(function (a, b) {\n            return b - a;\n        });\n        this.packaging.length = size[0];\n        this.packaging.width = size[1];\n        this.packaging.height = size[2];\n    }\n\n    /**\n     * Set an extra service\n     * @param {string} id Service ID\n     * @param {boolean} enabled Turn it on or off\n     * @param {string} value Service value, if needed (some are not just a boolean)\n     * @returns {undefined}\n     */\n    setExtraService(id, enabled, value) {\n        if (typeof value != \"undefined\" && enabled) {\n            this.extraServices[id] = value;\n        } else {\n            this.extraServices[id] = enabled == true;\n        }\n    }\n\n    getExtraServices() {\n        return this.extraServices;\n    }\n\n    /**\n     * Set to \"MEDIA_MAIL\", \"LIBRARY_MAIL\", or false\n     * @param {type} rate\n     * @returns {undefined}\n     */\n    set specialRate(rate) {\n        if (rate == \"MEDIA\") {\n            rate = \"MEDIA_MAIL\";\n        } else if (rate == \"LIBRARY\") {\n            rate = \"LIBRARY_MAIL\";\n        }\n        if (rate != \"MEDIA_MAIL\" && rate != \"LIBRARY_MAIL\") {\n            rate = false;\n        }\n        this.specialRateEligibility = rate;\n    }\n\n    get specialRate() {\n        return this.specialRateEligibility;\n    }\n\n    /**\n     * Save an address to this package.\n     * @param {string} type \"to\", \"return\", or \"origin\"\n     * @param {string} name\n     * @param {string} company\n     * @param {string} street1\n     * @param {string} street2\n     * @param {string} city\n     * @param {string} state\n     * @param {string} zip\n     * @param {string} country ISO 2-char country code\n     * @param {string} phone\n     * @param {string} email\n     * @returns {undefined}\n     */\n    setAddress(type, name, company, street1, street2, city, state, zip, country, phone, email) {\n        let address = Address.fromObject({\n            name: name,\n            company: company,\n            street1: street1,\n            street2: street2,\n            city: city,\n            state: state,\n            zip: zip,\n            country: country,\n            phone: phone,\n            email: email\n        });\n        switch (type) {\n            case \"to\":\n                this.toAddress = address;\n                break;\n            case \"return\":\n                this.returnAddress = address;\n                break;\n            case \"origin\":\n                this.originAddress = address;\n                break;\n        }\n    }\n\n    /**\n     * Set an address using an object that matches the internal form (see setAddress())\n     * @param {string} type\n     * @param {object} data\n     * @returns {undefined}\n     */\n    setAddressWhole(type, address) {\n        switch (type) {\n            case \"to\":\n                this.toAddress = Address.fromObject(address);\n                break;\n            case \"return\":\n                this.returnAddress = Address.fromObject(address);\n                break;\n            case \"origin\":\n                this.originAddress = Address.fromObject(address);\n                break;\n        }\n    }\n\n    get tracking() {\n        return this.trackingNumber;\n    }\n\n    set tracking(n) {\n        this.trackingNumber = n;\n    }\n\n    /**\n     * Get the \"from\" address that will be shown,\n     * using the return address or origin address as needed\n     * @returns {address}\n     */\n    getReturnAddress() {\n        var a = null;\n        if (typeof this.returnAddress == \"object\") {\n            a = Address.fromObject(this.returnAddress);\n        } else {\n            a = Address.fromObject(this.originAddress);\n        }\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n\n    getToAddress() {\n        var a = Address.fromObject(this.toAddress);\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n\n    getFromAddress() {\n        var a = null;\n        if (typeof this.originAddress == \"object\") {\n            a = Address.fromObject(this.originAddress);\n        } else {\n            a = Address.fromObject(this.returnAddress);\n        }\n        if (a.country == \"\") {\n            a.country = defaultCountryCode();\n        }\n        return a;\n    }\n}\n
"},{"location":"Docs/Receipt/","title":"Receipt Objects","text":""},{"location":"Docs/Receipt/#sample-receipt-object","title":"Sample receipt object","text":"

This is an example of the data global.apis.pos.onReceiptChange and global.apis.pos.onTransactionFinished send to plugins. All data for a PostalPoint transaction is available in its receipt object.

The money-related properties near the bottom of this sample are generated by PostalPoint from the items and payments.

"},{"location":"Docs/Receipt/#notes","title":"Notes:","text":"
{\n    \"items\": [\n        ReceiptItem.fromJSON({\n            \"id\": \"9100123456789012345678\",\n            \"label\": \"Test Package\",\n            \"text\": \"Package Details\\nTracking # 9100 1234 5678 9012 3456 78\\nTo:\\nTEST PERSON\\nORGANIZATION INC\\n123 TEST ROAD\\nTESTTOWN TE 99999-0001\",\n            \"priceEach\": 8,\n            \"qty\": 1,\n            \"cost\": 0,\n            \"retail\": 8,\n            \"taxRate\": 0,\n            \"free\": false,\n            \"barcode\": \"9100123456789012345678\",\n            \"certifiedInfo\": false,\n            \"isMerch\": false,\n            \"surcharge\": false,\n            \"toAddress\": {\n                \"name\": \"TEST PERSON\",\n                \"company\": \"ORGANIZATION INC\",\n                \"street1\": \"123 TEST ROAD\",\n                \"street2\": null,\n                \"city\": \"TESTTOWN\",\n                \"state\": \"TE\",\n                \"zip\": \"99999-0001\",\n                \"email\": null,\n                \"phone\": null,\n                \"country\": \"US\"\n            },\n            \"fromAddress\": {\n                \"name\": \"TEST PERSON\",\n                \"company\": \"ORGANIZATION INC\",\n                \"street1\": \"123 TEST ROAD\",\n                \"street2\": null,\n                \"city\": \"TESTTOWN\",\n                \"state\": \"TE\",\n                \"zip\": \"99999-0001\",\n                \"email\": null,\n                \"phone\": null,\n                \"country\": \"US\"\n            }\n        }),\n        ReceiptItem.fromJSON({\n            \"id\": \"testitem\",\n            \"label\": \"Test Item\",\n            \"text\": \"\",\n            \"priceEach\": 2,\n            \"qty\": 1,\n            \"cost\": 0,\n            \"retail\": 2,\n            \"taxRate\": 0.1,\n            \"free\": false,\n            \"barcode\": \"\",\n            \"certifiedInfo\": false,\n            \"isMerch\": true,\n            \"surcharge\": false\n        })\n    ],\n    \"payments\": [\n        ReceiptPayment.fromJSON({\n            \"amount\": 10,\n            \"type\": \"cash\",\n            \"text\": \"\",\n            \"id\": \"testcash\"\n        })\n    ],\n    \"subtotal\": 10,\n    \"subtotalFormatted\": \"$10.00\",\n    \"tax\": 0.2,\n    \"taxFormatted\": \"$0.20\",\n    \"grandTotal\": 10.2,\n    \"grandTotalFormatted\": \"$10.20\",\n    \"paid\": 10.2,\n    \"paidFormatted\": \"$10.20\",\n    \"due\": 0,\n    \"dueFormatted\": \"$0.00\",\n    \"emailTo\": null,\n    \"pendingEmailTo\": null,\n    \"uuid\": \"1234567890abcdef\",\n    \"customerAccountId\": null,\n    \"topTextHTML\": \"\",\n    \"bottomTextHTML\": \"\"\n}\n
"},{"location":"Docs/Receipt/#globalapisposreceiptitem","title":"global.apis.pos.ReceiptItem","text":"
export class ReceiptItem {\n    /**\n     *\n     * @param {string|number} id Unique ID number for this item (UPC code, inventory number, etc). Used to deduplicate line items. Unique items (like shipping labels) should be random or empty.\n     * @param {string} label One-line item information.\n     * @param {string} text Extra item information.\n     * @param {number} priceEach Price per unit\n     * @param {number} quantity Number of units\n     * @param {number} cost Cost per unit. Used for automatic expense tracking.\n     * @param {number} taxrate Examples: 0 (for 0%), 0.05 (for 5%), etc\n     * @param {string} taxableAmount The part of the sale price that's taxable. \"\" for default (all), \"markup\" for only taxing profit.\n     * @returns {ReceiptItem}\n     */\n    constructor(id, label, text, priceEach, quantity, cost, taxrate = 0.0, taxableAmount = \"\") {\n        this.id = id;\n        this.label = label;\n        if (text == null) {\n            this.txt == \"\";\n        } else {\n            this.txt = text;\n        }\n        this.priceEach = num(priceEach);\n        this.qty = num(quantity);\n        this.cost = num(cost);\n        if (isNaN(taxrate)) {\n            this.taxRate = 0;\n        } else {\n            this.taxRate = num(taxrate);\n        }\n        this.taxableAmount = taxableAmount;\n        this.merch = false;\n        this.merchid = null;\n        this.surcharge = false;\n        this.retail = 0; // For ensuring PostalPoint fee collection on office mode shipments\n        this.mailboxNumber = null;\n        this.mailboxDays = 0;\n        this.mailboxMonths = 0;\n        this.category = \"\"; // merch category\n        this.electronicReturnReceipt = false;\n        this.isStamp = false;\n        this.extraData = {};\n    }\n\n    static fromJSON(obj) {\n        var item = new ReceiptItem(obj.id, obj.label, obj.text, obj.priceEach, obj.qty, obj.cost, obj.taxRate, obj.taxableAmount ?? \"\");\n        item.free = obj.free;\n        item.barcode = obj.barcode;\n        item.certifiedInfo = obj.certifiedInfo;\n        item.toAddress = obj.toAddress;\n        item.fromAddress = obj.fromAddress;\n        item.merch = obj.isMerch == true || (typeof obj.merchid == \"string\" && obj.merchid.length > 0);\n        item.merchid = obj.merchid ?? null;\n        item.mailboxNumber = obj.mailboxNumber ?? null;\n        item.mailboxDays = obj.mailboxDays ?? 0;\n        item.mailboxMonths = obj.mailboxMonths ?? 0;\n        item.surcharge = obj.surcharge;\n        item.retailPrice = obj.retail;\n        item.carrier = obj.carrier ?? null;\n        item.service = obj.service ?? null;\n        item.category = obj.category ?? \"\";\n        item.electronicReturnReceipt = obj.electronicReturnReceipt ?? false;\n        item.isStamp = obj.isStamp ?? false;\n        item.extraData = obj.extraData ?? {};\n        return item;\n    }\n\n    toJSON() {\n        return {\n            id: this.id,\n            label: this.label,\n            text: this.text,\n            priceEach: num(this.priceEach),\n            qty: num(this.qty),\n            cost: num(this.cost),\n            retail: num(this.retail),\n            taxRate: num(this.taxRate),\n            taxableAmount: this.taxableAmount,\n            taxTotal: this.taxAmount,\n            free: this.free,\n            barcode: this.barcode,\n            certifiedInfo: this.certifiedInfo,\n            isMerch: this.merch,\n            merchid: this.merchid,\n            surcharge: this.surcharge,\n            toAddress: this.toAddress,\n            fromAddress: this.fromAddress,\n            mailboxNumber: this.mailboxNumber,\n            mailboxDays: this.mailboxDays,\n            mailboxMonths: this.mailboxMonths,\n            carrier: this.carrier,\n            service: this.service,\n            category: this.category,\n            electronicReturnReceipt: this.electronicReturnReceipt,\n            isStamp: this.isStamp,\n            extraData: this.extraData\n        };\n    }\n\n    setExtra(key, val) {\n        this.extraData[key] = val;\n    }\n\n    getExtra(key) {\n        if (typeof this.extraData[key] != \"undefined\") {\n            return this.extraData[key];\n        }\n        return null;\n    }\n\n    get text() {\n        if (typeof this.txt == \"string\") {\n            return this.txt;\n        }\n        return \"\";\n    }\n\n    set text(t) {\n        if (typeof t == \"string\") {\n            this.txt = t;\n        } else {\n            this.txt = \"\";\n        }\n    }\n\n    get certifiedInfo() {\n        if (typeof this.certified == \"undefined\") {\n            return false;\n        }\n        return this.certified;\n    }\n\n    set certifiedInfo(info) {\n        this.certified = info;\n    }\n\n    setCertifiedInfo(tracking, certfee, extrafees, postage, date, location, toaddress) {\n        this.certified = {\n            tracking: tracking,\n            certifiedFee: num(certfee),\n            extraFees: extrafees,\n            postage: num(postage),\n            date: date,\n            location: location,\n            to: toaddress\n        };\n    }\n\n    setQuantity(q) {\n        this.qty = num(q);\n    }\n\n    get free() {\n        return this.isFree == true;\n    }\n\n    set free(free) {\n        this.isFree = free == true;\n    }\n\n    get barcode() {\n        if (typeof this.barcodeData != \"string\") {\n            return \"\";\n        }\n        return this.barcodeData;\n    }\n\n    set barcode(data) {\n        this.barcodeData = data;\n    }\n\n    get linePrice() {\n        return round(m(this.priceEach, this.qty), 2);\n    }\n\n    get priceEachFormatted() {\n        return getCurrencySymbol() + round(num(this.priceEach), 2).toFixed(2);\n    }\n\n    get linePriceFormatted() {\n        return getCurrencySymbol() + round(num(this.linePrice), 2).toFixed(2);\n    }\n\n    get texthtml() {\n        if (typeof this.text != \"string\") {\n            return \"\";\n        }\n        var lines = this.text.split(\"\\n\");\n        for (var i = 0; i < lines.length; i++) {\n            if (lines[i].startsWith(\"Tracking # \")) {\n                // Allow copying tracking number\n                lines[i] = \"Tracking # <span class=\\\"usall\\\">\" + lines[i].replace(\"Tracking # \", \"\") + \"</span>\";\n            }\n        }\n        return lines.join(\"<br />\");\n    }\n\n    get taxAmount() {\n        if (this.taxableAmount == \"markup\") {\n            var lineCost = m(this.cost, this.qty);\n            var margin = s(this.linePrice, lineCost);\n            if (margin <= 0) {\n                return 0;\n            }\n            return round(m(margin, this.taxRate), 2);\n        } else {\n            return round(m(this.linePrice, this.taxRate), 2);\n        }\n    }\n\n    get retailPrice() {\n        if (typeof this.retail == \"number\") {\n            return this.retail;\n        }\n        return this.priceEach * this.qty;\n    }\n\n    set retailPrice(price) {\n        this.retail = num(price);\n    }\n\n}\n
"},{"location":"Docs/Receipt/#globalapisposreceiptpayment","title":"global.apis.pos.ReceiptPayment","text":"
export class ReceiptPayment {\n\n    /**\n     *\n     * @param {number} amount amount paid\n     * @param {string} type payment type\n     * @param {string} text extra data (credit card info, etc)\n     * @returns {ReceiptPayment}\n     */\n    constructor(amount, type, text) {\n        this.id = (Math.random() * 100000000) + \"_\" + type + \"_\" + amount;\n        this.text = (typeof text != \"string\" ? \"\" : text);\n        this.type = type;\n        this.amount = amount;\n        this.extraData = {};\n    }\n\n    static fromJSON(obj) {\n        var item = new ReceiptPayment(obj.amount, obj.type, obj.text);\n        item.id = obj.id;\n        item.extraData = obj.extraData ?? {};\n        return item;\n    }\n\n    toJSON() {\n        return {\n            amount: round(this.amount, 2),\n            type: this.type,\n            text: this.text,\n            id: this.id,\n            extraData: this.extraData ?? {}\n        };\n    }\n\n    setExtra(key, val) {\n        this.extraData[key] = val;\n    }\n\n    getExtra(key) {\n        if (typeof this.extraData[key] != \"undefined\") {\n            return this.extraData[key];\n        }\n        return null;\n    }\n\n    get texthtml() {\n        if (typeof this.text != \"string\") {\n            return \"\";\n        }\n        return this.text.replaceAll(\"\\n\", \"<br />\");\n    }\n\n    get amountFormatted() {\n        return getCurrencySymbol() + this.amount.toFixed(2);\n    }\n\n    get label() {\n        if (typeof this.type != \"string\") {\n            return \"Payment\";\n        }\n        switch (this.type) {\n            case \"cash\":\n                return \"Cash\";\n            case \"check\":\n                return \"Check\";\n            case \"card\":\n                return \"Card\";\n            case \"card_manual\":\n                return \"Card\";\n            case \"account\":\n                return \"Account\";\n            case \"free\":\n                return \"Free\";\n            case \"discount\":\n                return \"Discount\";\n            case \"crypto\":\n                return \"Cryptocurrency\";\n            case \"ach\":\n                return \"ACH Debit\";\n            case \"rounding\":\n                return \"Cash Rounding\"; // Used in penniless countries to balance a cash-only transaction\n            default:\n                return this.type;\n        }\n    }\n}\n
"},{"location":"Docs/ReceiptPrinter/","title":"Receipt Printer driver functions","text":"

PostalPoint abstracts the receipt printer hardware commands, so the same functions are available on all brands and languages of receipt printer, and printer media size and settings are also handled for you.

The drivers operate in line mode, where each successive command appends content to the bottom of the page.

These functions are available on the object supplied by the promise returned from global.apis.print.getReceiptPrinter().

"},{"location":"Docs/ReceiptPrinter/#functions","title":"Functions","text":"
//\n// Add one or more lines of text, with automatic wrapping.\n// If both firsttext and secondtext are provided, two columns of text are generated,\n// with the first left-justified and the second right-justified.\n// `firstjustify` can be \"L\" (left), \"C\" (center), or \"R\" (right).\n// Not all printers support all the formatting options, and may render them in different ways,\n// but the formatting intent is made clear regardless.\naddFieldBlock(firsttext, firstjustify, secondtext = \"\", secondjustify = \"R\", bold = false, doubleheight = false, underline = false);\n\n// Add a blank line to the label.\nnewLine();\n\n// Draw a horizontal line across the page.\ndrawLine();\n\n// Render a Code 128 barcode, centered horizontally, with a human-readable label beneath.\n// Important: this function is sometimes asynchronous depending on the printer driver.\nbarcode128(content);\n\n// Print an image.  Width is in pixels.\n// pixelByteArray is a Uint8Array where each bit is a pixel (1=black, 0=white),\n// starting at the top-left of the image and going across and then down. Use `imageToBitmap` to\n// obtain this data from a Jimp image.\n// Use \"L\" as the position to print on the next line, centered horizontally.\n// Some printers also support position = \"C\", which will\n// ignore other commands and print the image centered on the label,\n// but if you're doing that, just use `global.apis.print.printLabelImage()` instead.\ndrawImage(width, position, pixelByteArray);\n\n// If supported by the printer, opens an attached cash drawer.  Command is ignored if unavailable.\nopenCashDrawer();\n\n// The last command to run, when ready to print. Returns the raw data to send to the printer.\n// Important: this function is sometimes asynchronous depending on the printer driver.\ngetData();\n
"},{"location":"Docs/ReceiptPrinter/#example","title":"Example","text":"
var printer = await global.apis.print.getReceiptPrinter();\n\nprinter.addFieldBlock(\"Hello Bold World!\", \"C\", \"\", \"\", true);\nprinter.drawLine();\nawait printer.barcode128(\"1234567890\");\nprinter.newLine();\n\nawait global.apis.printer.printReceiptData(await printer.getData());\n
"},{"location":"Docs/TrackingBarcode/","title":"TrackingBarcode class","text":"

For your reference, here is the source code of the TrackingBarcode class, used to represent a prepaid drop-off. This class is provided to plugins as global.apis.barcode.TrackingBarcode.

export class TrackingBarcode {\n    /**\n     * Create a tracking barcode object.\n     * @param {string} code Tracking number.\n     * @returns {TrackingBarcode}\n     */\n    constructor(code) {\n        // All data are optional except for the tracking number. Missing data is gracefully handled by the PostalPoint UI.\n        this.cleanCode = code;\n        // Original barcode data this was created from\n        this.barcode = code;\n        // Destination ZIP Code, for domestic shipments. The city and state are automatically added.  If toAddress is specified, toZip is ignored in favor of it.\n        this.toZip = \"\";\n        // Two-letter destination country code.  If not \"US\", toZip is ignored, and the full country name is appended to the displayed address information.\n        this.toCountry = defaultCountryCode();\n        // If toAddress is set, it will be used instead of the toZip when displaying the destination.\n        // If both toZip and toAddress are empty strings, no destination will be displayed.\n        this.toAddress = \"\";\n        // If message is not empty, the barcode will NOT be added and the message will be displayed to the user.\n        this.message = \"\";\n        // If warning is not empty, the barcode WILL be added and a message will be displayed to the user.\n        this.warning = \"\";\n        // Shipping carrier name.\n        this.carrier = \"\";\n        // Shipping service/mail class full name and description. Example: \"Priority Mail Adult Signature Required\".\n        this.serviceName = \"\";\n        // Shipping service/mail class name, without extra info such as \"signature required\".\n        // Example: \"Priority Mail\"\n        this.serviceShort = \"\";\n        // If set to false, the barcode will be rejected with a suitable message when PostalPoint is running in self-serve kiosk mode.\n        this.dropoff = true;\n        // If false, app may prompt user to specify the shipping carrier\n        this.confidentCarrier = true;\n        // Extra description strings, like \"Signature Required\"\n        this.extraInfo = [];\n    }\n\n    /**\n     * Set the tracking number\n     * @param {string} str\n     * @returns {undefined}\n     */\n    set tracking(str) {\n        this.cleanCode = str;\n    }\n\n    /**\n     * Set the service/mail class description string.\n     * @param {string} str\n     * @returns {undefined}\n     */\n    set service(str) {\n        this.serviceShort = str;\n        this.serviceName = str;\n    }\n\n    /**\n     * Get the tracking number.\n     * @returns {String}\n     */\n    get tracking() {\n        return this.cleanCode;\n    }\n\n    /**\n     * Get the destination ZIP code.\n     * @returns {String}\n     */\n    get zip() {\n        return this.toZip;\n    }\n\n    /**\n     * Get the service/mail class description.\n     * @returns {String}\n     */\n    get service() {\n        if (this.serviceShort != \"\") {\n            return this.serviceShort;\n        } else if (this.serviceName != \"\") {\n            return this.serviceName;\n        }\n        return \"\";\n    }\n\n    /**\n     * Get the carrier and service info.\n     * @returns {String}\n     */\n    get serviceString() {\n        var str = [];\n        if (this.carrier != \"\") {\n            str.push(this.carrier);\n        }\n        if (this.serviceShort != \"\") {\n            str.push(this.serviceShort);\n        } else if (this.serviceName != \"\") {\n            str.push(this.serviceName);\n        }\n        return str.join(\" \");\n    }\n\n    /**\n     * Get the destination information as a human-presentable multiline string.\n     * @returns {String}\n     */\n    get destString() {\n        var addressLines = [];\n        if (this.toAddress != \"\") {\n            addressLines.push(...this.toAddress.split(\"\\n\"));\n        }\n        if (defaultCountryCode() == this.toCountry.toUpperCase() && this.toCountry.toUpperCase() == \"US\" && this.toZip != \"\" && this.toAddress == \"\") {\n            // Domestic shipment within USA, look up ZIP code\n            var zipdata = getZIP(this.toZip);\n            if (zipdata != false) {\n                addressLines.push(`${zipdata.city} ${zipdata.state} ${this.toZip}`);\n            } else {\n                addressLines.push(`${this.toZip}`);\n            }\n        } else if (defaultCountryCode() == this.toCountry.toUpperCase()) {\n            // Domestic shipment, outside USA, add postal code line if we have one\n            if (this.toZip != \"\" && this.toAddress.includes(this.toZip) != true) {\n                addressLines.push(`${this.toZip}`);\n            }\n        } else {\n            // International shipment, add country name\n            addressLines.push(getCountryNameForISO(this.toCountry));\n        }\n        return addressLines.join(\"\\n\");\n    }\n\n    /**\n     * Get the package information in a format suitable for display on a receipt.\n     * @param {boolean} includeTrackingNumber If false, the tracking number will be suppressed.\n     * @returns {String}\n     */\n    toString(includeTrackingNumber = true) {\n        var lines = [];\n        if (includeTrackingNumber && this.cleanCode) {\n            lines.push(this.cleanCode);\n        }\n        var serv = this.serviceString;\n        if (serv != \"\") {\n            lines.push(serv);\n        }\n        var dest = this.destString;\n        if (dest != \"\") {\n            var destlines = dest.split(\"\\n\");\n            destlines[0] = \"To \" + destlines[0];\n            lines.push(...destlines);\n        }\n\n        if (typeof this.extraInfo == \"object\" && this.extraInfo.length > 0) {\n            lines.push(...this.extraInfo);\n        }\n\n        return lines.join(\"\\n\");\n    }\n}\n
"},{"location":"Examples/01Minimal/","title":"Minimal Plugin","text":"

This is the smallest possible valid plugin.

plugin-name/plugin.js
exports.init = function () {\n    global.apis.alert(\"This message appears when PostalPoint launches.\", \"Hello!\");\n};\n
"},{"location":"Examples/02Basic/","title":"Basic Plugin","text":"

This sample plugin showcases some of the features many plugins will want to use.

Download JavaScript

plugin.js
// Sample plugin to demonstrate plugin capabilities and structure.\n\nasync function getPage() {\n    // A Framework7 component page\n    return global.apis.getPluginFolder(\"basic-demo\") + \"/uipluginpage.f7\";\n}\n\n// This is run when PostalPoint loads the plugin at launch.\n// Use it to register for things you want to do, like adding a page, hooking into payments or shipping rates, etc.\nexports.init = function () {\n    console.log(global.apis.settings.get(\"basic-demo_secretcode\"));\n    global.apis.ui.addToolsPage(getPage, \"Sample Page Title\", \"sampletool1234\", \"A sample plugin page\", \"Sample\", \"fa-solid fa-circle\");\n};\n\n// This defines a settings UI to display for the plugin.\n// If exports.config is a function instead of an array, it will be executed when opening the settings\n// and must return an array like the one below.\n// If exports.config is undefined, a settings menu will not be provided to the user.\nexports.config = [\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.alert(\"Button pressed\");\n        }\n    },\n    {\n        type: \"text\",\n        key: \"app.postalpoint.basic-demo_somestring\", // Try to make sure this is unique by using a prefix,\n        // settings storage is global so there could be conflicts if you aren't careful\n        defaultVal: \"\",\n        label: \"Type a string\",\n        placeholder: \"\",\n        text: \"Description text next to the input box\",\n        sync: false // Add sync: false to prevent automatically syncing this setting between\n                    // PostalPoint installations (i.e. it's a device-specific setting, like a pairing code)\n                    // If it's not present, or is any truthy value, it could be synced between PCs\n    },\n    {\n        type: \"password\",\n        key: \"app.postalpoint.basic-demo_secretcode\",\n        defaultVal: \"\",\n        label: \"Secret Code\",\n        placeholder: \"\",\n        text: \"Don't tell anyone this secret code:\"\n    },\n    {\n        type: \"textarea\",\n        key: \"app.postalpoint.basic-demo_sometext\",\n        defaultVal: \"\",\n        label: \"Text Box\",\n        placeholder: \"...\",\n        text: \"You can type a few lines of text here.\"\n    },\n    {\n        type: \"select\",\n        key: \"app.postalpoint.basic-demo_dropdownbox\",\n        defaultVal: \"\",\n        label: \"Choose an option\",\n        placeholder: \"\",\n        text: \"\",\n        options: [[\"key1\", \"Value 1\"], [\"key2\", \"Value 2\"]]\n    }\n];\n

Download HTML

uipluginpage.f7
<template>\n    <div class=\"page\">\n        <div class=\"navbar\">\n            <div class=\"navbar-bg\"></div>\n            <div class=\"navbar-inner\">\n                <div class=\"title\">${title}</div>\n            </div>\n        </div>\n        <div class=\"page-content\">\n            <a class=\"button\" @click=${openAlert}>Open Alert</a>\n            <a class=\"button\" @click=${printSomething}>Print Something</a>\n            <div class=\"list simple-list\">\n                <ul>\n                    ${names.map((name) => $h`\n                    <li>${name}</li>\n                    `)}\n                </ul>\n            </div>\n        </div>\n    </div>\n</template>\n<!-- component styles -->\n<style>\n    .red-link {\n        color: red;\n    }\n</style>\n<!-- rest of component logic -->\n<script>\n    // script must return/export component function\n    export default (props, { $f7, $on }) => {\n        const title = 'Component Page';\n        const names = ['John', 'Vladimir', 'Timo'];\n\n        const openAlert = () => {\n            $f7.dialog.alert('Hello world!\\nblah blah blah');\n        }\n\n        async function printSomething() {\n            // Print some text to the receipt printer\n            var printer = await global.apis.print.getReceiptPrinter();\n            printer.addFieldBlock('Hello world!\\nblah blah blah\\n\\n', \"C\");\n            global.apis.print.printReceiptData(await printer.getData());\n        }\n\n        $on('pageInit', () => {\n            // do something on page init\n        });\n        $on('pageAfterOut', () => {\n            // page has left the view\n        });\n\n        // component function must return render function\n        return $render;\n    }\n</script>\n
"},{"location":"Examples/03Shipping/","title":"Shipping Plugin","text":"

This sample plugin demonstrates how to add support for a custom shipping carrier.

Download JavaScript

plugin.js
// This is a sample PostalPoint plugin for adding support for a shipping carrier.\n\nvar rateCache = [];\nvar parcelCache = {};\n\nexports.init = function () {\n    // Add support for shipping rating and label purchasing\n    global.apis.shipping.registerRateEndpoint(getRates, purchase, \"uniqueprefixhere_\");\n\n    // Add support for prepaid drop-offs\n    global.apis.barcode.onPrepaidScan(function (barcode) {\n        if (barcode.startsWith(\"mycarrierbarcode\")) { // Replace this with your checks for barcode validity\n            var data = new global.apis.barcode.TrackingBarcode(barcode);\n            data.carrier = \"Carrier Name\";\n            data.service = \"Service Name\";\n            return data;\n        }\n        return false;\n    });\n}\n\nasync function purchase(rateid) {\n    for (var i = 0; i < rateCache.length; i++) {\n        if (rateCache[i].rateid == rateid) {\n            var rate = rateCache[i];\n            //\n            // Fetch label and tracking and such\n            //\n            var label;\n            var tracking = \"123456\";\n            var toAddressLines = parcelCache.toAddress.toStringArray();\n\n            // Create receipt item\n            var receiptitem = new global.apis.pos.ReceiptItem(`uniqueprefixhere_${tracking}`,\n                    `${rate.carrierName} ${rate.serviceName}`,\n                    `Tracking # ${global.apis.util.string.chunk(tracking, 3).join(\" \")}\\nTo:\\n${toAddressLines.join(\"\\n\")}`,\n                    rate.retail_rate, 1, rate.cost_rate, 0\n                    );\n            receiptitem.barcode = tracking;\n            receiptitem.carrier = \"Carrier Name\";\n            receiptitem.service = \"Service Name\";\n\n            return {\n                label: label,\n                labeltype: \"PNG\",\n                receiptItem: receiptitem,\n                tracking: tracking,\n                cost: rate.cost_rate,\n                price: rate.retail_rate,\n                carrier: rate.carrierName,\n                service: rate.serviceName,\n                delivery_days: rate.delivery_days,\n                delivery_date: rate.delivery_date,\n                to: toAddressLines\n            };\n        }\n    }\n}\n\nasync function getRates(parcel) {\n    // parcel is an object as shown in docs/Parcel.md\n    var rates = [];\n    rates.push({\n        rateid: \"uniqueprefixhere_\" + global.apis.util.uuid.v4(),\n        carrier: \"Carrier\",\n        carrierName: \"Carrier Name\",\n        service: \"CARRIER_SERVICE_ID\",\n        cost_rate: 10,\n        retail_rate: 15,\n        delivery_days: 3,\n        delivery_date: null,\n        guaranteed: true,\n        serviceName: \"Service Name\",\n        color: \"green\" // Rate card color\n    });\n\n    // Save details for later use if purchased\n    rateCache = rates;\n    parcelCache = parcel;\n\n    return rates;\n}\n
"},{"location":"Examples/04CardProcessor/","title":"Card Payments","text":"

This plugin template shows how to implement a card payment processor, allowing PostalPoint to interface with your card reader/terminal hardware, as well as saving, retrieving, and charging cards on file.

Download JavaScript

plugin-name/plugin.js
// This is a sample PostalPoint plugin that adds a card payment processor.\n\nexports.init = function () {\n    global.apis.pos.registerCardProcessor({\n        name: \"Demo Card Processor\",\n        init: async function () {\n            // This function runs once after starting PostalPoint\n            // and before any other card processor functions are called.\n        },\n        checkout: async function({amount, capture = true}) {\n            // amount is an integer number of pennies.\n\n            // If an error is encountered during processing,\n            //    display an error message in a dialog and return boolean false.\n            //    If this function returns anything except false or undefined, and doesn't throw an error,\n            //    it is assumed the payment was successful.\n            try {\n                if (capture) {\n                    // authorize, capture, add a ReceiptPayment to the receipt, and return boolean true.\n                    global.apis.pos.addOnscreenPaymentLog(\"Getting card payment...\"); // Add a line to the onscreen card processing status log\n                    await global.apis.util.delay(1000); // Replace this with something useful!\n                    global.apis.pos.addReceiptPayment(\n                        new global.apis.pos.ReceiptPayment(\n                            (amount / 100).toFixed(2) * 1,\n                            \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.  Other types will be displayed as-is to the user and on the receipt.\n                            \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n                        )\n                    );\n                    global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n                    return true;\n                } else {\n                    // only authorize the payment, don't actually capture/charge the payment method,\n                    // and return whatever transaction data that will be passed to finishPayment to capture the payment.\n                    await global.apis.util.delay(1000); // Replace this with something useful!\n                    return {amount: amount};\n                }\n            } catch (ex) {\n                global.apis.pos.addOnscreenPaymentLog(`Error: ${ex.message} [okay to put extra details here for troubleshooting or tech support, it's visible to the cashier]`);\n                if (global.apis.kiosk.isKiosk()) {\n                    // This message will be shown to an end-user/customer, not a cashier/employee\n                    global.apis.alert(\"Your card was declined.\", \"Card Error\");\n                } else {\n                    global.apis.alert(\"The customer's card was declined.\", \"Card Error\");\n                }\n                return false;\n            }\n        },\n        cancelCheckout: function () {\n            // The user requested to cancel the payment.\n            // Reset the terminal to its resting state, clear its screen, etc.\n        },\n        finishPayment: async function ({checkoutResponse}) {\n            // Finish a payment that was authorized but not captured because checkout was called with capture = false\n            // If payment was already captured and added to the receipt for some reason, just return true.\n            await global.apis.util.delay(1000); // Replace this with something useful!\n            global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                    (checkoutResponse.amount / 100).toFixed(2) * 1,\n                    \"card\", // Payment type.\n                    \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n                )\n            );\n            return true;\n        },\n        updateCartDisplay: function (receipt) {\n            // Show transaction data on the card reader display.\n            // This function will be called when the cart or total changes.\n            console.log(receipt);\n            // Sample structure of the receipt variable:\n            receipt = {\n                \"items\": [\n                    {\n                        \"id\": \"testitem\",\n                        \"label\": \"Test Item\",\n                        \"text\": \"\",\n                        \"priceEach\": 2,\n                        \"qty\": 1,\n                        \"cost\": 0,\n                        \"retail\": 2,\n                        \"taxRate\": 0.1,\n                        \"free\": false,\n                        \"barcode\": \"\",\n                        \"certifiedInfo\": false,\n                        \"isMerch\": true,\n                        \"surcharge\": false\n                    },\n                    {\n                        \"id\": \"9100123456789012345678\",\n                        \"label\": \"Test Package\",\n                        \"text\": \"Package Details\\nTracking # 9100 1234 5678 9012 3456 78\\nTo:\\nTEST PERSON\\nORGANIZATION INC\\n123 TEST ROAD\\nTESTTOWN TE 99999-0001\",\n                        \"priceEach\": 8,\n                        \"qty\": 1,\n                        \"cost\": 0,\n                        \"retail\": 8,\n                        \"taxRate\": 0,\n                        \"free\": false,\n                        \"barcode\": \"9100123456789012345678\",\n                        \"certifiedInfo\": false,\n                        \"isMerch\": false,\n                        \"surcharge\": false,\n                        \"toAddress\": {\n                            \"name\": \"TEST PERSON\",\n                            \"company\": \"ORGANIZATION INC\",\n                            \"street1\": \"123 TEST ROAD\",\n                            \"street2\": null,\n                            \"city\": \"TESTTOWN\",\n                            \"state\": \"TE\",\n                            \"zip\": \"99999-0001\",\n                            \"email\": null,\n                            \"phone\": null,\n                            \"country\": \"US\"\n                        },\n                        \"fromAddress\": {\n                            \"name\": \"TEST PERSON\",\n                            \"company\": \"ORGANIZATION INC\",\n                            \"street1\": \"123 TEST ROAD\",\n                            \"street2\": null,\n                            \"city\": \"TESTTOWN\",\n                            \"state\": \"TE\",\n                            \"zip\": \"99999-0001\",\n                            \"email\": null,\n                            \"phone\": null,\n                            \"country\": \"US\"\n                        }\n                    }\n                ],\n                \"payments\": [\n                    {\n                        \"amount\": 10,\n                        \"amountFormatted\": \"$10.00\",\n                        \"type\": \"cash\",\n                        \"label\": \"Cash\",\n                        \"text\": \"\",\n                        \"texthtml\": \"\",\n                        \"id\": \"12345678_cash_10\"\n                    },\n                    {\n                        \"amount\": 12.34,\n                        \"amountFormatted\": \"$12.34\",\n                        \"type\": \"card\",\n                        \"label\": \"Card\",\n                        \"text\": \"Card Details here\\n1234abcd\",\n                        \"texthtml\": \"Card Details here<br />1234abcd\",\n                        \"id\": \"87654321_card_12.34\"\n                    }\n                ],\n                \"subtotal\": 10,\n                \"subtotalFormatted\": \"$10.00\",\n                \"tax\": 0.2,\n                \"taxFormatted\": \"$0.20\",\n                \"grandTotal\": 10.2,\n                \"grandTotalFormatted\": \"$10.20\",\n                \"paid\": 22.34,\n                \"paidFormatted\": \"$22.34\",\n                \"due\": -12.14, // If negative, is the amount of change owed to the customer instead\n                \"dueFormatted\": \"$12.14\"\n            };\n        },\n        checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {\n            // Same as checkout() except using a payment method already on file.\n            // customerID and paymentMethodID are provided by getSavedPaymentMethods below.\n            await global.apis.util.delay(1000); // Replace this with something useful!\n            var error = false;\n            if (error) {\n                // If you can't charge the payment method, throw an Error with a string to display to the user.\n                throw new Error(\"The saved card didn't work.\");\n            }\n            global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                    (amount / 100).toFixed(2) * 1,\n                    \"card\", // Payment type.\n                    \"Card on File\\nx1234\" // Additional text for receipt\n                )\n            );\n            // Must return true upon success.\n            // If the payment is not successful, and you didn't throw an Error to show the user,\n            // then `return false` instead and it'll appear that the user's action to start the payment did nothing.\n            return true;\n        },\n        saveCardForOfflineUse: async function ({statusCallback, customerUUID, name, company, street1, street2, city, state, zip, country, email, phone}) {\n            // Use the card reader to capture an in-person card and save it for offline use.\n            // Provided details are the customer's info, which might be empty strings except for the customerUUID.\n            // Saved card details must be tied to the customerUUID, as that's how saved cards are looked up.\n\n            // statusCallback(string, boolean) updates the progress message on the cashier's screen.\n            // If the boolean is true, the progress message is replaced with a confirmation message.\n            statusCallback(\"Insert the card into the reader.\", false);\n\n            await global.apis.util.delay(1000); // Wait for the customer to insert their card,\n            //then save it for later offline billing\n\n            statusCallback(\"Saving card details...\", false);\n\n            await global.apis.util.delay(1000);\n\n            statusCallback(\"Card saved!\", true);\n\n            return true; // Card saved to customer\n            // If an error occurred, you can throw it and the error message will be displayed to the cashier.\n            // Alternatively, return boolean false and display the error yourself with global.apis.alert(message, title) or something.\n        },\n        cancelSaveCardForOfflineUse: function () {\n            // Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.\n        },\n        getSavedPaymentMethods: async function ({customerUUID}) {\n            // Return all saved payment methods tied to the provided customer UUID.\n            var methods = [];\n            methods.push({\n                customer: \"<internal string referencing the customer>\", // Passed to checkoutSavedMethod as customerID\n                customer_uuid: customerUUID,\n                id: \"<card/payment method identifier>\", // Passed to checkoutSavedMethod as paymentMethodID\n                type: \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.\n                label: \"Visa debit x1234 (exp. 12/29)\", // Label for payment method\n                label_short: \"Visa debit x1234\" // Abbreviated label for payment method\n            });\n            return methods;\n        },\n        deleteSavedPaymentMethod: async function ({customerUUID, customerID, paymentMethodID}) {\n            // Delete the payment method identified by paymentMethodID and tied to the PostalPoint customerUUID and the card processor customerID.\n            // If unable to delete, throw an error and the error message will be displayed to the cashier.\n            await global.apis.util.delay(1000);\n        }\n    });\n}\n\n// Plugin settings to display.\nexports.config = [\n    {\n        type: \"password\",\n        key: \"democardprocessor_apikey\",\n        defaultVal: \"\",\n        label: \"API Key\",\n        placeholder: \"\",\n        text: \"API Key\"\n    },\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.ui.openSystemWebBrowser(\"https://postalpoint.app\");\n        }\n    }\n];\n
"},{"location":"Examples/05CryptoProcessor/","title":"Cryptocurrency Payments","text":"

This plugin template shows how to implement a cryptocurrency payment processor, using the customer-facing display to show a payment QR code webpage.

Download JavaScript

plugin-name/plugin.js
// This is a sample PostalPoint plugin that adds a card payment processor.\n\nexports.init = function () {\n    global.apis.pos.registerCryptoProcessor({\n        name: \"Demo Crypto\",\n        init: async function () {\n            // This is run after PostalPoint starts, and before any other crypto functions are called.\n        },\n        checkout: async function ( {amount}) {\n            // Run the checkout process.\n            // amount is the amount of USD to collect, in pennies.\n\n            // If an error is encountered during processing,\n            //    display an error message in a dialog and return boolean false.\n            //    If this function returns anything except false or undefined, and doesn't throw an error,\n            //    it is assumed the payment was successful.\n\n            // Adds a line of text visible to the cashier\n            global.apis.pos.addOnscreenPaymentLog(\"Getting crypto payment...\");\n\n            // Display a web page (i.e. with a payment QR code) to the customer on the customer-facing display.\n            global.apis.ui.setCustomerScreen(\"<html></html>\", \"html\");\n            global.apis.ui.setCustomerScreen(\"https://postalpoint.app\", \"raw\");\n\n            // Poll the status of the crypto transaction\n            var paymentComplete = false;\n            do {\n                await global.apis.util.delay(1000);\n                paymentComplete = true;\n            } while (paymentComplete != true);\n\n            global.apis.pos.addReceiptPayment(\n                    new global.apis.pos.ReceiptPayment(\n                            (amount / 100).toFixed(2) * 1,\n                            \"crypto\", // Payment type.\n                            \"Bitcoin\\n0.00001234 BTC\" // Additional text for receipt\n                            )\n                    );\n            global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n            global.apis.ui.clearCustomerScreen();\n        },\n        cancelCheckout: function () {\n            // The user requested to cancel the payment.\n            // Reset things accordingly.\n            global.apis.ui.clearCustomerScreen();\n        },\n        isConfigured: function () {\n            // Is this plugin properly setup and able to process payments?  If not, return false.\n            return true;\n        }\n    });\n}\n\n// Plugin settings to display.\nexports.config = [\n    {\n        type: \"password\",\n        key: \"democryproprocessor_apikey\",\n        defaultVal: \"\",\n        label: \"API Key\",\n        placeholder: \"\",\n        text: \"API Key\"\n    },\n    {\n        type: \"button\",\n        label: \"Test Button\",\n        text: \"Some text about the button\",\n        onClick: function () {\n            global.apis.ui.openSystemWebBrowser(\"https://postalpoint.app\");\n        }\n    }\n];\n
"},{"location":"Plugin%20API/barcode/","title":"Barcode","text":""},{"location":"Plugin%20API/barcode/#barcode-object","title":"barcode : object","text":"

Handle tracking barcodes

Kind: global namespace

"},{"location":"Plugin%20API/barcode/#barcodetrackingbarcode","title":"barcode.TrackingBarcode","text":"

Kind: static class of barcode Properties

Name Type Description tracking string Tracking number barcode string Original barcode data this was created from toZip string Destination ZIP Code, for domestic shipments. The city and state are automatically added. If toAddress is specified, toZip is ignored in favor of it. toCountry string Two-letter destination country code. If it doesn't match the country PostalPoint is running in, the full country name is appended to the displayed address information. toAddress string Destination mailing/shipping address. carrier string Shipping carrier name. service string Shipping service/mail class name. Example: \"Priority Mail\". dropoff boolean If set to false, the barcode will be rejected with a suitable message when PostalPoint is running in self-serve kiosk mode. confidentCarrier boolean If false, PostalPoint may prompt user to specify the shipping carrier. extraInfo Array.<string> Extra description strings, like \"Signature Required\". message string If not empty, the barcode will NOT be added and the contents of message will be displayed to the user. warning string If not empty, the barcode WILL be added and the contents of warning will be displayed to the user. destString string (read only) Get the destination information as a human-presentable multiline string. serviceString string (read only) Get the carrier and service. toString() function Get the package information in a format suitable for display on a receipt. toString(false) function Get the package information in a format suitable for display on a receipt, suppressing the tracking number.

"},{"location":"Plugin%20API/barcode/#new-trackingbarcodecode","title":"new TrackingBarcode(code)","text":"

A Tracking barcode object.

Param Type Description code string Barcode data

"},{"location":"Plugin%20API/barcode/#barcodeaddprepaidbarcodetrackingbarcodedata","title":"barcode.addPrepaidBarcode(trackingBarcodeData)","text":"

Add a TrackingBarcode object to the transaction receipt at any time other than onPrepaidScan.

Kind: static method of barcode

Param Type trackingBarcodeData TrackingBarcode

"},{"location":"Plugin%20API/barcode/#barcodeinjectbarcodedata","title":"barcode.inject(barcodeData)","text":"

Pass data to the internal barcode event subsystem. The data is handled as if it were just received from a physical barcode scanner.

Kind: static method of barcode

Param Type barcodeData string

"},{"location":"Plugin%20API/barcode/#barcodeonprepaidscanf","title":"barcode.onPrepaidScan(f)","text":"

The function passed to onPrepaidScan is run when a barcode is scanned on the Prepaid page. The function is passed one argument, a string containing the raw barcode data. The function shall return boolean false if unable or unwilling to handle the barcode. If the barcode is handled by this function, it shall return a TrackingBarcode object.

Kind: static method of barcode

Param Type f function

"},{"location":"Plugin%20API/barcode/#barcoderegisterdropoffcarrierscanhandlercarrier-fn","title":"barcode.registerDropOffCarrierScanHandler(carrier, fn)","text":"

Register to handle prepaid drop off scans for a particular shipping carrier. Scans are kept in a local, disk-backed queue and the function registered here will be called when a queued barcode is processed for the provided carrier. This function is intended for carrier drop-off reimbursement programs such as ASO and FASC.

Kind: static method of barcode Throws:

Param Type Description carrier string Carrier name to register for. fn function Async function to pass scan details to. Returns true if processed, false if not processed (but the barcode should be removed from queue), or throws an Error if it should be retried later. See example for data and usage.

Example

global.apis.barcode.registerDropOffCarrierScanHandler(\"FedEx\", function (data) {\n    global.apis.alert(`Carrier: ${data.carrier}, Tracking number: ${data.tracking}, `\n        + `Raw scanned barcode: ${data.barcode}, `\n        + `UNIX timestamp of scan: ${data.timestamp}, Scan UUID: ${data.uuid}`,\n        \"Processing DropOffCarrierScan data\");\n\n    return false; // Not processed but should be discarded\n    return true; // Processed, discard from queue\n    throw new Error(\"Failed to process, try again later\");\n});\n

"},{"location":"Plugin%20API/database/","title":"Database","text":""},{"location":"Plugin%20API/database/#database-object","title":"database : object","text":"

Database connection

Kind: global namespace

"},{"location":"Plugin%20API/database/#databasegetconnection-promisedatabaseadapter","title":"database.getConnection() \u21d2 Promise.<DatabaseAdapter>","text":"

Return a database connection object to run SQL against the store database. See the Database docs for details.

Kind: static method of database

"},{"location":"Plugin%20API/fs/","title":"Fs","text":""},{"location":"Plugin%20API/fs/#fs-object","title":"fs : object","text":"

Basic filesystem access utility functions, wrapping Node.JS and/or NW.JS code.

Kind: global namespace

"},{"location":"Plugin%20API/fs/#fsopenfilesavedialogsuggestedfilename-fileextensions-promisestringnull","title":"fs.openFileSaveDialog(suggestedFilename, fileExtensions) \u21d2 Promise.<(string|null)>","text":"

Open a file save as dialog prompting the user to save a file. Opens to the user's Documents folder, with sane fallbacks if it cannot be located.

Kind: static method of fs Returns: Promise.<(string|null)> - The full file path the user selected, or null if they cancelled.

Param Type Description suggestedFilename string The filename string to pre-fill in the dialog. fileExtensions string The file type filter to show. Examples: \".csv\", \".csv,.html\"

"},{"location":"Plugin%20API/fs/#fsopenfilebrowsedialogchoosefolder-accept-dialogtitle-string-null","title":"fs.openFileBrowseDialog(chooseFolder, accept, dialogTitle) \u21d2 string | null","text":"

Open a file browse/file open dialog prompting the user to select a file or folder. Opens to the user's Documents folder, with sane fallbacks if it cannot be located.

Kind: static method of fs Returns: string | null - The selected file/folder path, or null if cancelled.

Param Type Default Description chooseFolder boolean false Set to true to choose a folder instead of a file. accept string File filter. \".csv,.html\", \"image/*\", etc. dialogTitle string | null null Title of the file open dialog.

"},{"location":"Plugin%20API/fs/#fswritefilefilename-data-encoding-flag-promise","title":"fs.writeFile(filename, data, encoding, flag) \u21d2 Promise","text":"

Write a file to disk.

Kind: static method of fs

Param Type Default Description filename string The path and filename to write to. data string | Buffer | ArrayBuffer | Uint8Array Data to write to the file. encoding string | null \"utf8\" Text encoding. Set to empty if not passing string data. flag string | null \"w+\" Filesystem flag.

"},{"location":"Plugin%20API/fs/#fsreadfilefilename-encoding-flag-promisestringbuffer","title":"fs.readFile(filename, encoding, flag) \u21d2 Promise.<(string|Buffer)>","text":"

Read a file from disk and return its contents.

Kind: static method of fs

Param Type Default Description filename string The path and filename to read from. encoding string \"utf8\" File encoding. Set to null or empty string when reading binary data. flag string \"r+\" Filesystem flag.

"},{"location":"Plugin%20API/fs/#fsfileexistsfilename-boolean","title":"fs.fileExists(filename) \u21d2 boolean","text":"

Check if a file exists.

Kind: static method of fs

Param Type Description filename string Path and filename to check."},{"location":"Plugin%20API/global%20functions/","title":"Global functions","text":""},{"location":"Plugin%20API/global%20functions/#f7","title":"f7","text":"

The Framework7 app instance for PostalPoint's entire UI, created by new Framework7(). See https://framework7.io/docs/app for details.

Kind: global constant

"},{"location":"Plugin%20API/global%20functions/#getpluginfolderid-string","title":"getPluginFolder([id]) \u21d2 string","text":"

Get the filesystem path to a plugin's installation folder.

Kind: global function Returns: string - \"/home/user/.config/postalpoint-retail/Default/storage/plugins/...\", \"C:\\Users\\user\\AppData...\", etc

Param Type Description [id] string Plugin ID. If omitted or empty, will return the parent folder plugins are installed within.

"},{"location":"Plugin%20API/global%20functions/#getappfolder-string","title":"getAppFolder() \u21d2 string","text":"

Get the filesystem path to the PostalPoint installation folder.

Kind: global function

"},{"location":"Plugin%20API/global%20functions/#alerttext-title-callback","title":"alert(text, title, [callback])","text":"

Display a simple alert-style dialog box.

Kind: global function

Param Type Default Description text string Body text of the dialog. title string Dialog title. [callback] function Function to call when the alert is closed."},{"location":"Plugin%20API/graphics/","title":"Graphics","text":""},{"location":"Plugin%20API/graphics/#graphics-object","title":"graphics : object","text":"

PostalPoint uses the Jimp library version 1.6 for creating and manipulating images and shipping labels.

Kind: global namespace

"},{"location":"Plugin%20API/graphics/#graphicsjimp-jimp","title":"graphics.Jimp() \u21d2 Jimp","text":"

The JavaScript Image Manipulation Program.

Kind: static method of graphics Example

const {Jimp} = global.apis.graphics.Jimp();\n

"},{"location":"Plugin%20API/graphics/#graphicsloadfontfilename-promise","title":"graphics.loadFont(filename) \u21d2 Promise","text":"

Replacement for Jimp's loadFont function, which gets very confused about our JS environment and ends up crashing everything.

Kind: static method of graphics

Param Type filename string"},{"location":"Plugin%20API/httpserver/","title":"Httpserver","text":""},{"location":"Plugin%20API/httpserver/#httpserver-object","title":"httpserver : object","text":"

Add features to PostalPoint's integrated LAN HTTP API server.

Kind: global namespace

"},{"location":"Plugin%20API/httpserver/#httpserveraddendpointid-oncall","title":"httpserver.addEndpoint(id, onCall)","text":"

Add a custom HTTP JSON POST endpoint to the LAN HTTP API server running inside PostalPoint. Requests must be POSTed and contain a JSON body (or empty body, which will be converted to null).

Kind: static method of httpserver

Param Type Description id string Endpoint ID. Used in URL, for example: http://<host>:7678/<id> onCall function Async function to call when the endpoint is called, which returns the response.

Example

global.apis.httpserver.addEndpoint(\"testendpoint\", async function (request) {\n    if (request.abc == \"123\") {\n        // A non-string `body` is converted to JSON before the HTTP reply is sent.\n        return {body: {json: true, abc: 123}, httpcode: 200, contentType: \"application/json\"};\n    }\n    // A string `body` is sent to the client as-is using whatever contentType you specify.\n    return {body: \"abc\", httpcode: 200, contentType: \"text/plain\"};\n});\n

"},{"location":"Plugin%20API/httpserver/#httpservergetserverport-number","title":"httpserver.getServerPort() \u21d2 number","text":"

Get the local HTTP server's port number.

Kind: static method of httpserver

"},{"location":"Plugin%20API/httpserver/#httpservergetclientkey-string","title":"httpserver.getClientKey() \u21d2 string","text":"

Get the local machine's HTTP client key it uses to authenticate with other installations of PostalPoint on the LAN.

Kind: static method of httpserver

"},{"location":"Plugin%20API/httpserver/#httpserversendrequesttoremotedata-endpointid-serveraddress-serverport-promiseobject","title":"httpserver.sendRequestToRemote(data, endpointID, serverAddress, serverPort) \u21d2 Promise.<Object>","text":"

Send a HTTP request to another PostalPoint installation on the local network.

Kind: static method of httpserver Returns: Promise.<Object> - The JSON reply. Throws:

Param Type Description data Object Data to encode as JSON in the request body. endpointID string Endpoint to call. serverAddress string | undefined Address of the PostalPoint server. If undefined, uses the host address configured in PostalPoint's Databases settings. serverPort number | undefined Port of the PostalPoint server. If undefined, the default PostalPoint port number is used."},{"location":"Plugin%20API/i18n/","title":"I18n","text":""},{"location":"Plugin%20API/i18n/#i18n-object","title":"i18n : object","text":"

Functions to help support multiple currencies and countries.

Kind: global namespace

"},{"location":"Plugin%20API/i18n/#i18ncountry-string","title":"i18n.country() \u21d2 string","text":"

Get the 2-character ISO country code that PostalPoint is running in.

Kind: static method of i18n Returns: string - \"US\", \"CA\", etc.

"},{"location":"Plugin%20API/i18n/#i18ncurrency-string","title":"i18n.currency() \u21d2 string","text":"

Get the 3-character currency code in use.

Kind: static method of i18n Returns: string - \"usd\", \"cad\", etc.

"},{"location":"Plugin%20API/i18n/#i18nsymbol-string","title":"i18n.symbol() \u21d2 string","text":"

Get the currency symbol.

Kind: static method of i18n Returns: string - \"$\", \"\u20ac\", \"\u20a4\", etc

"},{"location":"Plugin%20API/i18n/#i18ndecimals-number","title":"i18n.decimals() \u21d2 number","text":"

Get the number of decimal places for the currency: for example, USD has 2 ($x.00), KRW has 0 (x), UYW has 4 (x.0000).

Kind: static method of i18n Returns: number - 0, 2, 3, or 4

"},{"location":"Plugin%20API/i18n/#i18nconvertcurrencyamount-fromcurrency-tocurrency-promisenumber","title":"i18n.convertCurrency(amount, fromCurrency, [toCurrency]) \u21d2 Promise.<number>","text":"

Convert an amount of money to a different currency. Conversion rate is retrieved from the internet and cached for 4 hours.

Kind: static method of i18n

Param Type Default Description amount number Amount of money in the \"wrong\" currency fromCurrency string The currency code for the \"wrong\" currency that needs conversion [toCurrency] string \"getCurrencyCode()\" The \"correct\" currency we want the amount to be in.

"},{"location":"Plugin%20API/i18n/#i18nmoneytofixedamount-string","title":"i18n.moneyToFixed(amount) \u21d2 string","text":"

Returns the amount as a string formatted with the correct number of decimal places for the currency in use.

Kind: static method of i18n Returns: string - \"1.23\", \"1.2345\", etc

Param Type amount number

"},{"location":"Plugin%20API/i18n/#i18nmoneystringamount-string","title":"i18n.moneyString(amount) \u21d2 string","text":"

Returns the amount as a string, with the correct decimal places, and the local currency symbol.

Kind: static method of i18n Returns: string - \"$1.23\", etc

Param Type amount number

"},{"location":"Plugin%20API/i18n/#i18ncurrencyminortomajoramount-number","title":"i18n.currencyMinorToMajor(amount) \u21d2 number","text":"

Convert an amount in cents to dollars (or the local equivalent currency units).

Kind: static method of i18n Returns: number - Dollars, etc

Param Type Description amount number Cents, etc

"},{"location":"Plugin%20API/i18n/#i18ncurrencymajortominoramount-number","title":"i18n.currencyMajorToMinor(amount) \u21d2 number","text":"

Convert an amount in dollars to cents (or the local equivalent currency units).

Kind: static method of i18n Returns: number - Cents, etc

Param Type Description amount number Dollars, etc"},{"location":"Plugin%20API/kiosk/","title":"Kiosk","text":""},{"location":"Plugin%20API/kiosk/#kiosk-object","title":"kiosk : object","text":"

Self-serve kiosk mode

Kind: global namespace

"},{"location":"Plugin%20API/kiosk/#kioskiskiosk-boolean","title":"kiosk.isKiosk() \u21d2 boolean","text":"

Check if PostalPoint is running in kiosk mode.

Kind: static method of kiosk Returns: boolean - - True if system is in kiosk mode, else false.

"},{"location":"Plugin%20API/mailboxes/","title":"Mailboxes","text":""},{"location":"Plugin%20API/mailboxes/#mailboxes-object","title":"mailboxes : object","text":"

Add, modify, and delete mailboxes and mailbox customers.

Kind: global namespace

"},{"location":"Plugin%20API/mailboxes/#mailboxesformps1583","title":"mailboxes.FormPS1583","text":"

Kind: static class of mailboxes

"},{"location":"Plugin%20API/mailboxes/#new-formps1583","title":"new FormPS1583()","text":"

USPS Form PS1583 object, with all the fields needed by USPS for CMRA customers.

"},{"location":"Plugin%20API/mailboxes/#mailboxesgetlistfilter-promisearray","title":"mailboxes.getList(filter) \u21d2 Promise.<Array>","text":"

Get the list of mailboxes and boxholders as an array of objects, see example.

Kind: static method of mailboxes

Param Type Default Description filter null | object Filter to mailboxes matching a column condition, such as getList({number: \"102\"}) or getList({\"size >=\": \"2\"}). Supported filter names include \"number\" (string, box number), \"expires\" (expiration date), \"size\" (number 1-10), and \"barcode\" (string) SQL injection warning: Filter names are inserted directly into query strings without sanitization. Only the values are safe for user input.

Example

[{\n    num: \"123\", // Box number as string\n    expires: 1234567890, // UNIX timestamp (in seconds) or false if box vacant\n    size: \"2\", // Box size, 1-10\n    notes: \"\", // Notes for mailbox, not currently shown in Mailbox Manager UI but may be used in the future\n    barcode: \"\", // Unique barcode for the mailbox, for future use\n    renewalMerchID: \"\", // Merchandise item ID used for autorenewing this mailbox\n    isBusiness: false, // True if the box is for a business, false if for personal use\n    names: [], // Array of boxholders. See addOrUpdateBoxholder for the format.\n    packages: [], // Array of packages awaiting pickup, see below\n    vacant: false // True if the box is currently vacant, else false\n}]\n
Example
// Data objects in the packages array:\n{\n    tracking: tracking ?? \"[Untracked]\", // Package tracking number\n    finalized: true, // True if package check-in is finished and shelf tag/mailbox slips printed, false if not finalized\n    available_date: Date(), // The date and time the package was checked in\n    tag: \"\" // Unique number assigned to the package and printed on shelf tags, scanned by employee when customer picks up package\n}\n

"},{"location":"Plugin%20API/mailboxes/#mailboxesadddaystomailboxboxnumber-days-months-promise","title":"mailboxes.addDaysToMailbox(boxNumber, days, months) \u21d2 Promise","text":"

Add a number of days or months to a mailbox's expiration. Use either days or months, not both.

Kind: static method of mailboxes

Param Type Default Description boxNumber string Mailbox number. days number 0 Days to add. months number 0 Months to add.

"},{"location":"Plugin%20API/mailboxes/#mailboxessetmailboxexpirationdateboxnumber-date-promise","title":"mailboxes.setMailboxExpirationDate(boxNumber, date) \u21d2 Promise","text":"

Set the box expiration to a specific JavaScript Date object, or a UNIX timestamp (in seconds).

Kind: static method of mailboxes

Param Type boxNumber string date number | Date

"},{"location":"Plugin%20API/mailboxes/#mailboxescreatemailboxnumber-size-notes-barcode-promise","title":"mailboxes.createMailbox(number, size, notes, barcode) \u21d2 Promise","text":"

Create a new mailbox number with the specified box size. Throws an error if the box number is already in use.

Kind: static method of mailboxes

Param Type Description number string Mailbox number size number Box size (1 - 10) notes string Arbitrary string with human-readable notes about the box. barcode null | string A barcode value representing this mailbox, typically a sticker on the the physical box visible when delivering mail.

"},{"location":"Plugin%20API/mailboxes/#mailboxeseditmailboxoldnumber-newnumber-newsize-barcode-promise","title":"mailboxes.editMailbox(oldNumber, newNumber, newSize, barcode) \u21d2 Promise","text":"

Change the number and/or size of a mailbox while preserving the boxholders and packages associated. If only changing size, set oldNumber and newNumber to the same value.

Kind: static method of mailboxes

Param Type Default Description oldNumber string Currently assigned box number. newNumber string New box number. Must not exist yet. newSize number | null Box size (1 - 10), if changing the size. barcode null | string A barcode value representing this mailbox, typically a sticker on the the physical box visible when delivering mail.

"},{"location":"Plugin%20API/mailboxes/#mailboxesdeletemailboxnumber-promise","title":"mailboxes.deleteMailbox(number) \u21d2 Promise","text":"

Delete a mailbox. Throws an Error if the mailbox has boxholders attached.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to delete.

"},{"location":"Plugin%20API/mailboxes/#mailboxesclosemailboxnumber-promise","title":"mailboxes.closeMailbox(number) \u21d2 Promise","text":"

Close a mailbox by removing the boxholders and marking it as vacant. Boxholder PS Form 1583 records are automatically archived per USPS regulations.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to close.

"},{"location":"Plugin%20API/mailboxes/#mailboxesmailboxexistsnumber-promiseboolean","title":"mailboxes.mailboxExists(number) \u21d2 Promise.<boolean>","text":"

Returns true if the mailbox number exists, false if it doesn't.

Kind: static method of mailboxes

Param Type Description number string Mailbox number to check.

"},{"location":"Plugin%20API/mailboxes/#mailboxesaddorupdateboxholderboxnumber-info-promise","title":"mailboxes.addOrUpdateBoxholder(boxNumber, info) \u21d2 Promise","text":"

Modify or add a boxholder to a mailbox. info is the boxholder structure below. If the uuid given already belongs to a boxholder, their info is updated with what you supply. Otherwise, the info is added as a new boxholder.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number info Object Boxholder information.

Example

// Unless noted, all fields are strings and default to an empty string.\n{\n    name: [bizname, fname, mname, lname].filter(Boolean).join(\" \"),\n    fname: \"\", // First name\n    mname: \"\", // Middle name\n    lname: \"\", // Last name\n    email: \"\", // Email\n    phone: \"\", // Phone\n    uuid: \"\", // Customer UUID\n    bizname: \"\", // Business name\n    street1: \"\", // Street address\n    city: \"\", // City\n    state: \"\", // Two-character state\n    zipcode: \"\", // ZIP or postal code\n    country: \"\", // Two-character country code\n    primary: true // True if the primary (first) boxholder, false if an additional authorized mail recipient\n}\n

"},{"location":"Plugin%20API/mailboxes/#mailboxesremoveboxholderboxnumber-uuid-promise","title":"mailboxes.removeBoxholder(boxNumber, uuid) \u21d2 Promise","text":"

Remove a boxholder by their UUID, and archive their PS Form 1583 data per USPS regulations.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number. uuid string Boxholder UUID.

"},{"location":"Plugin%20API/mailboxes/#mailboxesget1583boxnumber-uuid-archivenumber-promiseformps1583","title":"mailboxes.get1583(boxNumber, uuid, archiveNumber) \u21d2 Promise.<FormPS1583>","text":"

Get the FormPS1583 object for a boxholder by UUID.

Kind: static method of mailboxes

Param Type Default Description boxNumber string Mailbox number. uuid string Boxholder UUID. archiveNumber boolean false If true, returns the form for a deleted boxholder from the archive.

"},{"location":"Plugin%20API/mailboxes/#mailboxesset1583boxnumber-uuid-formps1583-promise","title":"mailboxes.set1583(boxNumber, uuid, formps1583) \u21d2 Promise","text":"

Set the FormPS1583 object for a boxholder by UUID.

Kind: static method of mailboxes

Param Type Description boxNumber string Mailbox number. uuid string Boxholder UUID. formps1583 FormPS1583 The FormPS1583 object to use.

"},{"location":"Plugin%20API/mailboxes/#mailboxesboxnumbervalid-boolean","title":"mailboxes.boxNumberValid() \u21d2 boolean","text":"

Returns true if the mailbox number is an acceptable format, false if it isn't. Does not check if the box actually exists, merely if the number is acceptable to use as a mailbox number.

Kind: static method of mailboxes

"},{"location":"Plugin%20API/mailboxes/#mailboxesgetmailboxproducts-promisearray","title":"mailboxes.getMailboxProducts() \u21d2 Promise.<Array>","text":"

Get a list of merchandise items that are usable for mailbox renewals.

Kind: static method of mailboxes Example

[{\n    id: \"\", // Unique ID for this entry in the merchandise table\n    name: \"\", // Merch item name\n    category: \"\", // Merch item category\n    price: 0.0, // Sale price in dollars\n    cost: 0.0, // Merchandise cost in dollars (likely not used for mailboxes)\n    barcode: \"\", // Barcode/UPC (likely not used for mailboxes)\n    tax: 0.0, // Sales tax rate\n    rentaldays: 30, // Number of days this item adds to a mailbox (mutually exclusive with rentalmonths)\n    rentalmonths: 1, // Number of months (mutually exclusive with rentaldays)\n    boxsize: \"1\" // Mailbox size tier, 1-10\n}]\n

"},{"location":"Plugin%20API/pos/","title":"Pos","text":""},{"location":"Plugin%20API/pos/#pos-object","title":"pos : object","text":"

Point of Sale, transaction, and payment-related functionality.

Kind: global namespace

"},{"location":"Plugin%20API/pos/#posreceiptitem","title":"pos.ReceiptItem","text":"

Kind: static class of pos Properties

Name Type Default Description merch boolean false True if merchandise, false if shipping. barcode string Item barcode, or tracking number if merch = false. qty number 1 Item quantity. retailPrice number The calculated retail/markup price for a shipment, regardless of actual sale price. If unset, defaults to priceEach * qty. taxRate number 0 Tax rate toAddress Address Shipping destination address. fromAddress Address Shipping return address. carrier string Shipping carrier. service string Shipping service. category string Merchandise/item category. electronicReturnReceipt boolean false If true, the customer's receipt will have instructions on retrieveing the return receipt from USPS. mailboxDays number 0 Number of days this item adds to a mailbox's expiration date. mailboxMonths number 0 Number of months this item adds to a mailbox's expiration date. mailboxNumber string Mailbox number to apply mailboxDays or mailboxMonths to after checkout. setCertifiedInfo() function Set Certified Mail receipt data. setCertifiedInfo(trackingNumber, certfee, extrafees, postage, date, location, toaddress) toJSON() function Get the item as an object suitable for JSON encoding. fromJSON(json) static_function Returns a ReceiptItem created from the object returned by item.toJSON().

"},{"location":"Plugin%20API/pos/#new-receiptitemid-label-text-priceeach-quantity-cost-taxrate-taxableamount","title":"new ReceiptItem(id, label, text, priceEach, quantity, cost, taxrate, taxableAmount)","text":"

A class representing a sale item in the current transaction.

Param Type Default Description id string Unique ID number for this item (UPC code, inventory number, etc). Used to deduplicate and merge line items on the receipt. Unique items (like shipping labels) should be a unique/random ID. label string One-line item information. text string Extra multi-line item information. priceEach number Sale price per unit. quantity number Number of units. cost number Cost per unit. Used for automatic expense tracking. taxrate number 0.0 Examples: 0 (for 0%), 0.05 (for 5%), etc taxableAmount string The part of the sale price that's taxable. \"\" for default (all), \"markup\" for only taxing profit.

"},{"location":"Plugin%20API/pos/#posreceiptpayment","title":"pos.ReceiptPayment","text":"

Kind: static class of pos Properties

Name Type Description label string (readonly) The human-readable string of the payment type. id string Automatically-generated unique ID for this payment. toJSON() function Get the payment as an object suitable for JSON encoding. fromJSON(json) static_function Returns a ReceiptPayment created from the object returned by payment.toJSON().

"},{"location":"Plugin%20API/pos/#new-receiptpaymentamount-type-text","title":"new ReceiptPayment(amount, type, text)","text":"

A class representing a payment entry for the current transaction.

Param Type Description amount number amount paid type string payment type text string extra data (credit card info, etc)

"},{"location":"Plugin%20API/pos/#posaddreceiptitemitem","title":"pos.addReceiptItem(item)","text":"

Add an item (shipment, merchandise, etc) to the current transaction.

Kind: static method of pos

Param Type item ReceiptItem

"},{"location":"Plugin%20API/pos/#posaddreceiptpaymentpayment","title":"pos.addReceiptPayment(payment)","text":"

Add a payment to the current transaction/receipt.

Kind: static method of pos

Param Type payment ReceiptPayment

"},{"location":"Plugin%20API/pos/#posaddonscreenpaymentlogmsg","title":"pos.addOnscreenPaymentLog(msg)","text":"

Append a line of text to the onscreen log displayed during credit card processing. Not shown in kiosk mode.

Kind: static method of pos

Param Type Description msg string Line of text to add to the log.

"},{"location":"Plugin%20API/pos/#posgetreceiptid-string","title":"pos.getReceiptID() \u21d2 string","text":"

Get the unique alphanumeric ID for the current transaction/receipt. This is the same code printed on receipts and used in digital receipt URLs.

Kind: static method of pos

"},{"location":"Plugin%20API/pos/#posonreceiptchangef","title":"pos.onReceiptChange(f)","text":"

Specify a function to be called whenever the transaction data/receipt is changed. It is passed a single argument, a Receipt object containing the entire transaction so far.

Kind: static method of pos

Param Type f function

"},{"location":"Plugin%20API/pos/#posontransactionfinishedf","title":"~~pos.onTransactionFinished(f)~~","text":"

Deprecated

The supplied function will be called when a transaction is finished. It is passed a single argument, a Receipt object containing the entire transaction. Recommended to listen for the transactionFinished event instead.

Kind: static method of pos

Param Type f function

"},{"location":"Plugin%20API/pos/#posregistercardprocessorf","title":"pos.registerCardProcessor(f)","text":"

Register as a card payment processor.

Kind: static method of pos

Param Type Description f Object Payment processor functions

Example

global.apis.pos.registerCardProcessor({\n    name: \"Demo Card Processor\", // Shown in PostalPoint settings menu\n    init: async function () {\n        // This will run after PostalPoint launches\n        // and before any payments are processed.\n        // In some situations it might run multiple times in a session.\n    },\n    checkout: async function ({amount, capture = true}) {\n        // Charge a credit card using a card reader device.\n        // amount is in pennies (or the equivalent base unit in the local currency).\n\n        // Add a payment to the receipt with the total amount paid, card details, etc.\n        global.apis.pos.addReceiptPayment(\n            new global.apis.pos.ReceiptPayment(\n                global.apis.i18n.currencyMinorToMajor(amount),\n                \"card\", // Payment type. Accepted values are card, ach, crypto, cash,\n                // check, account, and free.  Other types will be displayed as-is.\n                \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\" // Additional text for receipt\n            )\n        );\n\n\n        // Must return boolean false if the payment failed.\n        // Otherwise it will be assumed it succeeded.\n        // If an error is encountered, handle it and return false.\n        // It's recommended to display a short \"payment failed\" error\n        // message via global.apis.alert, and outputting more details\n        // via global.apis.pos.addOnscreenPaymentLog.\n\n        // If capture is false, perform an authorization but don't capture,\n        // and return a value you can use to identify the authorization later\n        // and complete it.  The value will be passed back to finishPayment, below.\n        // This is used mainly for the self-serve kiosk mode, in case the label fails\n        // to be purchased/generated by the carrier.\n    },\n    cancelCheckout: function () {\n        // The user has requested the card transaction be canceled before it completes.\n        // Reset the terminal to its resting state, clear its screen, etc.\n    },\n    finishPayment: async function ({checkoutResponse}) {\n        // Finish a payment that was authorized but not captured\n        // because checkout() was called with capture = false.\n        // If payment was already captured and added\n        // to the receipt, just return true.\n        global.apis.pos.addReceiptPayment(\n            new global.apis.pos.ReceiptPayment(\n                global.apis.i18n.currencyMinorToMajor(amount),\n                \"card\",\n                \"Demo Card\\nCardholder Name, etc\\nMore info for receipt\"\n            )\n        );\n    },\n    updateCartDisplay: function (receipt) {\n        // Show transaction data on the card reader display.\n        // This function is called when the \"cart\" or total changes.\n        // `receipt` is a receipt object, see docs for details.\n    },\n    checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {\n        // Same as checkout() except using a payment method already on file.\n        // customerID and paymentMethodID are provided by getSavedPaymentMethods below.\n\n        // Must return true upon success.\n        // If the payment is not successful, and you didn't throw an Error to show the user,\n        // then `return false` instead and it'll appear that the user's action to start the payment did nothing.\n        return true;\n    },\n    saveCardForOfflineUse: async function ({statusCallback, customerUUID, name,\n             company, street1, street2, city, state, zip, country, email, phone}) {\n        // Use the card reader to capture an in-person card and save it for offline use.\n        // Provided details are the customer's info, which might be empty strings except for the customerUUID.\n        // Saved card details must be tied to the customerUUID, as that's how saved cards are looked up.\n\n        // statusCallback(string, boolean) updates the progress message on the cashier's screen.\n        // If the boolean is true, the progress message is replaced with a confirmation message.\n        statusCallback(\"Saving card details...\", false);\n\n        return true; // Card saved to customer\n        // If an error occurred, you can throw it and the error\n        // message will be displayed to the cashier.\n        // Alternatively, return boolean false and display the error\n        // yourself with global.apis.alert(message, title) or something.\n    },\n    cancelSaveCardForOfflineUse: function () {\n        // Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.\n    },\n    getSavedPaymentMethods: async function ({customerUUID}) {\n        // Return all saved payment methods tied to the provided customer UUID.\n        return [{\n            customer: \"<internal string referencing the customer>\", // Passed to checkoutSavedMethod as customerID\n            customer_uuid: customerUUID,\n            id: \"<card/payment method identifier>\", // Passed to checkoutSavedMethod as paymentMethodID\n            type: \"card\", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.\n            label: \"Visa debit x1234 (exp. 12/29)\", // Label for payment method\n            label_short: \"Visa debit x1234\" // Abbreviated label for payment method\n        }];\n    },\n    deleteSavedPaymentMethod: async function ({customerUUID, customerID, paymentMethodID}) {\n        // Delete the payment method identified by paymentMethodID\n        // and tied to the PostalPoint customerUUID and the card processor customerID.\n        // If unable to delete, throw an error and the error message\n        // will be displayed to the cashier.\n    }\n});\n

"},{"location":"Plugin%20API/pos/#posregistercryptoprocessorf","title":"pos.registerCryptoProcessor(f)","text":"

Register as a cryptocurrency payment processor.

Kind: static method of pos

Param Type Description f Object Payment processor functions

Example

global.apis.pos.registerCryptoProcessor({\n    name: \"Demo Crypto\", // Shown in PostalPoint settings menu\n    init: async function () {\n        // This is run after PostalPoint starts,\n        // and before any other crypto functions are called.\n    },\n    checkout: async function ({amount}) {\n        // Run the checkout process.\n        // amount is the amount of fiat currency to collect,\n        // in pennies (or the local equivalent).\n\n        // If an error is encountered during processing,\n        //    display an error message in a dialog and return boolean false.\n        //    If this function returns anything except false or undefined,\n        //    and doesn't throw an error,\n        //    it is assumed the payment was successful.\n\n        // Adds a line of text visible to the cashier\n        global.apis.pos.addOnscreenPaymentLog(\"Getting crypto payment...\");\n\n        // Display a web page (i.e. with a payment QR code)\n        // to the customer on the customer-facing display.\n        global.apis.ui.setCustomerScreen(\"<html></html>\", \"html\");\n        global.apis.ui.setCustomerScreen(\"https://postalpoint.app\", \"raw\");\n\n        // Poll the status of the crypto transaction\n        var paymentComplete = false;\n        do {\n            await global.apis.util.delay(1000);\n            paymentComplete = true;\n        } while (paymentComplete != true);\n\n        global.apis.pos.addReceiptPayment(\n                new global.apis.pos.ReceiptPayment(\n                        global.apis.i18n.currencyMinorToMajor(amount),\n                        \"crypto\", // Payment type.\n                        \"Bitcoin\\n0.00001234 BTC\" // Additional text for receipt\n                        )\n                );\n        global.apis.pos.addOnscreenPaymentLog(\"Payment successful!\");\n        global.apis.ui.clearCustomerScreen();\n    },\n    cancelCheckout: function () {\n        // The user requested to cancel the payment.\n        // Reset things accordingly.\n        global.apis.ui.clearCustomerScreen();\n    },\n    isConfigured: function () {\n        // Is this plugin properly setup\n        // and able to process payments?\n        // If not, return false.\n        // This determines if the crypto payment method button will be shown.\n        return true;\n    }\n});\n

"},{"location":"Plugin%20API/pos/#posgetshippingsalestax-object","title":"pos.getShippingSalesTax() \u21d2 Object","text":"

Get the sales tax percentage to charge on a shipping service ReceiptItem.

Kind: static method of pos Returns: Object - {type: \"\", percent: 0.15} type is an empty string for taxing the entire price, or \"markup\" for only adding tax to the markup amount. percent is the tax percentage. A value of 0.15 means a 15% tax.

"},{"location":"Plugin%20API/print/","title":"Print","text":""},{"location":"Plugin%20API/print/#print-object","title":"print : object","text":"

Printing to connected printers

Kind: global namespace

"},{"location":"Plugin%20API/print/#printprintlabelimageimage","title":"print.printLabelImage(image)","text":"

Print a 300 DPI image on the shipping label printer, centered on a 4x6 inch label. Image is automatically scaled to 200 DPI if required by the printer.

Kind: static method of print

Param Type Description image ArrayBuffer | Buffer | Uint8Array | string | Jimp image data, as a Jimp image object, raw PNG bytes, or a URL (http/https) string. 1200x1800 or 800x1200 images are scaled to 4x6 inches. Other image sizes are assumed to be 300 DPI and are centered on the shipping label. Image orientation is rotated to match the label orientation.

"},{"location":"Plugin%20API/print/#printgetreceiptprinter-promiseobject","title":"print.getReceiptPrinter() \u21d2 Promise.<Object>","text":"

Get the receipt printer interface. See the ReceiptPrinter docs for available functions.

Kind: static method of print

"},{"location":"Plugin%20API/print/#printprintreceiptdatadata","title":"print.printReceiptData(data)","text":"

Send raw data (generated by the printer interface) to the receipt printer.

Kind: static method of print

Param Type Description data string | Uint8Array | Array.<string> | Array.<Uint8Array> Data to send to printer.

"},{"location":"Plugin%20API/print/#printimagetobitmapjimpimage-dpifrom-dpito-object","title":"print.imageToBitmap(jimpImage, [dpiFrom], [dpiTo]) \u21d2 Object","text":"

Convert a Jimp image object to 1-bit monochrome image data before sending image data to a printer interface. Optionally scales the image to a different DPI before conversion.

Kind: static method of print Returns: Object - - Example: {width: 300, height: 200, img: Uint8Array}. Pass img to drawImage on a printer interface.

Param Type Default Description jimpImage Jimp [dpiFrom] number 300 Original image DPI. [dpiTo] number 300 New image DPI."},{"location":"Plugin%20API/reports/","title":"Reports","text":""},{"location":"Plugin%20API/reports/#reports-object","title":"reports : object","text":"

Define custom reports for the user.

Kind: global namespace

"},{"location":"Plugin%20API/reports/#reportsregisterreportname-onloadstartdateenddate-date","title":"reports.registerReport(name, onload(startDate,endDate), date)","text":"

Kind: static method of reports

Param Type Description name string Report name onload(startDate,endDate) function Called when the report is loaded, with start and end Date objects. See example. date boolean If the report requires a date range be selected.

Example

global.apis.reports.registerReport(\"sample\", function (startDate, endDate) {\n\n // Note about column datatypes:\n // Use \"string\" for the datatype, except for the special cases listed here.\n // Other datatypes may be added in the future, so use \"string\"\n // unless you like unexpected behavior!\n //\n // datetime:   Column is a UNIX timestamp (in seconds).\n //             It is displayed as a formatted date and time string.\n // receiptid:  Column is a PostalPoint receipt ID number. Displayed as a link.\n //             Clicking the ID will fetch and open the receipt in a popup.\n // userid:     Column contains an employee ID number from the PostalPoint database.\n //             It is queried in the database and replaced with the employee's name,\n //             or with an empty string if the ID lookup has no results.\n // money:      Column is a number that will be formatted as currency for display.\n // percent:    Column is a percent value (as 12.3, not .123) and will be formatted\n //             with a trailing % sign and rounded to two decimal places.\n\n // Single-table report\n return {\n   table: {\n     header: [\"Column 1\", \"Column 2\"],\n     datatypes: [\"string\", \"string\"],\n     rows: [\n       [\"Row 1 Col 1\", \"Row 1 Col 2\"],\n       [\"Row 2 Col 1\", \"Row 2 Col 2\"]\n     ]\n   }\n };\n\n // Multiple-table report\n return {\n    multitable: true,\n    table: {\n      titles: [\"Report 1 Title\", \"Report 2 Title\"],\n      header: [[\"Report 1 Column 1\", \"Report 1 Column 2\"], [\"Report 2 Column 1\", ...]],\n      datatypes: [[\"string\", \"string\"], [\"string\", \"string\"]],\n      rows: [\n        [\n          [\"Report 1 Row 1 Col 1\", \"Report 1 Row 1 Col 2\"],\n          [\"Report 1 Row 2 Col 1\", \"Report 1 Row 2 Col 2\"]\n        ],\n        [\n          [\"Report 2 Row 1 Col 1\", \"Report 2 Row 1 Col 2\"],\n          [\"Report 2 Row 2 Col 1\", \"Report 2 Row 2 Col 2\"]\n        ]\n      ]\n    }\n  }\n}, true);\n

"},{"location":"Plugin%20API/settings/","title":"Settings","text":""},{"location":"Plugin%20API/settings/#settings-object","title":"settings : object","text":"

PostalPoint provides a UI for user-configurable plugin settings. See exports.config in examples/basic-demo/plugin.js for details. Settings are typically very short strings. Do not store data in settings. For data storage, see Storing Data. Non-string settings values are transparently converted to/from JSON objects. Use a unique key name prefix for your plugin to prevent key name conflicts. Reverse domain style is recommended (i.e. \"com.example.pluginname.keyname\").

Kind: global namespace

"},{"location":"Plugin%20API/settings/#settingsgetkey-defaultvalue","title":"settings.get(key, defaultValue) \u21d2 *","text":"

Get a setting.

Kind: static method of settings

Param Type Description key string Setting key/ID defaultValue * Value to return if setting has no stored value.

"},{"location":"Plugin%20API/settings/#settingssetkey-value","title":"settings.set(key, value)","text":"

Set a setting.

Kind: static method of settings

Param Type Description key string Setting key/ID value string Value to set."},{"location":"Plugin%20API/shipping/","title":"Shipping","text":""},{"location":"Plugin%20API/shipping/#shipping-object","title":"shipping : object","text":"

Add custom carrier and rates, and adjust markup.

Kind: global namespace

"},{"location":"Plugin%20API/shipping/#shippingaddress","title":"shipping.Address","text":"

Kind: static class of shipping

"},{"location":"Plugin%20API/shipping/#new-address","title":"new Address()","text":"

A class representing an address.

"},{"location":"Plugin%20API/shipping/#shippinggetzipcodezipcode-country-object","title":"shipping.getZIPCode(zipcode, country) \u21d2 Object","text":"

Get data for a ZIP Code.

Kind: static method of shipping Returns: Object - Data about the ZIP code. See example. Fields may be empty if not available. Type may be \"STANDARD\", \"UNIQUE\", \"PO BOX\", or \"MILITARY\".

Param Type Default Description zipcode string ZIP or postal code. country string \"US\" Currently only \"US\" and \"CA\" are supported.

Example

{city: \"NEW YORK\", state: \"NY\", type: \"STANDARD\"}\n

"},{"location":"Plugin%20API/shipping/#shippinggetpackagingbyidid-promiseobject","title":"shipping.getPackagingByID(id) \u21d2 Promise.<Object>","text":"

Get a parcel's packaging type from PostalPoint's internal ID for it.

Kind: static method of shipping Returns: Promise.<Object> - See examples.

Param Type id number

Example

{\n    id: 100,\n    type: \"Parcel\",\n    img: \"box.png\",\n    name: \"Box\",\n    service: \"\",\n    l: -1,\n    w: -1,\n    h: -1,\n    weight: true,\n    hazmat: true,\n    source: \"Customer\"\n}\n
Example
{\n    id: 1,\n    type: \"FlatRateEnvelope\",\n    img: \"pm-fres.png\",\n    name: \"Flat Rate Envelope\",\n    service: \"Priority\",\n    l: -2,\n    w: -2,\n    h: -2,\n    weight: false,\n    hazmat: true,\n    usps_supplied: true,\n    envelope: true,\n    source: \"USPS\",\n    skus: [\"PS00001000014\", \"PS00001000012\", \"PS00001000027\", \"PS00001000064\", \"PS00001001921\", \"PS00001035000\", \"PS00001036014\", \"PS00001128600\", \"https://qr.usps.com/epsspu?p=30\", \"https://qr.usps.com/epsspu?p=8\"]\n}\n
Example
{\n    id: 201,\n    type: \"UPSLetter\",\n    img: \"ups-env.png\",\n    name: \"Envelope\",\n    carrier: \"UPS\",\n    l: -2,\n    w: -2,\n    h: -2,\n    weight: true,\n    hazmat: true,\n    source: \"OtherCarrier\"\n}\n

"},{"location":"Plugin%20API/shipping/#shippinggetretailpricewithmarkupcost-retail-carrier-service-weightoz-packaging-promisenumber","title":"shipping.getRetailPriceWithMarkup(cost, retail, carrier, service, weightOz, packaging) \u21d2 Promise.<number>","text":"

Calculate the retail price for a shipment rate based on the configured margin settings.

Kind: static method of shipping Returns: Promise.<number> - The amount to charge the customer

Param Type Description cost number Cost of shipment to business retail number Default retail price from label provider carrier string Shipment carrier service string Shipment service weightOz number The weight of the shipment in ounces, or null if not available. packaging string An empty string if not available, or \"Letter\", \"FlatRateEnvelope\", etc.

"},{"location":"Plugin%20API/shipping/#shippinggetcarriernamecarrierid-string","title":"shipping.getCarrierName(carrierId) \u21d2 string","text":"

Converts the carrier ID string into a consistent and human-readable name.

Kind: static method of shipping

Param Type carrierId string

"},{"location":"Plugin%20API/shipping/#shippinggetservicenameserviceid-carrier-string","title":"shipping.getServiceName(serviceId, carrier) \u21d2 string","text":"

Converts the service ID string into a consistent and human-readable name. Set the carrier ID for better results.

Kind: static method of shipping

Param Type Default serviceId string carrier string \"USPS\"

"},{"location":"Plugin%20API/shipping/#shippingregisterrateendpointgetrates-purchase-idprefix","title":"shipping.registerRateEndpoint(getRates, purchase, idPrefix)","text":"

Register the plugin as a shipping rate and label provider. See the Shipping example plugin.

Kind: static method of shipping

Param Type Description getRates function A function passed a Parcel object to get rates for. Returns a Promise that resolves to an array of rate objects. purchase function A function passed a rate ID to purchase. Returns a Promise that resolves to the label information. idPrefix string A unique string that will be prefixing all rate IDs from this plugin.

Example

// getRates sample return value:\n[{\n    rateid: `${idPrefix}_${global.apis.util.uuid.v4()}`,\n    carrier: \"CarrierID\",\n    carrierName: \"Carrier Name\",\n    service: \"CARRIER_SERVICE_ID\",\n    cost_rate: 10,\n    retail_rate: 15,\n    delivery_days: 3,\n    delivery_date: null,\n    guaranteed: true,\n    serviceName: \"Service Name\",\n    color: \"green\" // Rate card color\n}]\n
Example
// purchase sample return value:\n{\n    label: labelImageToPrint,\n    labeltype: \"PNG\",\n    receiptItem: ReceiptItem,\n    tracking: \"12345678901234567890\",\n    cost: 10.0,\n    price: 15.0,\n    carrier: \"Carrier Name\",\n    service: \"Service Name\",\n    delivery_days: 3,\n    delivery_date: 1234567890, // UNIX timestamp\n    to: toAddressLines // Array of strings\n}\n

"},{"location":"Plugin%20API/shipping/#shippingregistermarkupcalculatormarkupfn","title":"shipping.registerMarkupCalculator(markupFn)","text":"

Register the plugin to modify PostalPoint's shipping markup calculation during shipment rating.

Kind: static method of shipping Throws:

Param Type Description markupFn function A function that must return either the retail price to charge for this rate, or false to opt-out of setting this particular rate.

Example

global.apis.shipping.registerMarkupCalculator(\n    // Parameters:\n    // cost:       Cost to shipper\n    // retail:     Carrier-suggested retail price\n    // suggested:  PostalPoint-suggested retail (default margin calc)\n    // carrier:    Shipping carrier name\n    // service:    Shipping service code\n    // weightOz:   The weight of the shipment in ounces, or null if not available.\n    // packaging:  An empty string if not available, or \"Letter\", \"FlatRateEnvelope\", etc. See https://docs.easypost.com/docs/parcels#predefined-package\n    // parcel:     The Parcel object for this shipment.  May be null for some rate-only requests without a shipment, such as USPS price calculations.\n    function (cost, retail, suggested, carrier, service, weightOz, packaging, parcel) {\n        if (carrier == \"USPS\") {\n            if (service == \"First-Class Mail\") {\n                // Handle First-Class Mail differently if it's a 1oz letter (i.e. Forever stamp)\n                if (weightOz <= 1 && packaging == \"Letter\") {\n                    return retail + 0.05;\n                } else {\n                    return retail + 0.25;\n                }\n            }\n            // Handle flat rate envelopes differently\n            if (global.apis.shipping.getServiceName(service, carrier) == \"Priority Mail\" && packaging == \"FlatRateEnvelope\") {\n                return retail + 1.0;\n            }\n            return suggested + 2.0; // Charge the PostalPoint-calculated amount plus $2\n        } else {\n            return cost * 2; // Charges the customer double the shipment's cost.\n        }\n    }\n);\n

"},{"location":"Plugin%20API/shipping/#shippingregisterinsuranceproviderid-name-cardtext-maxvalue-getquote-insure","title":"shipping.registerInsuranceProvider(id, name, cardText, maxValue, getQuote, insure)","text":"

Add a shipping insurance provider.

Kind: static method of shipping

Param Type Description id string | null Unique ID for the provider. Will be autogenerated if null. name string Human-readable name for the provider. Shown as the card heading on the Insurance section of the Ship screen. cardText string Text or HTML to display on the Ship screen card for this provider. maxValue number The largest number that will be accepted for the \"Insured for\" value. getQuote function Returns the cost and retail price for insuring the parcel, or a Promise that resolves into the same. See the example for details. insure function Insure the parcel and add the insurance details to the receipt. See example.

Example

async function getQuote(value, parcel, carrier, service, rateObject) {\n    // See shipping rate provider documentation for rateObject structure.\n\n    // Do math, etc\n    var cost = value / 100;\n\n    return {\n        cost: cost,\n        retail: cost * 2\n    };\n    // Or, to remove this shipping rate from the list,\n    // because the shipment/carrier/service combination\n    // is not eligible for insurance:\n    return false;\n}\n\nasync function insure(value, parcel, carrier = \"USPS\", service = \"Priority\", trackingNumber = \"94055...\") {\n    // Purchase the insurance\n    var cost = value / 100;\n    var retailPrice = cost * 2;\n    var costPrice = cost;\n\n    var receiptitem = new global.apis.pos.ReceiptItem(`sampleinsurance_${trackingNumber}`,\n        \"Sample Insurance\",\n        \"Insured for \" + global.apis.i18n.moneyString(value),\n        retailPrice, 1, costPrice, 0\n    );\n    receiptitem.merch = true;\n    receiptitem.category = \"Shipping Insurance\";\n    receiptitem.barcode = trackingNumber;\n    global.apis.pos.addReceiptItem(receiptitem);\n}\n\nglobal.apis.shipping.registerInsuranceProvider(\n     \"sampleproviderid\", \"Sample Insurance\",\n     \"Insurance coverage from Sample Insurance. $1 per $100 of value.\",\n     5000, getQuote, insure);\n

"},{"location":"Plugin%20API/storage/","title":"Storage","text":""},{"location":"Plugin%20API/storage/#storage-object","title":"storage : object","text":"

Get and set data.

Kind: global namespace

"},{"location":"Plugin%20API/storage/#storagegetsmallkey-defaultvalue","title":"storage.getSmall(key, defaultValue) \u21d2 *","text":"

Get a value from the small data storage, using localStorage or a similar mechanism (may change in the future).

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetsmallkey-value","title":"storage.setSmall(key, value)","text":"

Set a value in the small data storage, using localStorage or a similar mechanism (may change in the future).

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store.

"},{"location":"Plugin%20API/storage/#storagegetbigkey-defaultvalue","title":"storage.getBig(key, defaultValue)","text":"

Get a value in the large data storage. Unserialized from a JSON file on disk.

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetbigkey-value","title":"storage.setBig(key, value)","text":"

Set a value in the large data storage. Serialized to JSON and stored on disk as a file.

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store.

"},{"location":"Plugin%20API/storage/#storagegetdbkey-defaultvalue-promise","title":"storage.getDB(key, defaultValue) \u21d2 Promise.<*>","text":"

Get a value from the database storage. Unlike other storage types, values in the database are available on all PostalPoint installations in a single location.

Kind: static method of storage

Param Type Description key string Storage item key/ID defaultValue * Value to return if the item key doesn't have a stored value.

"},{"location":"Plugin%20API/storage/#storagesetdbkey-value-promise","title":"storage.setDB(key, value) \u21d2 Promise","text":"

Set a value in the database storage. Non-string values are serialized to JSON. Unlike other storage types, values in the database are available on all PostalPoint installations in a single location.

Kind: static method of storage

Param Type Description key string Storage item key/ID value * Value to store."},{"location":"Plugin%20API/ui/","title":"Ui","text":""},{"location":"Plugin%20API/ui/#ui-object","title":"ui : object","text":"

Interact with and modify the PostalPoint user interface.

Kind: global namespace

"},{"location":"Plugin%20API/ui/#uiaddtoolspagepage-title-id-description-cardtitle-icon-type","title":"ui.addToolsPage(page, title, id, description, cardTitle, icon, type)","text":"

Add a page to the Tools screen. If type is set to \"function\", the page argument will be run as a function and will not be expected to return a page component. Framework7 Page Component documentation

Kind: static method of ui

Param Type Default Description page string | function Page content as a Framework7 component page. If page is a string ending in .f7 it is treated as a file path and the page content will be loaded from disk. If page is any other string, it is treated as the page content. If page is a function, it will be called and must return the page content (unless type is set to \"function\", see examples) title string Page title. id string Page ID. Make it unique, or pass an empty string to be assigned a random ID. Tools with a random ID are not pinnable to the program's main navigation bar. description string Description of this tool for its card on the Tools screen. cardTitle string Title of the card for this page on the Tools screen. icon string FontAwesome icon class, for example, \"fa-solid fa-globe\". FontAwesome Pro solid, regular, light, and duotone icons are available. type string \"page\" What kind of data is supplied by page: \"page\" or \"function\".

Example

// Full plugin that displays an alert when the card is clicked on the Tools page\nexports.init = function () {\n    global.apis.ui.addToolsPage(\n                                 displayMessage,\n                                 \"click me\",\n                                 \"clickmecard\",\n                                 \"Click here to see a message\",\n                                 \"Click This Card\",\n                                 \"fa-solid fa-arrow-pointer\",\n                                 \"function\"\n                               );\n}\n\nfunction displayMessage() {\n    global.apis.alert(\"Card clicked\");\n}\n
Example
// Open a dynamically-generated page\nfunction rollDice() {\n    var randomNumber = Math.round(Math.random() * 6) + 1;\n    return `<div class=\"page\">\n        <div class=\"navbar\">\n            <div class=\"navbar-bg\"></div>\n            <div class=\"navbar-inner\">\n                <a href=\"#\" class=\"link back\">\n                    <i class=\"icon icon-back\"></i>\n                </a>\n                <div class=\"title\">Random Number</div>\n            </div>\n        </div>\n        <div class=\"page-content\">\n            <div class=\"block\">\n                <h1>You rolled ${randomNumber}</h1>\n            </div>\n        </div>\n    </div>`;\n}\nglobal.apis.ui.addToolsPage(\n                             rollDice,\n                             \"Random\",\n                             \"randomnumbercard\",\n                             \"Click here for a random number\",\n                             \"Random Number\",\n                             \"fa-regular fa-dice\",\n                             \"page\"\n                           );\n
Example
// Open a page from a file.\n// See https://framework7.io/docs/router-component#single-file-component\nglobal.apis.ui.addToolsPage(\n                             global.apis.getPluginFolder(\"example-plugin\") + \"/page.f7\",\n                             \"Page\",\n                             \"filepage\",\n                             \"Open page.f7 from the plugin installation folder\",\n                             \"Open Custom Page\",\n                             \"fa-regular fa-file\",\n                             \"page\"\n                           );\n

"},{"location":"Plugin%20API/ui/#uiaddhometabcontent-title-icon-id-undefined","title":"ui.addHomeTab(content, title, icon, id) \u21d2 undefined","text":"

Add a custom tab to the PostalPoint home screen. Works almost the same as addToolsPage.

Kind: static method of ui

Param Type Description content string | function Tab content. It is rendered/processed when the user navigates to the Home screen and clicks the tab; if the user navigates to a different screen and back to Home, it will be re-rendered. If content is a string ending in .f7 it is treated as a file path and the content will be loaded from disk. If content is any other string, it is treated as the content. If content is a function, it will be called and must return the content. title string Tab title. Keep it short; depending on screen size and tab count, you have as little as 150px of space. icon string FontAwesome icon displayed above the tab title. id string Tab ID. Make it unique, or pass an empty string to be assigned a random ID. If addHomeTab is called with a tab ID that is already registered, it will be overwritten.

Example

global.apis.ui.addHomeTab(\"<div class='block'>Hello</div>\", \"Hello Tab\", \"fa-duotone fa-hand-wave\", \"hellotab\");\n
Example
function renderTab() {\n    return \"<template><div><h1>${hellovar}</h1></div></template><script>export default (props, {$on, $update, $f7}) => {var hellovar = 'hello world'; return $render;}</script>\";\n}\nglobal.apis.ui.addHomeTab(renderTab, \"Hello Template\", \"fa-duotone fa-file-code\", \"hellotemplatetab\");\n

"},{"location":"Plugin%20API/ui/#uishowprogressspinnertitle-text-subtitle-undefined","title":"ui.showProgressSpinner(title, text, subtitle) \u21d2 undefined","text":"

Show a notification with a loading icon.

Kind: static method of ui

Param Type Description title string The message to show on the spinner. text string Optional. Body text on the spinner. subtitle string Optional. Sub-heading under the title.

"},{"location":"Plugin%20API/ui/#uihideprogressspinner","title":"ui.hideProgressSpinner()","text":"

Close the notification opened by showProgressSpinner.

Kind: static method of ui

"},{"location":"Plugin%20API/ui/#uiopensystemwebbrowserurl","title":"ui.openSystemWebBrowser(url)","text":"

Open the native OS default browser to the URL given.

Kind: static method of ui

Param Type url string

"},{"location":"Plugin%20API/ui/#uiopeninternalwebbrowserurl","title":"ui.openInternalWebBrowser(url)","text":"

Open a web browser inside PostalPoint. The browser has forward/back/close buttons.

Kind: static method of ui

Param Type url string

Example

global.apis.ui.openInternalWebBrowser(\"https://postalpoint.app\");\nglobal.apis.eventbus.on(\"browserNavigate\", function (newUrl) {\n    global.apis.alert(`New URL: ${newUrl}`, \"Browser Navigated\");\n    if (newUrl == \"https://closeme.com\") {\n        global.apis.eventbus.emit(\"browserCloseRequest\");\n    }\n});\n

"},{"location":"Plugin%20API/ui/#uiclearcustomerscreen","title":"ui.clearCustomerScreen()","text":"

Clear any custom content on the customer-facing display, defaulting back to blank/receipt/shipping rates, as applicable.

Kind: static method of ui

"},{"location":"Plugin%20API/ui/#uisetcustomerscreencontent-type-displayincard-cardsize-displaystatusbar","title":"ui.setCustomerScreen(content, type, displayInCard, cardSize, displayStatusBar)","text":"

Render content on the customer-facing display. Encodes content as a data URI (example: data:text/html;charset=utf-8,${content}) and renders on the customer-facing display. If type is html, renders the string as HTML. If type is pdf, displays a PDF viewer. If type is raw, functions like setting an iframe's src to content. All other type values are rendered as text/plain. To display the iframe in a card centered on the screen, set displayInCard to true and pass the desired dimensions (w, h) of the card in px. If the requested size is larger than the available screen space, the card will instead fill the available space. Warning: Do not load third-party websites, this is a security risk. Wrap it in a tag if you need to display one.

Kind: static method of ui

Param Type Default Description content string Page content. type string \"html\" Format of the content. One of \"html\", \"pdf\", \"raw\", \"body\", or \"text\". displayInCard boolean false Set to true to wrap the content in a card UI. cardSize Array.<number> [300,300 Size of the card UI if displayInCard == true. displayStatusBar boolean true Whether the bar on the bottom of the screen should be visible, containing the store logo and scale weight.

Example

// How content is converted by PostalPoint before display:\nif (type == \"html\") {\n    customerScreenContent = `data:text/html;charset=utf-8,${content}`;\n} else if (type == \"pdf\") {\n    customerScreenContent = `data:application/pdf,${content}`;\n} else if (type == \"raw\") {\n    global.customerScreenContent = `${content}`;\n} else if (type == \"body\") {\n    customerScreenContent = `data:text/html;charset=utf-8,<!DOCTYPE html>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <style>\n        html, body {margin: 0; padding: 0; font-family: Roboto, Ubuntu, Arial, sans-serif;}\n        h1, h2, h3 {margin: 0;}\n    </style>\n    <div id=\"maindiv\">${content}</div>`;\n} else {\n    customerScreenContent = `data:text/plain;charset=utf-8,${content}`;\n}\n

"},{"location":"Plugin%20API/ui/#uicollectsignaturefromcustomerscreentitle-terms-termstype","title":"ui.collectSignatureFromCustomerScreen(title, terms, termstype)","text":"

Show a signature pad on the customer-facing display. When the customer indicates the signature is finished, the customerSignatureCollected event is emitted with the data {\"svg\": \"data:image/svg+xml;base64,...\", \"png\": \"data:image/png;base64,...\"}

Kind: static method of ui

Param Type Default Description title string null Display some title/header text on the customer screen near the signature box. Not shown if terms are set. terms string | boolean false Set to a string to display terms and conditions or other text content next to the signature pad. termstype string \"body\" \"html\", \"pdf\", \"raw\", or \"body\". See setCustomerScreen()

Example

global.apis.ui.collectSignatureFromCustomerScreen(\"\", \"<p>By signing, you agree to pay us lots of money</p>\", \"body\");\nglobal.apis.eventbus.on(\"customerSignatureCollected\", function (sigdata) {\n    const pngDataURL = sigdata.png;\n    const svgDataURL = sigdata.svg;\n});\n

"},{"location":"Plugin%20API/ui/#uicancelsignaturecollection","title":"ui.cancelSignatureCollection()","text":"

Cancels customer signature collection and returns the customer-facing display to normal operation.

Kind: static method of ui Example

global.apis.ui.cancelSignatureCollection();\n

"},{"location":"Plugin%20API/ui/#uiclearsignaturepad","title":"ui.clearSignaturePad()","text":"

Erase the signature drawn on the customer-facing display. Note that the customer is also provided a button to do this.

Kind: static method of ui Example

global.apis.ui.clearSignaturePad();\n

"},{"location":"Plugin%20API/ui/#uigetcustomerdisplayinfo-object","title":"ui.getCustomerDisplayInfo() \u21d2 Object","text":"

Describes if the customer-facing display is currently enabled, and if it supports customer touch interaction.

Kind: static method of ui Returns: Object - {\"enabled\": true, \"touch\": true} Example

var info = global.apis.ui.getCustomerDisplayInfo();\n

"},{"location":"Plugin%20API/user/","title":"User","text":""},{"location":"Plugin%20API/user/#user-object","title":"user : object","text":"

Access data about employees.

Kind: global namespace

"},{"location":"Plugin%20API/user/#useruser","title":"user.User","text":"

Kind: static class of user Properties

Name Type Description id number name string pass string barcode string enabled boolean hasPassword() function Returns true if the user has a password set, else false. checkPassword(string) function Returns true if the provided password matches the user's password, or if there is no password set. icon(number) function Returns a SVG data URI with a procedurally-generated icon for the user. Size defaults to 50px if not specified.

"},{"location":"Plugin%20API/user/#new-userid-name-password-barcode-enabled","title":"new User(id, name, password, barcode, enabled)","text":"

A User object.

Param Type id number name string password string barcode string enabled boolean

"},{"location":"Plugin%20API/user/#usergetuser-user","title":"user.getUser() \u21d2 User","text":"

Get the user currently logged in.

Kind: static method of user

"},{"location":"Plugin%20API/user/#usergetuserid-number","title":"user.getUserID() \u21d2 number","text":"

Get the current user's ID number.

Kind: static method of user

"},{"location":"Plugin%20API/user/#usergetuserbyid-promiseuser","title":"user.getUserByID() \u21d2 Promise.<User>","text":"

Look up the User for an ID number.

Kind: static method of user

"},{"location":"Plugin%20API/user/#userlistusersmanagermode-promisearrayuser","title":"user.listUsers([managerMode]) \u21d2 Promise.<Array.<User>>","text":"

Get a list of all users in the system.

Kind: static method of user

Param Type Default Description [managerMode] boolean false If false, list only active/enabled users, and if no users, return a default user account (user ID -1). If true, return all users in the database, and don't return a default account if the list is empty (return an empty list instead)."},{"location":"Plugin%20API/util/","title":"Util","text":""},{"location":"Plugin%20API/util/#util-object","title":"util : object","text":"

Various utility functions: HTTP, time/date, barcode creation, clipboard, etc.

Kind: global namespace

"},{"location":"Plugin%20API/util/#utiluuid-object","title":"util.uuid : object","text":"

Unique ID generators.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#uuidv4-string","title":"uuid.v4() \u21d2 string","text":"

Generate a UUID string

Kind: static method of uuid Returns: string - UUID v4 with dashes: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

"},{"location":"Plugin%20API/util/#uuidshortlength-string","title":"uuid.short([length]) \u21d2 string","text":"

Generate a short random alphanumeric string.

Kind: static method of uuid Returns: string - A string of length length, from the character set \"acdefhjkmnpqrtuvwxy0123456789\".

Param Type Default Description [length] number 16 String character count.

"},{"location":"Plugin%20API/util/#utilhttp-object","title":"util.http : object","text":"

HTTP requests and webhooks.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#httpwebhook-object","title":"http.webhook : object","text":"

Use webhooks via a PostalPoint cloud relay service.

Kind: static namespace of http

"},{"location":"Plugin%20API/util/#webhookgeturlsourcename-promisestring","title":"webhook.geturl(sourcename) \u21d2 Promise.<string>","text":"

geturl - Returns a public URL that can be used as a webhook target/endpoint for third-party integrations.

Kind: static method of webhook Returns: Promise.<string> - A URL for the webhook.

Param Type Description sourcename string Unique identifier for the webhook

"},{"location":"Plugin%20API/util/#webhookpollsourcename-promisearrayobject","title":"webhook.poll(sourcename) \u21d2 Promise.<Array.<Object>>","text":"

poll - Returns an array of webhook payloads received by the webhook identified by sourcename.

Kind: static method of webhook Returns: Promise.<Array.<Object>> - Payloads as received by the webhook relay service.

Param Type Description sourcename string Unique identifier for the webhook

Example

[\n     {\n         // Unique ID. Used for ack(webhookid).\n         id: 123,\n\n         // UNIX timestamp (in seconds) of when the data was received by the webhook URL.\n         timestamp: 1234567890,\n\n         // Source name set in geturl()\n         source: \"sourcename\",\n\n         // JSON string of all the HTTP headers sent to the webhook URL.\n         headers: \"{'Content-Type': 'application/json'}\",\n\n         // Entire HTTP request body sent to the webhook URL.\n         body: \"\"\n     }\n]\n

"},{"location":"Plugin%20API/util/#webhookackwebhookid","title":"webhook.ack(webhookid)","text":"

Acknowledge receipt of a webhook payload, deleting it from the relay server. Webhook payload is only queued for deletion, so polling may still return it for a short time.

Kind: static method of webhook

Param Type Description webhookid number Numeric unique ID received with the payload. See poll.

"},{"location":"Plugin%20API/util/#httpposturl-data-responsetype-headers-method-continueonbadstatuscode-timeoutseconds-promisestringblobarraybufferobject","title":"http.post(url, data, [responseType], [headers], [method], [continueOnBadStatusCode], [timeoutSeconds]) \u21d2 Promise.<(string|Blob|ArrayBuffer|Object)>","text":"

post - Fetch a HTTP POST request.

Kind: static method of http Returns: Promise.<(string|Blob|ArrayBuffer|Object)> - The server response body. See responseType parameter.

Param Type Default Description url string data Object.<string, string> POST data key/value list [responseType] string \"text\" \"text\", \"blob\", \"buffer\", or \"json\" [headers] Object.<string, string> HTTP headers to send. Defaults to {\"Content-Type\": \"application/json\"}. [method] string \"POST\" [continueOnBadStatusCode] boolean false If false, throws an Error when the HTTP response code is not 2XX. If true, ignores the response code and proceeds as normal. [timeoutSeconds] number 15 Aborts the request (timeout) after this many seconds.

"},{"location":"Plugin%20API/util/#httpfetchurl-responsetype-timeoutseconds-promisestringblobarraybufferobject","title":"http.fetch(url, [responseType], [timeoutSeconds]) \u21d2 Promise.<(string|Blob|ArrayBuffer|Object)>","text":"

fetch - Fetch a HTTP GET request.

Kind: static method of http Returns: Promise.<(string|Blob|ArrayBuffer|Object)> - The server response body. See responseType parameter.

Param Type Default Description url string [responseType] string \"text\" \"text\", \"blob\", \"buffer\", or \"json\" [timeoutSeconds] number 15 Aborts the request (timeout) after this many seconds.

"},{"location":"Plugin%20API/util/#utilstring-object","title":"util.string : object","text":"

String manipulation functions.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#stringsplitinput-separator-limit-arraystring","title":"string.split(input, separator, [limit]) \u21d2 Array.<string>","text":"

Split a string with a separator regex.

Kind: static method of string

Param Type Description input string Input string separator string Passed to new RegExp(separator, 'g') [limit] number Maximum number of splits to perform

"},{"location":"Plugin%20API/util/#stringchunkinput-chunksize-arraystring","title":"string.chunk(input, chunksize) \u21d2 Array.<string>","text":"

Split a string into chunks of length chunksize.

Kind: static method of string

Param Type Description input string Input string chunksize string Number of characters per chunk

"},{"location":"Plugin%20API/util/#utiltime-object","title":"util.time : object","text":"

Date and time functions

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#timenow-number","title":"time.now() \u21d2 number","text":"

Get the current UNIX timestamp in seconds.

Kind: static method of time

"},{"location":"Plugin%20API/util/#timediffcompareto-number","title":"time.diff(compareto) \u21d2 number","text":"

Get the number of seconds between now and the given Date or UNIX timestamp in seconds.

Kind: static method of time

Param Type compareto number | Date

"},{"location":"Plugin%20API/util/#timestrtotimestr-number","title":"time.strtotime(str) \u21d2 number","text":"

Parse a string date and return UNIX timestamp (in seconds).

Kind: static method of time

Param Type str string

"},{"location":"Plugin%20API/util/#timeformatformat-timestamp-string","title":"time.format(format, [timestamp]) \u21d2 string","text":"

Take a Date or UNIX timestamp in seconds and format it to a string. Mostly compatible with the PHP date format codes.

Kind: static method of time

Param Type Default Description format string \"Y-m-d H:i:s\", etc [timestamp] number | Date now()

"},{"location":"Plugin%20API/util/#timetodatestringtimestamp-string","title":"time.toDateString(timestamp) \u21d2 string","text":"

Format a UNIX timestamp (in seconds) as a localized date string.

Kind: static method of time

Param Type timestamp number

"},{"location":"Plugin%20API/util/#timetotimestringtimestamp-string","title":"time.toTimeString(timestamp) \u21d2 string","text":"

Format a UNIX timestamp (in seconds) as a localized time string.

Kind: static method of time

Param Type timestamp number

"},{"location":"Plugin%20API/util/#utilclipboard-object","title":"util.clipboard : object","text":"

OS clipboard

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#clipboardcopytext-shownotification-promiseboolean","title":"clipboard.copy(text, [showNotification]) \u21d2 Promise.<boolean>","text":"

Copy a string to the system clipboard.

Kind: static method of clipboard Returns: Promise.<boolean> - True if the copy succeeded, else false.

Param Type Default Description text string [showNotification] boolean false If true, a \"Copied\" notification will appear onscreen briefly.

"},{"location":"Plugin%20API/util/#utilbarcode-object","title":"util.barcode : object","text":"

Barcode image generation functions.

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#barcodegetbufferdata-type-height-scale-includetext-promisebuffer","title":"barcode.getBuffer(data, [type], [height], [scale], [includetext]) \u21d2 Promise.<Buffer>","text":"

Get a PNG image buffer of a barcode. Uses library \"bwip-js\".

Kind: static method of barcode Returns: Promise.<Buffer> - PNG data for the barcode.

Param Type Default Description data string [type] string \"\\\"code128\\\"\" [height] number 10 [scale] number 2 [includetext] boolean false Set true to render the barcode's content as text below the code.

"},{"location":"Plugin%20API/util/#barcodegetbase64data-type-height-scale-includetext-promisestring","title":"barcode.getBase64(data, [type], [height], [scale], [includetext]) \u21d2 Promise.<string>","text":"

Get a PNG image of a barcode as a base64 data URI. Uses library \"bwip-js\".

Kind: static method of barcode Returns: Promise.<string> - \"data:image/png;base64,...\"

Param Type Default Description data string [type] string \"\\\"code128\\\"\" [height] number 10 [scale] number 2 [includetext] boolean false Set true to render the barcode's content as text below the code.

"},{"location":"Plugin%20API/util/#utilgeography-object","title":"util.geography : object","text":"

Kind: static namespace of util

"},{"location":"Plugin%20API/util/#geographyisotocountrynameiso-string","title":"geography.isoToCountryName(iso) \u21d2 string","text":"

Get a human-readable country name from an ISO country code.

Kind: static method of geography

Param Type Description iso string | number 2 or 3 letter country code, or numeric country code.

"},{"location":"Plugin%20API/util/#utilobjectequalsa-b-boolean","title":"util.objectEquals(a, b) \u21d2 boolean","text":"

Compare two objects for equality. See https://stackoverflow.com/a/16788517

Kind: static method of util Returns: boolean - True if equal, else false.

Param Type a * b *

"},{"location":"Plugin%20API/util/#utildelayms-promise","title":"util.delay([ms]) \u21d2 Promise","text":"

Pause execution for some amount of time in an async function, i.e., returns a Promise that resolves in some number of milliseconds.

Kind: static method of util

Param Type Default Description [ms] number 1000 Number of milliseconds to pause."}]} \ No newline at end of file diff --git a/next/sitemap.xml.gz b/next/sitemap.xml.gz index d42f5b8..e4e15b4 100644 Binary files a/next/sitemap.xml.gz and b/next/sitemap.xml.gz differ