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>
2. Create an GrubpayElement instance using secureId and optional options

options is optional, you can leave it blank, or find example options here

var GrubpayElement = Grubpay(secureId, options);
3. 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();
4. Call the submitPayment() with async or .then() syntax
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;
}
Last Updated:
Contributors: grant, Edward, Edward Yuan, Grant