2025-05-21 13:00:23 -06:00
// Adds Square Terminal as a payment processor option.
const SQUARE _ENV = "PROD" ; // "SANDBOX"
var URL _BASE = "https://connect.squareup.com" ;
if ( SQUARE _ENV == "SANDBOX" ) {
URL _BASE = "https://connect.squareupsandbox.com" ;
} else {
URL _BASE = "https://connect.squareup.com" ;
}
const APP _ID = "sq0idp-mrUlE9gCQ9yLevtbqyurDw" ; // "sandbox-sq0idb-EGkAFQuSaL3mPwzG8fxPIA";
2025-05-21 13:06:51 -06:00
const SQUARE _API _VERSION = "2025-05-21" ;
2025-05-21 13:00:23 -06:00
const REDIRECT _URI = "https://postalpoint.app/oauth/square" ;
var code _verifier _code = null ;
var checkout _id = null ;
var latestTerminalActionID = "" ;
async function apiRequest ( url , data = { } , method = "POST" ) {
var accessToken = global . apis . settings . get ( "app.postalpoint.squareterminal.access_token" , "" ) ;
console . log ( ` ${ url } : ` , data ) ;
return await global . apis . util . http . post ( ` ${ URL _BASE } / ${ url } ` , data , "json" , {
"Content-Type" : "application/json" ,
"Square-Version" : ` ${ SQUARE _API _VERSION } ` ,
"Authorization" : ` Bearer ${ accessToken } `
} , method , true ) ;
}
async function loginToSquare ( ) {
code _verifier _code = global . apis . util . uuid . v4 ( ) ;
var code _challenge _array = await window . crypto . subtle . digest ( "SHA-256" , ( new TextEncoder ( ) . encode ( code _verifier _code ) ) ) ;
const code _challenge = Array . from ( new Uint8Array ( code _challenge _array ) )
. map ( ( item ) => item . toString ( 16 ) . padStart ( 2 , "0" ) )
. join ( "" ) ;
const scope = [ "DEVICE_CREDENTIAL_MANAGEMENT" , "DEVICES_READ" , "MERCHANT_PROFILE_READ" , "MERCHANT_PROFILE_WRITE" , "CUSTOMERS_WRITE" , "CUSTOMERS_READ" , "PAYMENTS_READ" , "PAYMENTS_WRITE" ] . join ( "+" ) ;
const state = global . apis . util . uuid . v4 ( ) ;
const url = ` ${ URL _BASE } /oauth2/authorize?client_id= ${ APP _ID } &scope= ${ scope } &session=false&redirect_uri= ${ REDIRECT _URI } &code_challenge= ${ code _challenge } &state= ${ state } ` ;
async function onBrowserLogin ( url ) {
console . log ( url ) ;
if ( url . startsWith ( REDIRECT _URI ) ) {
console . log ( "Got oauth redirect!" ) ;
global . apis . eventbus . emit ( "browserCloseRequest" ) ;
global . apis . eventbus . off ( "browserNavigate" , onBrowserLogin ) ;
var params = url . split ( "?" ) [ 1 ] ;
var query = new URLSearchParams ( params ) ;
if ( query . get ( "error" ) != null ) {
global . apis . alert ( ` Could not authorize PostalPoint to access your Square account. Error code ${ query . get ( "error" ) } ( ${ query . get ( "error_description" ) } ) ` , "Square Login Error" ) ;
return ;
}
if ( query . get ( "response_type" ) == "code" && query . get ( "code" ) != null ) {
var authorizationCode = query . get ( "code" ) ;
var resp = await global . apis . util . http . post ( ` ${ URL _BASE } /oauth2/token ` , {
client _id : APP _ID ,
grant _type : "authorization_code" ,
redirect _uri : REDIRECT _URI ,
code : authorizationCode ,
code _verifier : code _verifier _code
} , "json" , {
"Content-Type" : "application/json" ,
"Square-Version" : ` ${ SQUARE _API _VERSION } `
} ) ;
if ( ! resp . access _token ) {
global . apis . alert ( ` Could not authorize PostalPoint to access your Square account. The token request failed. ` , "Square Login Error" ) ;
return ;
}
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token" , resp . access _token ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.merchant_id" , resp . merchant _id ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token" , resp . refresh _token ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token_expires_at" , resp . refresh _token _expires _at ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token_expires_at" , resp . expires _at ) ;
global . apis . alert ( "PostalPoint is now logged in to your Square merchant account." , "Connected to Square" ) ;
} else {
global . apis . alert ( ` Could not authorize PostalPoint to access your Square account. No authorization code (or error code) was received from Square. ` , "Square Login Error" ) ;
return ;
}
}
}
global . apis . eventbus . on ( "browserNavigate" , onBrowserLogin ) ;
global . apis . ui . openInternalWebBrowser ( url ) ;
}
/ * *
* Checks if the Square tokens are expiring "soon" and refresh them if needed .
* @ returns { undefined }
* /
async function refreshTokens ( ) {
var accessExpiresAt = global . apis . settings . get ( "app.postalpoint.squareterminal.access_token_expires_at" , "" ) ;
var refreshExpiresAt = global . apis . settings . get ( "app.postalpoint.squareterminal.refresh_token_expires_at" , "" ) ;
if ( accessExpiresAt == "" || refreshExpiresAt == "" ) {
return ;
}
const refreshExpireThreshold = 60 * 60 * 24 * 30 ; // 30 days
const accessExpireThreshold = 60 * 60 * 24 * 7 ; // 7 days
if ( global . apis . util . time . strtotime ( accessExpiresAt ) < global . apis . util . time . now ( ) - accessExpireThreshold || global . apis . util . time . strtotime ( refreshExpiresAt ) < global . apis . util . time . now ( ) - refreshExpireThreshold ) {
// Renew tokens
console . log ( "Renewing Square tokens" ) ;
var resp = await global . apis . util . http . post ( ` ${ URL _BASE } /oauth2/token ` , {
client _id : APP _ID ,
grant _type : "refresh_token" ,
redirect _uri : REDIRECT _URI ,
refresh _token : global . apis . settings . get ( "app.postalpoint.squareterminal.refresh_token" , "" ) ,
} , "json" , {
"Content-Type" : "application/json" ,
"Square-Version" : ` ${ SQUARE _API _VERSION } `
} ) ;
if ( ! resp . access _token ) {
global . apis . alert ( ` You need to log in to Square again in the plugin settings. ` , "Square Logged Out" ) ;
return ;
}
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token" , resp . access _token ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.merchant_id" , resp . merchant _id ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token" , resp . refresh _token ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token_expires_at" , resp . refresh _token _expires _at ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token_expires_at" , resp . expires _at ) ;
}
}
/ * *
* Clear the Square access tokens and info from the settings storage , then display a message to the user .
* @ returns { undefined }
* /
function logoutFromSquare ( ) {
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.merchant_id" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.refresh_token_expires_at" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.access_token_expires_at" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.terminal_device_code_id" , null ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.location_id" , null ) ;
global . apis . alert ( ` PostalPoint is now logged out of your Square account. You'll need to log in again to use Square with PostalPoint. ` , "Square Logged Out" ) ;
}
async function pairTerminal ( ) {
var locationId = "main" ;
var locations = await apiRequest ( "v2/locations" , { } , "GET" ) ;
var locationList = [ ] ;
for ( var i = 0 ; i < locations . locations . length ; i ++ ) {
if ( locations . locations [ i ] . type != "PHYSICAL" ) {
continue ;
}
if ( locations . locations [ i ] . status != "ACTIVE" ) {
continue ;
}
if ( locations . locations [ i ] . capabilities . includes ( "CREDIT_CARD_PROCESSING" ) != true ) {
continue ;
}
locationList . push ( {
id : locations . locations [ i ] . id ,
name : locations . locations [ i ] . name
} ) ;
}
if ( locationList . length == 0 ) {
global . apis . alert ( "Your Square account doesn't have a physical location that can process card payments. Set one up in your Square Dashboard." , "Square Error" ) ;
return ;
}
if ( locationList . length == 1 ) {
locationId = locationList [ 0 ] . id ;
getTerminalPairingCode ( locationId ) ;
} else {
var buttons = [ ] ;
for ( let i = 0 ; i < locationList . length ; i ++ ) {
buttons . push ( {
text : locationList [ i ] . name ,
onClick : function ( ) {
getTerminalPairingCode ( locationList [ i ] . id ) ;
}
} ) ;
}
global . apis . f7 . dialog . create ( {
title : "Select Square location" ,
text : "Your Square account has multiple locations. Which one is PostalPoint set up at?" ,
verticalButtons : true ,
buttons : buttons
} ) . show ( ) ;
}
}
async function getTerminalPairingCode ( locationId = "main" ) {
var resp = await apiRequest ( "v2/devices/codes" , {
idempotency _key : global . apis . util . uuid . v4 ( ) ,
device _code : {
product _type : "TERMINAL_API" ,
location _id : locationId
}
} ) ;
console . log ( resp ) ;
global . apis . f7 . dialog . alert ( ` On your Square Terminal device's sign in page, tap "Use a device code" and enter this pairing code within five minutes. Wait for the Square Terminal to be fully loaded, then press OK here.<br /><br /><h3> ${ resp . device _code . code } </h3> ` , "Pairing Code" , async function ( ) {
resp = await apiRequest ( ` v2/devices/codes/ ${ resp . device _code . id } ` , { } , "GET" ) ;
console . log ( resp ) ;
if ( resp . device _code . status != "PAIRED" ) {
global . apis . ui . showProgressSpinner ( "Pairing..." , "Pairing to the Square Terminal is taking a little while." ) ;
await global . apis . util . delay ( 10000 ) ;
resp = await apiRequest ( ` v2/devices/codes/ ${ resp . device _code . id } ` , { } , "GET" ) ;
console . log ( resp ) ;
}
global . apis . ui . hideProgressSpinner ( ) ;
if ( resp . device _code . status != "PAIRED" ) {
global . apis . alert ( "Your Square Terminal is not paired. Please try again." , "Pairing Failed" ) ;
return ;
}
global . apis . settings . set ( "app.postalpoint.squareterminal.terminal_device_id" , resp . device _code . device _id ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.terminal_device_code_id" , resp . device _code . id ) ;
global . apis . settings . set ( "app.postalpoint.squareterminal.location_id" , resp . device _code . location _id ) ;
global . apis . alert ( "Your Square Terminal has been paired with PostalPoint. You're ready to take card payments!" , "Pairing Complete" ) ;
} ) ;
}
/ * *
* Poll a list of payment IDs and wait for them to stop being PENDING . If a payment needs capture , do that too .
* @ param { array } paymentIDs
* @ returns { nm$ _plugin . waitForPaymentsToFinish . paymentData }
* /
async function waitForPaymentsToFinish ( paymentIDs = [ ] ) {
var paymentData = {
amountCaptured : 0 ,
paymentsCaptured : [ ]
} ;
for ( let i = 0 ; i < paymentIDs . length ; i ++ ) {
var payment = await apiRequest ( ` v2/payments/ ${ paymentIDs [ i ] } ` , { } , "GET" ) ;
do {
if ( payment . payment . status == "PENDING" ) {
global . apis . pos . addOnscreenPaymentLog ( "Waiting for payment to process..." ) ;
await global . apis . util . delay ( 2000 ) ;
payment = await apiRequest ( ` v2/payments/ ${ paymentIDs [ i ] } ` , { } , "GET" ) ;
} else if ( payment . payment . status == "APPROVED" ) {
// capture payment
payment = await apiRequest ( ` v2/payments/ ${ paymentIDs [ i ] } /complete ` , { } ) ;
}
} while ( payment . payment . status == "PENDING" || payment . payment . status == "APPROVED" ) ;
switch ( payment . payment . status ) {
case "COMPLETED" :
paymentData . amountCaptured += payment . payment . amount _money . amount ;
paymentData . paymentsCaptured . push ( payment . payment ) ;
break ;
case "CANCELED" :
break ;
case "FAILED" :
break ;
}
}
return paymentData ;
}
function addPaymentsToReceipt ( payments ) {
for ( let i = 0 ; i < payments . length ; i ++ ) {
var p = payments [ i ] ;
if ( p . payment ) {
p = p . payment ;
}
var ptype = "card" ;
var pstring = "" ;
switch ( p . source _type ) {
case "CARD" :
ptype = "card" ;
if ( p . card _details . entry _method == "EMV" ) {
pstring = ` ${ p . card _details . card . cardholder _name } \n ${ p . card _details . card . card _brand } \n x ${ p . card _details . card . last _4 } \n EMV Chip \n ${ p . card _details . application _name } \n AID: ${ p . card _details . application _identifier } ` ;
} else {
pstring = ` ${ p . card _details . card . cardholder _name ? ? "" } \n ${ p . card _details . card . card _brand } \n x ${ p . card _details . card . last _4 } \n ` ;
switch ( p . card _details . entry _method ) {
case "KEYED" :
pstring += "Keyed-in" ;
break ;
case "SWIPED" :
pstring += "Swiped" ;
break ;
case "ON_FILE" :
pstring += "Card on file" ;
break ;
case "CONTACTLESS" :
pstring += "Contactless" ;
break ;
}
}
break ;
case "BANK_ACCOUNT" :
ptype = "ach" ;
if ( p . bank _account _details . transfer _type == "ACH" ) {
pstring = ` ${ p . bank _account _details . bank _name } \n x ${ p . bank _account _details . ach _details . account _number _suffix } \n ${ p . bank _account _details . ach _details . account _type } ` ;
} else {
pstring = ` ${ p . bank _account _details . bank _name } ` ;
}
break ;
case "CASH" :
ptype = "cash" ;
break ;
case "WALLET" :
ptype = "card" ;
pstring = ` ${ p . wallet _details . brand } ` ;
if ( p . wallet _details . brand == "CASH_APP" ) {
pstring += ` \n ${ p . wallet _details . cash _app _details . buyer _full _name } \n ${ p . wallet _details . cash _app _details . buyer _cashtag } ` ;
}
break ;
case "BUY_NOW_PAY_LATER" :
ptype = "card" ;
pstring = ` Buy Now, Pay Later \n ${ p . buy _now _pay _later _details . brand } ` ;
break ;
case "SQUARE_ACCOUNT" :
ptype = "card" ;
pstring = "Square Account" ;
break ;
case "EXTERNAL" :
switch ( p . external _details . type ) {
case "CHECK" :
ptype = "check" ;
break ;
case "BANK_TRANSFER" :
ptype = "ach" ;
break ;
case "CRYPTO" :
ptype = "crypto" ;
break ;
default :
ptype = "card" ;
break ;
}
pstring = ` ${ p . external _details . type } \n ${ p . external _details . source } ` ;
break ;
}
global . apis . pos . addReceiptPayment (
new global . apis . pos . ReceiptPayment (
( p . amount _money . amount / 100 ) . toFixed ( 2 ) * 1 ,
ptype , // 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.
pstring // Additional text for receipt
)
) ;
}
}
exports . init = function ( ) {
setInterval ( refreshTokens , 1000 * 60 * 60 ) ;
global . apis . pos . registerCardProcessor ( {
name : "Square" ,
init : async function ( ) {
// This function runs once after starting PostalPoint
// and before any other card processor functions are called.
console . info ( "Hello from Square Terminal plugin!" ) ;
} ,
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 {
// 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
var checkoutData = await apiRequest ( "v2/terminals/checkouts" , {
idempotency _key : global . apis . util . uuid . v4 ( ) ,
checkout : {
amount _money : {
amount : amount ,
currency : "USD"
} ,
device _options : {
device _id : global . apis . settings . get ( "app.postalpoint.squareterminal.terminal_device_id" , "" ) ,
tip _settings : {
allow _tipping : false
} ,
skip _receipt _screen : true
} ,
payment _options : {
autocomplete : capture
}
}
} ) ;
checkout _id = checkoutData . checkout . id ;
checkoutStatus = checkoutData . checkout . status ;
while ( checkoutStatus != "COMPLETED" && checkoutStatus != "CANCELED" ) {
await global . apis . util . delay ( 1000 ) ; // Wait a second
checkoutData = await apiRequest ( ` v2/terminals/checkouts/ ${ checkout _id } ` , { } , "GET" ) ;
checkoutStatus = checkoutData . checkout . status ;
}
if ( checkoutStatus != "COMPLETED" ) {
global . apis . pos . addOnscreenPaymentLog ( "Square Terminal payment not completed." ) ;
if ( checkoutStatus == "CANCELED" ) {
global . apis . alert ( "The card payment was canceled before it finished." , "Payment canceled" ) ;
switch ( checkoutData . checkout . cancel _reason ) {
case "BUYER_CANCELED" :
global . apis . pos . addOnscreenPaymentLog ( "The payment was canceled from the Square device." ) ;
break ;
case "SELLER_CANCELED" :
global . apis . pos . addOnscreenPaymentLog ( "The payment was canceled by the merchant." ) ;
break ;
case "TIMED_OUT" :
global . apis . pos . addOnscreenPaymentLog ( "The payment was canceled because it took too long to complete." ) ;
break ;
}
} else {
global . apis . alert ( ` The card payment was not completed, and has status code ${ checkoutStatus } . ` , "Payment not complete" ) ;
}
return false ;
}
console . log ( checkoutData ) ;
if ( checkoutData . checkout . payment _options . autocomplete ) {
var paymentInfos = await waitForPaymentsToFinish ( checkoutData . checkout . payment _ids ) ;
console . log ( paymentInfos ) ;
addPaymentsToReceipt ( paymentInfos . paymentsCaptured ) ;
global . apis . pos . addOnscreenPaymentLog ( "Payment successful!" ) ;
return true ;
} else {
return checkoutData . checkout . payment _ids . join ( "," ) ;
}
} catch ( ex ) {
global . apis . pos . addOnscreenPaymentLog ( ` Error: ${ ex . message } ` ) ;
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 : async function ( ) {
// The user requested to cancel the payment.
// Reset the terminal to its resting state, clear its screen, etc.
try {
if ( checkout _id != null ) {
var resp = await apiRequest ( ` v2/terminals/checkouts/ ${ checkout _id } /cancel ` ) ;
if ( resp . checkout . status == "CANCELED" ) {
checkout _id = null ;
}
}
} catch ( ex ) {
console . error ( ex ) ;
}
} ,
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.
var paymentIDs = checkoutResponse . split ( "," ) ;
var paymentInfos = await waitForPaymentsToFinish ( paymentIDs ) ;
addPaymentsToReceipt ( paymentInfos . paymentsCaptured ) ;
return true ;
} ,
updateCartDisplay : function ( receipt ) {
// no-op
} ,
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.
var paymentResponse = await apiRequest ( ` v2/payments ` , {
idempotency _key : global . apis . util . uuid . v4 ( ) ,
amount _money : {
amount : amount ,
currency : "USD"
} ,
autocomplete : true ,
source _id : paymentMethodID ,
customer _id : customerID ,
location _id : global . apis . settings . get ( "app.postalpoint.squareterminal.location_id" , "main" )
} , "POST" ) ;
console . log ( paymentResponse ) ;
if ( typeof paymentResponse . errors != "undefined" ) {
global . apis . pos . addOnscreenPaymentLog ( "Error processing saved payment: " + paymentResponse . errors [ 0 ] . code ) ;
global . apis . alert ( paymentResponse . errors [ 0 ] . code , "Saved payment error" ) ;
return false ;
} else {
if ( paymentResponse . payment . status == "COMPLETED" ) {
addPaymentsToReceipt ( [ paymentResponse . payment ] ) ;
return true ;
} else if ( paymentResponse . payment . status == "CANCELED" ) {
global . apis . pos . addOnscreenPaymentLog ( "The saved card payment was canceled." ) ;
global . apis . alert ( "The saved card payment was canceled before it finished." , "Payment canceled" ) ;
return false ;
} else if ( paymentResponse . payment . status == "FAILED" ) {
global . apis . pos . addOnscreenPaymentLog ( "The saved card payment failed." ) ;
global . apis . alert ( "The saved card payment failed." , "Payment failed" ) ;
return false ;
}
}
return false ;
} ,
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.
// TODO: Lookup or create Square customer
statusCallback ( "Syncing customer data with Square" , false ) ;
var squareCustomerID = "" ;
var customerSearchResponse = await apiRequest ( ` v2/customers/search ` , {
query : {
filter : {
reference _id : {
exact : customerUUID
}
}
} ,
limit : 2
} , "POST" ) ;
if ( Object . keys ( customerSearchResponse ) . length == 0 || customerSearchResponse . customers . length == 0 ) {
// Create customer
var newCustomerInfo = await apiRequest ( "v2/customers" , {
idempotency _key : global . apis . util . uuid . v4 ( ) ,
given _name : name . split ( " " , 2 ) [ 0 ] ,
family _name : ( name . split ( " " , 2 ) [ 1 ] ? ? "" ) ,
nickname : name ,
company _name : company ,
email _address : email ,
phone _number : phone ,
address : {
address _line _1 : street1 ,
locality : city ,
administrative _district _level _1 : state ,
postal _code : zip
}
} , "POST" ) ;
squareCustomerID = newCustomerInfo . customer . id ;
} else if ( customerSearchResponse . customers . length == 1 ) {
// Customer found
squareCustomerID = customerSearchResponse . customers [ 0 ] . id ;
} else {
// Too many customers found
throw new Error ( "There are multiple customers in your Square account with the same PostalPoint customer UUID number (a.k.a. Square reference ID). PostalPoint can't tell which one to use for this action." ) ;
return false ;
}
statusCallback ( "Insert the card into the reader." , false ) ;
var actionResponse = await apiRequest ( ` v2/terminals/actions ` , {
idempotency _key : global . apis . util . uuid . v4 ( ) ,
action : {
device _id : global . apis . settings . get ( "app.postalpoint.squareterminal.terminal_device_id" , "" ) ,
type : "SAVE_CARD" ,
save _card _options : {
customer _id : squareCustomerID ,
reference _id : customerUUID
}
}
} , "POST" ) ;
// Save this in case we need to cancel
latestTerminalActionID = actionResponse . action . id ;
do {
await global . apis . util . delay ( 1000 ) ;
actionResponse = await apiRequest ( ` v2/terminals/actions/ ${ actionResponse . action . id } ` , { } , 'GET' ) ;
} while ( actionResponse . action . status == "PENDING" || actionResponse . action . status == "IN_PROGRESS" ) ;
if ( actionResponse . action . status == "COMPLETED" ) {
statusCallback ( "Card saved!" , true ) ;
return true ;
} else if ( actionResponse . action . status == "CANCELED" || actionResponse . action . status == "CANCEL_REQUESTED" ) {
throw new Error ( "Operation cancelled." ) ;
return false ;
} else {
throw new Error ( "Can't determine if card was saved at this time." ) ;
return false ;
}
} ,
cancelSaveCardForOfflineUse : function ( ) {
// Cancel the process running in saveCardForOfflineUse() at the user/cashier's request.
if ( latestTerminalActionID != "" ) {
apiRequest ( ` v2/terminals/actions/ ${ latestTerminalActionID } /cancel ` , { } , "POST" ) ;
latestTerminalActionID = "" ;
}
} ,
getSavedPaymentMethods : async function ( { customerUUID } ) {
// Return all saved payment methods tied to the provided customer UUID.
var methods = [ ] ;
var response = await apiRequest ( ` v2/cards?reference_id= ${ customerUUID } ` , { } , "GET" ) ;
if ( Object . keys ( response ) . length == 0 ) {
return [ ] ;
}
console . log ( response ) ;
for ( let i = 0 ; i < response . cards . length ; i ++ ) {
var card = response . cards [ i ] ;
methods . push ( {
customer : card . customer _id , // Passed to checkoutSavedMethod as cardProcessorCustomerID
customer _uuid : customerUUID ,
id : card . id , // Passed to checkoutSavedMethod as paymentMethodID
type : "card" , // Payment type. Accepted values are card, ach, crypto, cash, check, account, and free.
label : ` ${ card . card _brand } ${ card . card _type } x ${ card . last _4 } (exp. ${ card . exp _month } / ${ card . exp _year } ) ` , // Label for payment method
label _short : ` ${ card . card _brand } ${ card . card _type } x ${ card . last _4 } ` // Abbreviated label for payment method
} ) ;
}
console . log ( methods ) ;
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 apiRequest ( ` v2/cards/ ${ paymentMethodID } /disable ` , { } , "POST" ) ;
}
} ) ;
}
// Plugin settings to display.
exports . config = [
{
type : "button" ,
label : "Log In to Square" ,
text : "Step 1: Connect PostalPoint to your Square merchant account." ,
onClick : function ( ) {
loginToSquare ( ) ;
}
} ,
{
type : "button" ,
label : "Pair Square Terminal hardware" ,
text : "Step 2: Connect a Square Terminal device to PostalPoint and accept card payments." ,
onClick : function ( ) {
pairTerminal ( ) ;
}
} ,
{
type : "button" ,
label : "Log Out from Square" ,
text : "Disconnect PostalPoint from your Square merchant account." ,
onClick : function ( ) {
logoutFromSquare ( ) ;
}
}
] ;