Embedded Integration

How It Works

Endpoint

https://api.grubpay.io/v4/auth

Method:

POST

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 Purchaseopen in new window which may help you when going through the documentation.

1. Request to Endpoint with format below
Request
NameRequiredTypeSampleDescription
mchIdyString10000701assigned by GrubPay
mchOrderNoyString1234567890abcassigned by merchant
amountyInt1500in cents; must > 0
currencyyString(3)USDfixed to: USD
loginNameyString(12)jack123merchant's login name
subjectnString(64)
bodynString(250)
channelyStringCC_CARDfixed to: CC_CARD
capturenStringY or N (default Y)specify Y to capture the transaction for settlement if approved
signyString(32)C380BEC2BFD727A4B6845133519F3AD6Sign algorithm
Response
nameRequiredtypesampledescription
retCodeyStringSUCCESS or FAIL
retMsgyString
retData.secureIdyStringFor usage see secureId
2. You will need the secureId from response above to create a GrubPay instance through JS SDK. With secureId in hand, continue to SDK Integration

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 Tokenizeopen in new window which may help you when going through the documentation.

1. Request to Endpoint with format below
Request
namerequiredtypesampledescription
mchIdyString10000701assigned by GrubPay
loginNameyString(12)jack123merchant's login name
channelyStringCC_CARDfixed to: CC_CARD
signyString(32)C380BEC2BFD727A4B6845133519F3AD6Sign algorithm
Response
namerequiredtypesampledescription
retCodeyStringSUCCESS or FAIL
retMsgyString
retData.secureIdyStringFor usage see secureId
2. You will need the 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

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.

1. Request to Endpoint with format below
Request
NameRequiredTypeSampleDescription
mchIdyString10000701assigned by GrubPay
mchOrderNoyString1234567890abcassigned by merchant
amountyInt1500in cents; must > 0
currencyyString(3)USDfixed to: USD
loginNameyString(12)jack123merchant's login name
subjectnString(64)
bodynString(250)
channelyStringCC_ACHfixed to: CC_ACH
notifyUrlyString(200)http://sample.com/notifymerchant endpoint for receiving ACH transaction notifications , must be http or https
signyString(32)C380BEC2BFD727A4B6845133519F3AD6Sign algorithm
Response
nameRequiredtypesampledescription
retCodeyStringSUCCESS or FAIL
retMsgyString
retData.secureIdyStringFor usage see secureId
2. You will need the secureId from response above to create a GrubPay instance through JS SDK. With secureId in hand, continue to SDK Integration

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-jsopen in new window

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();

Demo: styles

Last Updated:
Contributors: grant, Edward, Grant, Edward Yuan, Grant Yao, H