Embedded Integration
How It Works
Endpoint
https://api.grubpay.io/v4/auth
Method:
POST
Header
Content-Type: application/json;charset=UTF-8
Card Purchase Guide
What is capture
Capture authorizes the transaction through to settlement and may be triggered during purchase request (see capture field in the request) or some time after the initial authorization is submitted via Capture api.
! Tip
After you receive access credentials (merchant id, login name, merchant key). You can step through the demo Card Purchase which may help you when going through the documentation.
Endpoint with format below
1. Request toRequest
Name | Required | Type | Sample | Description |
---|---|---|---|---|
mchId | y | String | 10000701 | assigned by GrubPay |
mchOrderNo | y | String | 1234567890abc | assigned by merchant |
amount | y | Int | 1500 | in cents; must > 0 |
currency | y | String(3) | USD | fixed to: USD |
loginName | y | String(12) | jack123 | merchant's login name |
subject | n | String(64) | ||
body | n | String(250) | ||
channel | y | String | CC_CARD | fixed to: CC_CARD |
capture | n | String | Y or N (default Y) | specify Y to capture the transaction for settlement if approved |
sign | y | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | Sign algorithm |
Response
name | Required | type | sample | description |
---|---|---|---|---|
retCode | y | String | SUCCESS or FAIL | |
retMsg | y | String | ||
retData.secureId | y | String | For usage see secureId |
secureId
from response above to create a GrubPay instance through JS SDK. With secureId
in hand, continue to SDK Integration
2. You will need the Card Tokenize Guide
Info
GrubPay provides encryption and tokenization services with a secure data vault typically used to store payment card Primary Account Numbers in a PCI-DSS compliant manner. GrubPay generates a unique and random token value to be used by your application instead of a clear or encrypted PAN, potentially reducing your PCI scope considerably.
! Tip
After you receive access credentials (merchant id, login name, merchant key). You can step through the demo Card Tokenize which may help you when going through the documentation.
Endpoint with format below
1. Request toRequest
name | required | type | sample | description |
---|---|---|---|---|
mchId | y | String | 10000701 | assigned by GrubPay |
loginName | y | String(12) | jack123 | merchant's login name |
channel | y | String | CC_CARD | fixed to: CC_CARD |
sign | y | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | Sign algorithm |
Response
name | required | type | sample | description |
---|---|---|---|---|
retCode | y | String | SUCCESS or FAIL | |
retMsg | y | String | ||
retData.secureId | y | String | For usage see secureId |
secureId
from tokenization response as above to create a GrubPay instance and mount a container through JS SDK. With secureId
in hand, continue to SDK Integration
2. You will need the ACH Transfer Guide
Tip
ACH transactions are automatically captured upon authorization. There is no tokenization api, tokens for direct method are created upon successful ACH transfer.
Endpoint with format below
1. Request toRequest
Name | Required | Type | Sample | Description |
---|---|---|---|---|
mchId | y | String | 10000701 | assigned by GrubPay |
mchOrderNo | y | String | 1234567890abc | assigned by merchant |
amount | y | Int | 1500 | in cents; must > 0 |
currency | y | String(3) | USD | fixed to: USD |
loginName | y | String(12) | jack123 | merchant's login name |
subject | n | String(64) | ||
body | n | String(250) | ||
channel | y | String | CC_ACH | fixed to: CC_ACH |
notifyUrl | y | String(200) | http://sample.com/notify | merchant endpoint for receiving ACH transaction notifications , must be http or https |
sign | y | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | Sign algorithm |
Response
name | Required | type | sample | description |
---|---|---|---|---|
retCode | y | String | SUCCESS or FAIL | |
retMsg | y | String | ||
retData.secureId | y | String | For usage see secureId |
secureId
from response above to create a GrubPay instance through JS SDK. With secureId
in hand, continue to SDK Integration
2. You will need the JS SDK
Integration Guide
Prerequisite
secureId
from Auth API: tokenization or Auth API: purchase
Integration with ES Module / NPM / TypeScript
Refer to npm @grubpay/grubpay-js
Integration with including js file
Step 1: Include Js
Include grubpay.js script and create a container for GrubPay form in your html and 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>
Step 2: Create Grubpay
Create an GrubpayElement instance using secureId
and optional options
. See here for possible configurations to options
. For updating GrubpayElement options
after initialization see Updating options.
var GrubpayElement = Grubpay(secureId);
// or
var GrubpayElement = Grubpay(secureId, options);
Step 3: Mount Grubpay in your HTML
Mount the form to the container you just created with async
or .then()
syntax
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();
Step 4: Call submitPayment() when user click the submit button in your html
Call the submitPayment() with async
or .then()
syntax. Depending on your use case (purchase or tokenize) you will receive different responses in the callback. The next section sample responses details this.
Congratulations, You're done!
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;
}
}
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: '334728756943',
channel: 'CC_CARD',
capture: 'Y',
// This token param is only when saveUser: true is set
token: '1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa',
cardNum: '412345**********1234',
expiryDate: '1025', // 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: '1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa',
cardNum: '412345**********1234',
expiryDate: '1025', // MMYY
zip: '12345',
pan: '896a38b73df44c2e0da633b4c3843b51', // Card's MD5
// 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,
capture: 'Y',
},
};
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,
capture: 'Y',
},
};
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',
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,
};
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()
// If this is set to true, merchant's params on submitPayment({saveCard:true})
// will be ignored
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
// // ---------------------------------------------------------
// // 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);
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;
bodyColor?: 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 | null; // 2022-11-19 00:56:36
cardType?: string; // ''
authNum?: string; // 'PPS009'
transNum?: string; // '322838071796'
channel?: string; // 'CC_CARD', 'CC_ACH'
capture?: string; // 'Y' or 'N'
// Tokenize related results ------------------------------------
token?: string; // '1aaaaa11-1a11-aaa1-aa11-11a11aa1a1aa'
cardNum?: string; // '412345**********1234'
expiryDate?: string; // '1025', MMYY, only available in CC_CARD, not available in CC_ACH
pan?: string; // '896a38b73df44c2e0da633b4c3843b51' Card's MD5
// Conditional results ------------------------------------
returnUrl?: string; //
}
export interface GrubpaySubmitParams {
forceSaveCard?: boolean;
}
Demo: Html + Js code
A minimal implementation SDK demo (secureId
required); for obtaining secureId
see Server demo code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script type="text/javascript" src="https://api.grubpay.io/v4/grubpay.js"></script>
</head>
<body>
<div id="grubpay-container"></div>
<button id="submit-button" disabled>Submit</button>
</body>
<script type="text/javascript" src="./checkout.js"></script>
</html>
let GrubpayElement = null;
let mounted = false;
async function initializeGrubpay() {
const secureId = await fetch('Your Server API to get secureId from Grubpay API');
mounted = false;
GrubpayElement = Grubpay(secureId);
mounted = await GrubpayElement.mount('#grubpay-container');
if (mounted) {
document.querySelector('#submit-button').disabled = false;
}
}
async function onSubmit() {
// For user experience, you can lock/set a loading to your submit button
document.querySelector('#submit-button').disabled = true;
let res = await GrubpayElement.submitPayment();
console.log(res);
if (res.success) {
// Call success logic
} else {
alert(res.error);
console.log(res.error);
}
// Don't forget to unlock the button
document.querySelector('#submit-button').disabled = false;
}
// Give your submit button the proper function
document.querySelector('#submit-button').addEventListener('click', (event) => {
onSubmit();
});
initializeGrubpay();