Skip to content

# Card Payments

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.

    exports.init = function () {
        global.apis.pos.registerCardProcessor({
            name: "Demo Card Processor",
            init: async function () {
                // This function runs once after starting PostalPoint
                // and before any other card processor functions are called.
            },
            checkout: async function({amount, capture = true}) {
                // amount is an integer number of pennies.

                // If an error is encountered during processing,
                //    display an error message in a dialog and return boolean false.
                //    If this function returns anything except false or undefined, and doesn't throw an error,
                //    it is assumed the payment was successful.
                try {
                    if (capture) {
                        // authorize, capture, add a ReceiptPayment to the receipt, and return boolean true.
                        global.apis.pos.addOnscreenPaymentLog("Getting card payment..."); // Add a line to the onscreen card processing status log
                        await global.apis.util.delay(1000); // Replace this with something useful!
                        global.apis.pos.addReceiptPayment(
                            new global.apis.pos.ReceiptPayment(
                                (amount / 100).toFixed(2) * 1,
                                "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.
                                "Demo Card\nCardholder Name, etc\nMore info for receipt" // Additional text for receipt
                            )
                        );
                        global.apis.pos.addOnscreenPaymentLog("Payment successful!");
                        return true;
                    } else {
                        // only authorize the payment, don't actually capture/charge the payment method,
                        // and return whatever transaction data that will be passed to finishPayment to capture the payment.
                        await global.apis.util.delay(1000); // Replace this with something useful!
                        return {amount: amount};
                    }
                } catch (ex) {
                    global.apis.pos.addOnscreenPaymentLog(`Error: ${ex.message} [okay to put extra details here for troubleshooting or tech support, it's visible to the cashier]`);
                    if (global.apis.kiosk.isKiosk()) {
                        // This message will be shown to an end-user/customer, not a cashier/employee
                        global.apis.alert("Your card was declined.", "Card Error");
                    } else {
                        global.apis.alert("The customer's card was declined.", "Card Error");
                    }
                    return false;
                }
            },
            cancelCheckout: function () {
                // The user requested to cancel the payment.
                // Reset the terminal to its resting state, clear its screen, etc.
            },
            finishPayment: async function ({checkoutResponse}) {
                // Finish a payment that was authorized but not captured because checkout was called with capture = false
                // If payment was already captured and added to the receipt for some reason, just return true.
                await global.apis.util.delay(1000); // Replace this with something useful!
                global.apis.pos.addReceiptPayment(
                    new global.apis.pos.ReceiptPayment(
                        (checkoutResponse.amount / 100).toFixed(2) * 1,
                        "card", // Payment type.
                        "Demo Card\nCardholder Name, etc\nMore info for receipt" // Additional text for receipt
                    )
                );
                return true;
            },
            updateCartDisplay: function (receipt) {
                // Show transaction data on the card reader display.
                // This function will be called when the cart or total changes.
                console.log(receipt);
                // Sample structure of the receipt variable:
                receipt = {
                    "items": [
                        {
                            "id": "testitem",
                            "label": "Test Item",
                            "text": "",
                            "priceEach": 2,
                            "qty": 1,
                            "cost": 0,
                            "retail": 2,
                            "taxRate": 0.1,
                            "free": false,
                            "barcode": "",
                            "certifiedInfo": false,
                            "isMerch": true,
                            "surcharge": false
                        },
                        {
                            "id": "9100123456789012345678",
                            "label": "Test Package",
                            "text": "Package Details\nTracking # 9100 1234 5678 9012 3456 78\nTo:\nTEST PERSON\nORGANIZATION INC\n123 TEST ROAD\nTESTTOWN TE 99999-0001",
                            "priceEach": 8,
                            "qty": 1,
                            "cost": 0,
                            "retail": 8,
                            "taxRate": 0,
                            "free": false,
                            "barcode": "9100123456789012345678",
                            "certifiedInfo": false,
                            "isMerch": false,
                            "surcharge": false,
                            "toAddress": {
                                "name": "TEST PERSON",
                                "company": "ORGANIZATION INC",
                                "street1": "123 TEST ROAD",
                                "street2": null,
                                "city": "TESTTOWN",
                                "state": "TE",
                                "zip": "99999-0001",
                                "email": null,
                                "phone": null,
                                "country": "US"
                            },
                            "fromAddress": {
                                "name": "TEST PERSON",
                                "company": "ORGANIZATION INC",
                                "street1": "123 TEST ROAD",
                                "street2": null,
                                "city": "TESTTOWN",
                                "state": "TE",
                                "zip": "99999-0001",
                                "email": null,
                                "phone": null,
                                "country": "US"
                            }
                        }
                    ],
                    "payments": [
                        {
                            "amount": 10,
                            "amountFormatted": "$10.00",
                            "type": "cash",
                            "label": "Cash",
                            "text": "",
                            "texthtml": "",
                            "id": "12345678_cash_10"
                        },
                        {
                            "amount": 12.34,
                            "amountFormatted": "$12.34",
                            "type": "card",
                            "label": "Card",
                            "text": "Card Details here\n1234abcd",
                            "texthtml": "Card Details here<br />1234abcd",
                            "id": "87654321_card_12.34"
                        }
                    ],
                    "subtotal": 10,
                    "subtotalFormatted": "$10.00",
                    "tax": 0.2,
                    "taxFormatted": "$0.20",
                    "grandTotal": 10.2,
                    "grandTotalFormatted": "$10.20",
                    "paid": 22.34,
                    "paidFormatted": "$22.34",
                    "due": -12.14, // If negative, is the amount of change owed to the customer instead
                    "dueFormatted": "$12.14"
                };
            },
            checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {
                // Same as checkout() except using a payment method already on file.
                // customerID and paymentMethodID are provided by getSavedPaymentMethods below.
                await global.apis.util.delay(1000); // Replace this with something useful!
                var error = false;
                if (error) {
                    // If you can't charge the payment method, throw an Error with a string to display to the user.
                    throw new Error("The saved card didn't work.");
                }
                global.apis.pos.addReceiptPayment(
                    new global.apis.pos.ReceiptPayment(
                        (amount / 100).toFixed(2) * 1,
                        "card", // Payment type.
                        "Card on File\nx1234" // Additional text for receipt
                    )
                );
                // Must return true upon success.
                // If the payment is not successful, and you didn't throw an Error to show the user,
                // then `return false` instead and it'll appear that the user's action to start the payment did nothing.
                return true;
            },
            saveCardForOfflineUse: async function ({statusCallback, customerUUID, name, company, street1, street2, city, state, zip, country, email, phone}) {
                // Use the card reader to capture an in-person card and save it for offline use.
                // Provided details are the customer's info, which might be empty strings except for the customerUUID.
                // Saved card details must be tied to the customerUUID, as that's how saved cards are looked up.

                // statusCallback(string, boolean) updates the progress message on the cashier's screen.
                // If the boolean is true, the progress message is replaced with a confirmation message.
                statusCallback("Insert the card into the reader.", false);

                await global.apis.util.delay(1000); // Wait for the customer to insert their card,
                //then save it for later offline billing

                statusCallback("Saving card details...", false);

                await global.apis.util.delay(1000);

                statusCallback("Card saved!", true);

                return true; // Card saved to customer
                // If an error occurred, you can throw it and the error message will be displayed to the cashier.
                // Alternatively, return boolean false and display the error yourself with global.apis.alert(message, title) or something.
            },
            cancelSaveCardForOfflineUse: function () {
                // Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.
            },
            getSavedPaymentMethods: async function ({customerUUID}) {
                // Return all saved payment methods tied to the provided customer UUID.
                var methods = [];
                methods.push({
                    customer: "<internal string referencing the customer>", // Passed to checkoutSavedMethod as customerID
                    customer_uuid: customerUUID,
                    id: "<card/payment method identifier>", // Passed to checkoutSavedMethod as paymentMethodID
                    type: "card", // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.
                    label: "Visa debit x1234 (exp. 12/29)", // Label for payment method
                    label_short: "Visa debit x1234" // Abbreviated label for payment method
                });
                return methods;
            },
            deleteSavedPaymentMethod: async function ({customerUUID, customerID, paymentMethodID}) {
                // Delete the payment method identified by paymentMethodID and tied to the PostalPoint customerUUID and the card processor customerID.
                // If unable to delete, throw an error and the error message will be displayed to the cashier.
                await global.apis.util.delay(1000);
            }
        });
    }

    // Plugin settings to display.
    exports.config = [
        {
            type: "password",
            key: "democardprocessor_apikey",
            defaultVal: "",
            label: "API Key",
            placeholder: "",
            text: "API Key"
        },
        {
            type: "button",
            label: "Test Button",
            text: "Some text about the button",
            onClick: function () {
                global.apis.ui.openSystemWebBrowser("https://postalpoint.app");
            }
        }
    ];