JS integration
After obtained secureId
from Auth API: tokenization or Auth API: purchase, you can use our JS SDK to embed the card input element directly on your website and receive callbacks directly from the SDK.
How to use
1. Include grubpay.js script and create a container for the form in your html
Also, don't forget to add a <button>
for submitting
<script type="text/javascript" src="https://api.grubpay.io/v4/grubpay.js">
<div id="grubpay-container"></div>
<button onclick="onSubmit()" id="submit-button">Pay</button>
secureId
and optional options
2. Create an GrubpayElement instance using options
is optional, you can leave it blank, or find example options here
var GrubpayElement = Grubpay(secureId, options);
async
or .then()
syntax
3. Mount the form to the container you just created with var mounted = false;
async function initializeGrubpay() {
// use async/await
mounted = await GrubpayElement.mount('#grubpay-container');
// or use .then()
// GrubpayElement.mount('#grubpay-container').then((res) => {
// mounted = res;
// });
// If false returned, check the console log for error
// It's often caused by an invalid secureId that's been paid or expired
}
initializeGrubpay();
submitPayment()
with async
or .then()
syntax
4. Call the async function onSubmit() {
// lock the button to prevent duplicate submission
document.querySelector('#submit-button').disabled = true;
var res = await GrubpayElement.submitPayment({
// this is defaulted to false,
// if set to true, customer's choice on the checkbox will be ignored
// and card's token will be return regardless
forceSaveCard: false,
});
if (res.success) {
// Call success logic
// A check up from your backend is strongly RECOMMENDED
// Client side js could be altered thus make falsy callbacks
// this is where all the response data will be returned
console.log(res.data);
if (res.data.payOrderId) {
console.log('payment successful');
}
if (res.data.token) {
}
// window.location.replace(
// 'your result page to show success and query the order from your backend with ' +
// res.data.payOrderId +
// ' or for tokenize, save the card token' +
// res.data.token,
// );
// -----------------------------------------------------
// Recommended Action:
// Redirect to your success page, and query the order again from our API to confirm with res.data.payOrderId
} else {
if (res.validatorErrors) {
var formElement = document.querySelector('#grubpay-container');
window.scrollTo(formElement.offsetLeft, formElement.offsetTop);
// Errors with form validation,
// -----------------------------------------------------
// Recommended Action:
// scroll to the form's container and suggest user to check the input
} else if (res.invalidSecureId) {
window.location.replace('your result page to generate a new secureId and try again');
// if invalidSecureId is true, you will need to create a
// new secureId and restart the process
// -----------------------------------------------------
// Recommended Action:
// Regenerate secureId and start over again
} else if (res.timeout) {
window.location.replace('your result page to query the order');
// In rare cases
// When purchasing
// The payment is submitted, and the order is queried for 30 seconds
// but we did not get the success result. (status is not 2 or 3)
// This does NOT mean the transaction is failed,
// please query the order from your end to confirm the status
// -----------------------------------------------------
// Recommended Action:
// Change order's status to pending, and query the result by our API
} else if (res.captchaError) {
console.log("you don't need to do anything");
// In rare cases
// We will required captcha for security reasons
// When user canceled the captcha, this error will be returned
// -----------------------------------------------------
// Recommended Action:
// You don't have to do anything, or you can tell user captcha is required
} else if (res.agreementError) {
console.log("you don't need to do anything");
// When using ACH and
// when user did not agree to the ACH agreement
// -----------------------------------------------------
// Recommended Action:
// You don't have to do anything, or you can tell user agreement is required
} else if (res.error) {
alert(res.error);
// res.error is a string describes what went wrong
// All other possible errors
// Such as:
// - not enough fund on card
// - incorrect card number
// - payment failed
// - network error
// -----------------------------------------------------
// Recommended Action:
// You can directly show `res.error` message and let user try again
}
// Don't forget to unlock the button
document.querySelector('#submit-button').disabled = false;
}
}
Example options
const options = {
//---------------------------------
// Only available in pay mode
// Default is true, when showing, customer can decide if he/she wants
// to save the card, and if decided to save, card's token will be returned
// to the merchant upon submitPayment()
showSaveCardCheckBox: true,
defaultCheckBoxValue: false, // This option is only available in pay mode
checkboxToggleStyle: true, // true, false Checkbox or Switch/Toggle style?
// these are all the appearance options
// For simple usage, you can just specify a theme and colorPrimary
appearance: {
theme: 'default', // 'default','simple','minimal'
dense: false, // true, false
dark: false, // true, false
variables: {
// // ---------------------------------------------------------
// // Common CSS variables
// // ---------------------------------------------------------
// bodyColor: "#fff", // Background color of form's body
// fontColor: "#333", // Base color through out the element
// fontSize: "15px",
// fontFamily: "-apple-system, 'Segoe UI', 'Roboto', 'Helvetica Neue', 'helvetica neue', helvetica, arial, sans-serif",
// // ---------------------------------------------------------
// // This is the base theme color, alpha will be modified to provide
// // --color-primary-20, --color-primary-40, --color-primary-60
// // ---------------------------------------------------------
// colorPrimary: 'rgb(43, 196, 107)',
// // ---------------------------------------------------------
// // This is the base error color, alpha will be modified to provide
// // --color-danger-20, --color-danger-40, --color-danger-60
// // ---------------------------------------------------------
// colorDanger: "rgb(255, 84, 71)",
// borderRadius: "8px",
// borderColor: "rgba(155, 165, 175, 0.3)",
// borderWidth: "1px",
// borderStyle: "solid",
// ---------------------------------------------------------
// More CSS Variables
// ---------------------------------------------------------
// borderFocusColor: "var(--color-primary)",
// borderFocusWidth: "var(--border-width)",
// borderFocusStyle: "var(--border-style)",
// borderFocusOutlineWidth: "4px",
// borderFocusOutlineColor: "var(--color-primary-20)",
// labelColor: "rgba(67, 73, 85, 0.7)",
// labelFontSize: "16px",
// labelFontWeight: "500",
// labelFocusColor: "blue",
// labelFontFamily: "inherit",
// labelPadding: "0 0 4px 0",
// verticalGapSize: "20px",
// inputPadding: "16px 8px",
// inputFontSize: "16px",
// inputFontWeight: "inherit",
// inputColor: "inherit",
// inputBackgroundColor: "white",
// inputPlaceHolderColor: "rgb(204, 207, 214)",
// inputShadow: 'none',
// inputFocusShadow: 'none',
// checkBoxColor: "var(--color-primary)",
},
},
};
Update options
GrubpayElement.update(options);
Sample responses
Here are some sample response from await GrubpayElement.submitPayment()
const successPurchaseRes: GrubpayResponse = {
success: true,
data: {
payOrderId: 'PC20221130204820303051774745',
mchId: '10001234',
mchOrderNo: '1234567890',
originalOrderId: '',
currency: 'USD',
amount: 100,
payType: 'pay',
refundable: 100,
status: 2,
paySuccTime: '2022-11-30 20:49:03',
authNum: 'PPS009',
transNum: '322736173192',
channel: 'CC_CARD',
token: 'tbnea6bo',
capture: 'Y',
// This token param is only when saveUser: true is set
token: '1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa',
cardNum: '412345**********1234',
expiryDate: '1225', // MMYY
// only when returnUrl is set
returnUrl:
'https://example.com/order/5?retCode=SUCCESS&retMsg=&status=2&payOrderId=PC20221130011309597814807931',
},
userWantSaveCard: true,
forceSaveCard: false,
};
const successTokenizeRes: GrubpayResponse = {
success: true,
data: {
token: 'cf6e8f3f-c010-4b99-9a9f-32cf21ea7cb6',
cardNum: '412345**********1234',
expiryDate: '1025',
zip: '12345',
pan: '896a38b73df44c2e0da633b4c3843b51',
// only when returnUrl is set
returnUrl:
'https://example.com/order/5?retCode=SUCCESS&retMsg=&token=1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa',
},
};
// In this case:
// 'capture' = 'Y' means purchase
// 'status' = 9 means failed
// so this payment is failed
const orderError: GrubpayResponse = {
success: false,
error: 'Transaction failed',
data: {
payOrderId: 'PC20221119011952089577758119',
mchId: '11111628',
mchOrderNo: '1668820792',
originalOrderId: '',
currency: 'USD',
amount: 100,
payType: 'pay',
refundable: 100,
status: 9,
invoiceNum: '089577758119',
capture: 'Y',
redirectUrl: 'https://example.com/order/5?&retCode=FAIL&status=9&retMsg=error',
},
};
const paidError: GrubpayResponse = {
success: false,
error: 'This order is already paid.',
invalidSecureId: true,
};
const invalidSecureIdError: GrubpayResponse = {
success: false,
error: 'This secureId is invalid or expired, please generate another one',
invalidSecureId: true,
};
const timeoutError: GrubpayResponse = {
success: false,
timeout: true,
error: 'Query order timeout after 30s',
message: null,
hint: 'The payment is checked for 30s but we did not get the result. This does NOT mean the payment is failed, please check the order on your side to confirm',
data: {
payOrderId: 'PC20221119011952089577758119',
mchId: '11111628',
mchOrderNo: '1668820792',
originalOrderId: '',
currency: 'USD',
amount: 100,
payType: 'pay',
refundable: 100,
status: 0,
invoiceNum: '089577758119',
capture: 'Y',
redirectUrl:
'https://develop.iotpay.ca/devdemo/grubpay/result.php?abc=111&code=234&retCode=SUCCESS&status=0#foude',
},
};
const platformError: GrubpayResponse = {
success: false,
error: 'Network error',
};
const validatorError: GrubpayResponse = {
success: false,
error: 'There are some errors on your credit card information, please check your input.',
validatorErrors: {
cardNum: 'Invalid card number',
holder: 'Name is required',
expiryDate: 'Invalid expiry date',
cvv: 'Invalid cvv',
zip: 'Zip required',
},
};
const awaitError: GrubpayResponse = {
success: false,
error: 'Please wait for the previous call to be completed',
};
const notMountedError: GrubpayResponse = {
success: false,
error: 'GrubpayElement form is not mounted yet, did you call GrubpayElement.mount(#id)',
};
const captchaError: GrubpayResponse = {
success: false,
error: 'Please complete captcha first',
captchaError: true,
};
const agreementError: GrubpayResponse = {
success: false,
error: 'Your must agree to our ach agreement to proceed',
agreementError: true,
};
Dispose form
You can dispose the form by calling dispose()
This will remove the form from your container
GrubpayElement.dispose();
Reference: Types
For reference, here are the types we use:
export interface GrubpayInstance {
mount(target: string): Promise<boolean>;
update(option: GrubpayOption): void;
submitPayment(params: GrubpaySubmitParams | null): Promise<GrubpayResponse>;
dispose(): void;
}
export interface GrubpayOption {
// Show a checkbox to let user choose to save card
// if user checked, card's token will be returned upon success purchase
showSaveCardCheckBox?: boolean;
// A default value for the checkbox
defaultCheckBoxValue?: boolean;
// Toggle style / Checkbox style
checkboxToggleStyle?: boolean;
// Some detailed appearance
appearance?: GrubpayAppearance;
}
export interface GrubpayAppearance {
// The base theme of the element
theme?: string; // 'default' 'simple' 'minimal'
// Make is dense, takes less space
dense?: boolean;
// Dark theme?
dark?: boolean;
// Extra style variables
variables?: GrubpayCssVariables;
}
export interface GrubpayCssVariables {
fontColor?: string;
fontSize?: string | number;
fontFamily?: string;
colorPrimary?: string;
colorDanger?: string;
borderRadius?: string | number;
borderColor?: string;
borderWidth?: string | number;
borderStyle?: string;
borderFocusColor?: string;
borderFocusWidth?: string | number;
borderFocusStyle?: string;
borderFocusOutlineWidth?: string | number;
borderFocusOutlineColor?: string;
labelColor?: string;
labelFontSize?: string;
labelFontWeight?: string | number;
labelFocusColor?: string;
labelFontFamily?: string;
labelPadding?: string | number;
verticalGapSize?: string | number;
horizontalGapSize?: string | number;
inputPadding?: string;
inputFontSize?: string | number;
inputFontWeight?: string | number;
inputColor?: string;
inputBackgroundColor?: string;
inputPlaceHolderColor?: string;
inputShadow?: string;
inputFocusShadow?: string;
checkboxColor?: string;
}
export interface GrubpayResponse {
// If the result is success
success: boolean;
// The is equivalent to retMsg from our API
message?: string | null;
// The is equivalent to retData from our API
data?: GrubpayResponseData | null;
// SecuredId expired or invalid or paid, a new one need to be generated
invalidSecureId?: boolean | null;
// The reason caused it to fail
error?: string | null;
// Time out when query order, this does NOT mean the order is failed, further query from merchant's side need to be performed
timeout?: boolean | null;
// This field is usually empty
hint?: string | null;
// Input form has validation error, request not sent
validatorErrors?: object | null;
// User cancelled captcha
captchaError?: boolean | null;
// User cancelled agreement
agreementError?: boolean | null;
// When captcha is required
requireCaptcha?: boolean | null;
// When ach agreement is required
requireAchAgreement?: boolean | null;
// If use have selected the 'Save card' checkbox
userWantSaveCard?: boolean | null;
// If the merchant requested a forceSaveCard
forceSaveCard?: boolean | null;
}
export interface GrubpayResponseData {
// Purchase related results ------------------------------------
payOrderId?: string; // 'PC20221119005622848403095837'
mchId?: string; // '10001234'
mchOrderNo?: string; // '1234567890'
originalOrderId?: string; // ''
currency?: string; // 'USD'
amount?: number; // 100 Amount unit in cents
payType?: string; // 'pay'
refundable?: number; // 100 How much can be refunded?
status?: number; // 0 = pending, 2 = paid, 3 = paid and notified, others = failed
invoiceNum?: string; // '848403095837'
paySuccTime?: string; // 2022-11-19 00:56:36
cardType?: string; // ''
authNum?: string; // 'PPS009'
transNum?: string; // '322838071796'
channel?: string; // 'CC_CARD'
capture?: string; // 'Y' or 'N'
// Tokenize related results ------------------------------------
token?: string; // '1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa'
cardNum?: string; // '412345**********1234'
expiryDate?: string; // '1025'
pan?: string; // '896a38b73df44c2e0da633b4c3843b51' Card's MD5
// Conditional results ------------------------------------
returnUrl?: string;
}
export interface GrubpaySubmitParams {
forceSaveCard?: boolean;
}