237 lines
12 KiB
JavaScript
Raw Normal View History

2025-03-06 15:02:55 -07:00
// 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"
}
},
2025-05-21 12:53:16 -06:00
checkoutSavedMethod: async function ({customerID, paymentMethodID, amount}) {
2025-03-06 15:02:55 -07:00
// Same as checkout() except using a payment method already on file.
2025-05-21 12:53:16 -06:00
// customerID and paymentMethodID are provided by getSavedPaymentMethods below.
2025-03-06 15:02:55 -07:00
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.
"Card on File\nx1234" // Additional text for receipt
)
);
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({
2025-05-21 12:53:16 -06:00
customer: "<internal string referencing the customer>", // Passed to checkoutSavedMethod as customerID
2025-03-06 15:02:55 -07:00
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");
}
}
];