Skip to main content
Online Payments

Checkout components

Overview

CheckoutComponents enables you to customize the checkout experience while maintaining security and compliance requirements. By using CheckoutComponents, you design and control the look, feel, and placement of payment elements within your checkout flow, resulting in a seamless and branded consumer experience.

Integrate CheckoutComponents with the JavaScript library package. Each field is hosted in an iframe, ensuring that sensitive payment information never touches your server.

CheckoutComponents supports guest checkout only. Consumer payment information is not saved.

Key benefits

  • Enhanced customization

    • Tailor the checkout interface to match your brand identity, and optimize layout, colors, and user interactions.
    • Position payment fields anywhere within the checkout page to support your unique business requirements and user journeys.
  • Seamless integration

    • Enables rapid integration of payment methods without compromising design.
    • Supports responsive layouts and mobile-first experiences.
  • PCI compliance and security

    • The payment provider securely handles sensitive payment data, which reduces your Payment Card Industry Data Security Standard (PCI DSS) scope.
    • Ensures that cardholder data never touches your servers, which minimizes risk and compliance burden.
  • Developer-friendly

    • Comprehensive documentation and a software development kit (SDK) help you implement and customize your checkout experience.
    • Supports error handling, validation, and accessibility.

Supported payment methods

CheckoutComponents is available in the United States, Canada, and Australia. The following payment methods are supported:

Supported payment methods
Method of payment Supported regions Supported currencies Supported brands
Cards (credit and debit)
  • United States (US)
  • Canada
  • Australia
  • USD
  • CAD
  • AUD
  • Visa
  • Mastercard
  • Amex
  • Discover
  • Discover Diners
  • JCB
  • China UnionPay
  • ChaseNet Credit (US only)
  • eftpos (Australia only)
Electronic Check Processing (ECP)
  • United States (US)
  • Canada
  • USD
  • CAD
N/A
Apple Pay
  • United States (US)
  • Canada
  • Australia
  • USD
  • CAD
  • AUD

Card eligibility depends on the wallet, issuer, and merchant configuration.

Google Pay
  • United States (US)
  • Canada
  • Australia
  • USD
  • CAD
  • AUD
Card eligibility depends on the wallet, issuer, and merchant configuration.

Cards

Refer to the supported payment methods table for CheckoutComponents card support details.

Note

You are responsible for collecting any terms and conditions or consents from the consumer.

Electronic check processing

  • The Automated Clearing House (ACH) Network processes ECP, eCheck, and US ACH payments. The National Automated Clearing House Association (NACHA) regulates the network.
  • J.P. Morgan collects the bank account number, routing number, name, and address of the account.
Note

You are responsible for collecting the NACHA ACH mandate from the consumer to debit their account.

Apple Pay

Apple Pay is a secure digital wallet payment service developed by Apple that enables consumers to make card payments using Apple mobile devices, such as phones, tablets, and watches. To offer Apple Pay as a payment method to your consumers, complete the Apple Pay onboarding steps in both the certification and live environments.

Google Pay

Google Pay, a digital wallet payment service developed by Google, enables consumers to make payments online and with Android devices such as phones, tablets, and watches. To offer Google Pay to your consumers, complete the Google Pay onboarding steps.

Before you begin

To use CheckoutComponents, you must first load the JavaScript library and obtain a checkout session token by creating a checkout session.

Step 1: Load the library

The following script loads Checkout Components for one merchant ID.

<script type="module" src="https://checkout-<env>.merchant.jpmorgan.com/hosted-fields/checkout-components.mjs?merchantIds=YOUR_MERCHANT_ID"></script>"

If you use more than one merchant ID, pass all IDs in a single comma-separated merchantIds value. A shopper is excluded when any listed ID appears on the exclude list.

The following example loads CheckoutComponents with three merchant IDs:

<script type="module" src="https://checkout-cat.merchant.jpmorgan.com/hosted-fields/checkout-components.mjs?merchantIds=YOUR_MERCHANT_ID_1,YOUR_MERCHANT_ID_2,YOUR_MERCHANT_ID_3"></script>

Apply these rules when you add the merchantIds parameter to the CheckoutComponents script URL:

Rules for the merchantIds parameter
Rule Correct Incorrect
Param name is plural, case-sensitive merchantIds= merchantId=, MerchantIds=
Value is your J.P. Morgan merchant ID merchantIds=604995 —
Multiple IDs: comma-separated merchantIds=604995,605009 space/semicolon separated
Put it in the query string, not a fragment/header ...mjs?merchantIds=604995 ...mjs#merchantIds=604995

Step 2: Verify that it is loaded in the browser console

Run the following command in the browser console to verify that CheckoutComponents loaded and check its version.

CheckoutComponents.VERSION; // e.g. "v1.2.3-ABC123"

Step 3: Create a checkout session token

  1. Create a checkout session on your server. Refer to Create a checkout session for instructions.
  2. Pass the token to the browser.

How CheckoutComponents work

CheckoutComponents has two flows for how CheckoutComponents, your server, and the Checkout backend exchange information: one flow for a static cart, and one flow for a dynamic cart.

Static cart

The following flow shows how CheckoutComponents process a payment when the cart remains unchanged during checkout.

  1. The browser initiates checkout with your server.
  2. Your server authenticates with the Checkout backend (certificate-based).
  3. On success, the backend returns an accessToken.
  4. Your server creates a checkout session or intent by sending a POST to /checkout/intent (Setup checkout intent) with the accessToken (bearer), merchantId, order reference, and cart object.
  5. The backend returns a checkoutSessionToken.
  6. Your server sends the checkoutSessionToken to the browser.
  7. The page loads the CheckoutComponents bundle and initializes it with the checkoutSessionToken.
  8. CheckoutComponents renders the fields (and any wallet buttons) in the browser.
  9. The consumer clicks Pay or Submit.
  10. CheckoutComponents submits the form data to the backend.
  11. The backend returns payResult.
  12. CheckoutComponents relays a success or failure status to the browser.

Dynamic cart

The following flow shows how your backend updates the checkout intent and your frontend refreshes the cart when cart details change during checkout.

  1. The consumer starts checkout and your server creates the initial checkout intent.
  2. The page initializes CheckoutComponents with the checkoutSessionToken.
  3. A consumer action triggers a cart change (for example, items, shipping, or discount update).
  4. Your backend recalculates totals and calls PATCH /checkout/intent/{checkoutIntentId}. (Update checkout intent)
  5. The backend returns an updated cartVersion.
  6. Your backend returns the cartVersion to the browser.
  7. The frontend calls fetchUpdate with the new cartVersion.
  8. After fetchUpdate succeeds, proceed with completeWallet for a wallet flow or submit for a form flow.

Use CheckoutComponents methods

A CheckoutComponents instance exposes the following methods. Initialize your payment methods (initField, initMethodOfPayment, initQuickApplePay) first, then call mount() once.

Create an instance

The following is a reference for the public methods on a CheckoutComponents instance.

The following example creates a CheckoutComponents instance with a checkout session token and optional configuration settings.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token", // required
  // theme: { ... },               // optional style overrides
  // cookiePolicy: CookiePolicy.ACCEPT_ALL, // optional
});

The following table lists the parameters for creating a CheckoutComponents instance.

Parameters for creating a CheckoutComponents instance
Parameter Type Required Description
token string Yes The checkout session token used for Checkout API calls.
theme object No Theme overrides to customize field appearance.
cookiePolicy enum No UNSPECIFIED (0), ACCEPT_ALL (1), or ONLY_NECESSARY (2). Setting ONLY_NECESSARY disables non-essential analytics.
Tip

Error convention:

  • The SDK throws every error as new Error(CheckoutComponentsErrors.NAME, { cause: "readable message" }).
  • The stable identifier is on error.message.
  • The human-readable detail is on error.cause.
  • Match on error.message.
  • Do not parse error.cause.

Setup and lifecycle

subscribe(subscriber) registers a callback that receives SDK announcements (state changes, errors, wallet events) published over the internal publish-subscribe bus. It returns an unsubscribe handle.

Each announcement has the shape { level, namespace, message, payload? }. Filter on level, or on namespace and message, to react to the events relevant to your integration.

A common pattern is to toggle an error style on the field's target element as it becomes valid or invalid, and to enable the submit button when the whole form is valid:

The following example subscribes to validation events, updates field error styling, and enables form submission when the form is valid.

const unsubscribe = myCheckoutComponents.subscribe((msg) => {
  const { level, namespace, message, payload } = msg;

// Real-time inline field validation - toggle an error class on the target
  if (level === "error" && payload?.target) {
    document.getElementById(payload.target)?.classList.add("error");
  } else if (message === "FieldValidationSuccess") {
    document.getElementById(payload.target)?.classList.remove("error");
  }

  // Overall form validity flips (payload.message is "true" / "false")
  if (namespace === "form" && message === "FormValidity") {
    setSubmitEnabled(payload.message === "true");
  }
});

// later: unsubscribe();

Use the following example to log the full announcement object and inspect raw announcements during integration.

myCheckoutComponents.subscribe((announcement) => { 
  console.log(announcement); 
});

Commonly used validation events

Commonly used validation events
Namespace Message Description
field FieldValidationError A field became invalid — payload.message holds the error code(s), payload.field the field name. Refer to the Field validation error codes section for the full list.
field FieldValidationSuccess A field became valid.
form FormValidity Overall form validity changed — payload.message is "true" or "false"

What your subscriber receives

Your callback receives a structured announcement object. Do not parse a preformatted string.

The following example shows the structure of a FieldValidationError announcement.

{
  level: "error";
  namespace: "field";
  message: "FieldValidationError";
  payload: {
    message: string;    // comma-joined validation codes, e.g. "required,cardNumber"
    field: string;      // the field name, e.g. "cardNumber"
    target: string;     // the DOM element id you mounted the field into
    instanceId: string; // the CheckoutComponents instance id
  };
}

Read the properties directly — do not parse a string.

The following example reads validation details from the event payload and separates the error codes.

myCheckoutComponents.subscribe((msg) => {
  if (msg.message === "FieldValidationError") {
    const { field, message, target } = msg.payload;
    const codes = message.split(","); // ["required", "cardNumber"]
    // ...map codes → your localized copy, style `target`, etc.
  }
});

clearAllSubscribers() removes all registered subscribers at once. Use this when you tear down a page and no longer hold references to those subscribers.

mount() renders the registered fields and wallet buttons into their target elements. Call mount() once after initField, initMethodOfPayment, and initQuickApplePay to render the initialized fields in the Document Object Model (DOM).

The following example initializes a cardNumber field and mounts the registered components.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.mount();

unmount() tears down the rendered components, clears the button registry, and resets wallet and fetch state. Unmounts the initialized fields from the DOM.

The following example mounts the components, then removes them from the page with unmount().

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.mount();
// ... Later
myCheckoutComponents.unmount();

getInstanceId() returns the unique identifier for this CheckoutComponents instance. Use this when running multiple instances on one page. The instanceId is included in event payloads.

The following example retrieves the instanceId.

const instanceId = myCheckoutComponents.getInstanceId();

Fields

initField(field, options) initializes a single secure input field rendered in a J.P. Morgan-hosted iframe. Use this method for form-based payment methods, such as credit/debit card and ECP (bank account). Call initField before mount().

Payment type field values
Payment type Field values

Card

  • cardNumber
  • expirationDate
  • expirationMonth
  • expirationYear
  • securityCode
  • nameOnCard
ECP
  • nameOnAccount
  • routingNumber
  • confirmRoutingNumber
  • accountNumber
  • confirmAccountNumber

All fields are optional and initialize independently. Each field is appended to its target element.

Expiration date: Use either the single expirationDate field (MM / YY) or the separate expirationMonth and expirationYear dropdowns, not both.

Options

The following table describes the field options:

Field options for initField
Option Applies to Type Description
target All fields string (required) Cascading Style Sheets (CSS) id of the container element the field mounts into.
placeholder All fields string Placeholder shown when the input is empty.
maskInput All fields boolean When true, the value is hidden behind dots for maskable fields.
networks cardNumber only string[] Card networks to accept: visa, mastercard, amex, discover, jcb, diners, unionpay, and maestro. If omitted, all networks pass frontend validation.
hideCardIcon cardNumber only boolean Hides the detected-network icon inside the card input.

The following example initializes a cardNumber field.

myCheckoutComponents.initField("cardNumber", {
  target: "card-number",              // id of the container element
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"], 
  hideCardIcon: false,                
  maskInput: true,
});

The function validates that the passed options are valid and that the field has not yet been initialized. The function throws the following errors:

initField function error details
Error Description
INVALID_FIELD_TYPE The field name is not a known card or ECP field.
TARGET_REQUIRED The target is missing or the element is not in the DOM.
NETWORKS_OPTION_NOT_SUPPORTED networks is passed to a field other than cardNumber.
HIDE_CARD_ICON_OPTION_NOT_SUPPORTED hideCardIcon is passed to a field other than cardNumber.
FIELD_ALREADY_INITIALIZED The same field is initialized twice.

The error identifier is on error.message — it holds one of the error names in the preceding table. A human-readable explanation is on error.cause.

Wrap the initializations in a try/catch block to handle these errors:

The following example catches and handles errors from initField().

import { CheckoutComponentsErrors } from "...";

try {
  myCheckoutComponents.initField("cardNumber", {
    target: "card-number-field",
    hideCardIcon: true, // valid only on cardNumber
  });
} catch (error) {
  switch (error.message) {
    case "INVALID_FIELD_TYPE":
      // unknown field name
      break;
      ...
      ...
    default:
      console.error(error.message, error.cause);
  }
}

toggleShowField(field, show) toggles the visibility of a field between visible and hidden. This method is only applicable to fields that support masking, such as cardNumber or securityCode.

The following example shows and hides the cardNumber field.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.mount();
// Show the card number
myCheckoutComponents.toggleShowField("cardNumber", true);
// Hide the card number
myCheckoutComponents.toggleShowField("cardNumber", false); 

getFormErrors() returns a record of field names and their error messages.

The following example retrieves the current form errors.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.mount();
const errors = myCheckoutComponents.getFormErrors();

isValid(paymentType?) returns a boolean that indicates whether all fields are valid. Optionally pass in a payment type (card or electronicCheck) to validate only the fields for that payment type.

isValid paymentType behavior
Payment type Behavior
card Validates only the card fields.
electronicCheck Validates only the ECP fields.
googlePay or applePay No field-level effect. It returns overall form validity because wallets are not hosted fields.
(omitted) Returns overall validity of all registered fields.

The following example checks whether all fields or only card fields are valid.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number-field",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.initField("securityCode", {
  target: "security-code-field",
});
myCheckoutComponents.initField("accountNumber", {
  target: "account-number-field",
});
myCheckoutComponents.mount();
// Check if all fields are valid
const isValid = myCheckoutComponents.isValid();
// Only check credit card fields
const isCardFieldsValid = myCheckoutComponents.isValid("card");

Wallets

Wallet buttons, such as Google Pay and Apple Pay, are initialized with initMethodOfPayment or initQuickApplePay instead of initField.

Use initMethodOfPayment(type, options) to initialize a Google Pay or Apple Pay button.

The following example initializes a Google Pay button for the shared-intent flow.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});

myCheckoutComponents.initMethodOfPayment("googlePay", {
  target: "google-pay-button",
  sharedIntent: true,         // opt into the shared-intent flow
  context: { trackingId: "item-123-google" },
});

Common options

The following table describes common wallet button options:

Common wallet button options
Option Type Description
target string (required) CSS id of the button container.
language string 2-5 characters, such as en or en-US. Default is en-US.
context { trackingId: string } and Record<string,string> Required when sharedIntent: true.
sharedIntent boolean Google Pay only. Opt in to the shared-intent flow. Passing it for Apple Pay throws SHARED_INTENT_NOT_SUPPORTED; use initQuickApplePay instead.

The following table lists the supported button options and values for Google Pay and Apple Pay.

Wallet button options
Option Google Pay Apple Pay
buttonColor default, black, white black, white, white-outline
buttonType book, buy, checkout, donate, order, pay, plain, subscribe, long, short add-money, book, buy, check-out, continue, contribute, donate, order, pay, plain, reload, rent, set-up, subscribe, support, top-up, tip
buttonSizeMode fill, static Not supported
Note

Apple Pay ignores buttonSizeMode and emits a MethodOfPaymentOptionNotSupported warning event with level warn. The SDK does not throw an error.

initMethodOfPayment throws the following errors:

initMethodOfPayment function error details
Error Description
TARGET_REQUIRED target is required but missing (or the element is not in the DOM) for Google Pay initialization.
LANGUAGE_OPTION_INVALID The language option is invalid. The value must be 2-5 characters long.
INVALID_MOP_TYPE Invalid method of payment type provided for initialization.
MOP_ALREADY_INITIALIZED Method of payment has already been initialized.
BUTTON_COLOR_OPTION_INVALID Unsupported value provided for buttonColor option.
BUTTON_TYPE_OPTION_INVALID Unsupported value provided for buttonType option.
BUTTON_SIZE_MODE_OPTION_INVALID Unsupported value provided for buttonSizeMode option.
SHARED_INTENT_NOT_SUPPORTED Apple Pay does not support sharedIntent through initMethodOfPayment. Use initQuickApplePay() instead.
TRACKING_ID_REQUIRED

sharedIntent: true is missing context.trackingId.

initQuickApplePay(options) initializes the Apple Pay button in the shared-intent flow.

The following example initializes an Apple Pay button with product, total, and tracking details.

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});

myCheckoutComponents.initQuickApplePay({
  target: "apple-pay-button",
  product: { sku: "item-123", price: "29.99" },       // price: string, exactly 2 decimals
  optimisticTotal: { amount: "34.47" },               // amount: string, exactly 2 decimals
  buttonType: "buy",
  context: { trackingId: "item-123-apple" },
});

The function validates the passed options at runtime. TypeScript catches most misconfigurations at compile time. It throws the following errors:

initQuickApplePay function error details
Error Description
TARGET_REQUIRED

The target option is required but missing or does not identify an element in the DOM.

PRODUCT_SKU_REQUIRED product.sku is required.
INVALID_PRODUCT_PRICE product.price is missing or is not a string with exactly two decimal places.
INVALID_OPTIMISTIC_TOTAL_AMOUNT optimisticTotal.amount is missing or is not a string with exactly two decimal places.
OPTIMISTIC_TOTAL_BREAKDOWN_MISMATCH optimisticTotal.breakdown sum does not equal optimisticTotal.amount.
TRACKING_ID_REQUIRED context.trackingId is required.
LANGUAGE_OPTION_INVALID

The language option is invalid. The value must contain between two and five characters.

BUTTON_COLOR_OPTION_INVALID Unsupported buttonColor value.
BUTTON_TYPE_OPTION_INVALID Unsupported buttonType value.

For advanced express checkout configure Apple Pay with initQuickApplePay() or Google Pay with initMethodOfPayment("googlePay", options). Use these options to collect shipping and contact details in the wallet sheet:

  • shippingAddressRequired
  • shippingType
  • shippingMethods
  • allowedShippingCountries
  • requiredShippingContactFields
  • requiredBillingContactFields

Use these callbacks to respond to shipping or authorization events:

  • onShippingAddressChange
  • onShippingMethodChange
  • onAuthorized

Checkout throws the following additional errors when these options or callbacks are invalid or missing:

Express checkout errors
Error Description
INVALID_SHIPPING_TYPE shippingType is not one of shipping, delivery, storePickup, or servicePickup.
EXPRESS_CHECKOUT_INVALID_SHIPPING_TYPE shippingType is incompatible with shippingAddressRequired.
EXPRESS_CHECKOUT_MISSING_REQUIRED_FIELD shippingAddressRequired: true is used without onShippingAddressChange.
INVALID_CONTACT_FIELD A contact-field entry is not email, name, phone, or postalAddress.
INVALID_COUNTRY_CODE allowedShippingCountries contains a non-ISO 3166-1 alpha-2 code, such as US or CA.

getButtonRegistry() returns a read-only view of the registered wallet buttons. Use this function to inspect what was registered or to look up a button by its trackingId, target, or type.

The returned object exposes:

getButtonRegistry function returned object methods
Method/Property Description
getAll() All registered wallet entries (array).
getByType(type) All entries for a payment type ("googlePay" / "applePay").
getByTarget(target) The entry mounted at a DOM target, or undefined.
getByTrackingId(trackingId) The entry with that context.trackingId, or undefined.
size Number of registered buttons.

The following example retrieves the wallet button registry and logs each button’s details.

const registry = myCheckoutComponents.getButtonRegistry();

// How many wallet buttons are registered, and of which types
console.log(registry.size); // e.g. 2
registry.getAll().forEach((mop) => {
  console.log(mop.type, mop.target, mop.context?.trackingId);
});

getWalletState() returns the current wallet mutex state. Mutex state is a single, global state shared across all wallet buttons. The SDK serializes wallet interactions so only one sheet is in flight at a time. While one button's flow is active, every other Google Pay and Apple Pay button is unavailable.

Refer to the following state table to guard logic, such as ignoring a second click while a payment sheet is already opening.

getWalletState function states
State Description
idle No wallet interaction is in progress.
clicked A wallet button was clicked. The payment sheet is opening or awaiting a cart update.
updating

fetchUpdate is running and fetching the canonical cart.

ready

The cart is updated and ready to open or complete the payment sheet.

completing

completeWallet is in progress and opening the native payment sheet.

done

The wallet flow finished for this interaction.

The following example checks the wallet state and exits if another wallet interaction is in progress.

if (myCheckoutComponents.getWalletState() !== "idle") {
  return; // a wallet flow is already in progress — ignore this click
}

Shared-intent wallet flow

Use these methods when you register wallet buttons with sharedIntent: true (Google Pay) or through initQuickApplePay (Apple Pay). With this pattern, you update the cart with your backend after the consumer clicks the wallet button, then programmatically open the native sheet.

The typical sequence is:

  1. A button is clicked.
  2. fetchUpdate(...).
  3. completeWallet(...).
  4. Call abortSession(...) if your backend fails while an Apple Pay sheet is already open.

Method details

  • fetchUpdate({ cartVersion, trackingId? }) retrieves the canonical cart from the Checkout backend for the specified cartVersion. It returns a JavaScript Promise, which tells your code when the operation succeeds or fails. The Promise resolves when the cart update completes and the wallet state changes from updating to ready. If the cartVersion is stale or no checkout session token exists, the Promise rejects with IncorrectIdempotency, and Checkout emits the error through the event bus.
  • completeWallet({ trackingId }) opens the native payment sheet for a button that you registered with sharedIntent: true. It finds the button by trackingId and sets the wallet mutex state to completing. The Promise rejects if no session token exists or no button matches the trackingId.
  • abortSession({ trackingId }) aborts an active Apple Pay session for the specified trackingId. Use it if your backend fails after the Apple Pay sheet opens. The method closes the sheet and resets the wallet mutex to idle. For Google Pay, abortSession has no effect because the payment sheet has not opened. If your backend fails, do not call completeWallet.
  • getActiveTrackingId() returns the trackingId for the wallet interaction that holds the mutex. It returns undefined when the wallet is idle.

Submit

submit(options?) submits the form. Pass optional parameters to specify the payment type, ECP account type, accepted mandates, and whether to save the payment method.

The method returns a Promise that resolves after successful form submission.

This interface defines the optional parameters for submit().

interface SubmitOptions {
  accountType?: "CHECKING" | "SAVINGS" | "COMMERCIAL_CHECKING";
  paymentType?: PaymentType;
  mandates?: string[];
  savePaymentMethod?: boolean;
  setAsDefault?: boolean;
  billingAddress?: BillingAddress;
}

The following table lists the submit function parameters:

Submit function parameters
Parameter Type Description
accountType string Account type for electronic check payments. Required if payment type is electronicCheck.
paymentType string

card or electronicCheck. If omitted, CheckoutComponents infers the value from the initialized fields.

mandates string[] List of accepted mandates for the transaction. Required if payment type is electronicCheck. Identifies an agreement between the consumer, debtor bank, and you giving you permission to debit funds from the consumer's account.
savePaymentMethod boolean Save the payment method for future use. Defaults to false.
setAsDefault boolean Set the payment method as the default. Only applies if savePaymentMethod is true. Defaults to false.
billingAddress BillingAddress Billing address to associate with the payment.

The following interface defines the billing address fields:

interface BillingAddress {
  recipientFullName?: string;
  line1: string;
  line2?: string;
  city: string;
  state?: string;
  country: string;
  postalCode: string;
}

The following is a card example:

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
  placeholder: "1111 2222 3333 4444",
  networks: ["visa", "mastercard", "amex", "discover"],
  hideCardIcon: false,
});
myCheckoutComponents.mount();
// Later...
myCheckoutComponents
  .submit()
  .then(() => {
    console.log("Form submitted successfully");
  })
  .catch((error) => {
    console.error(error);
  });

The following is an ECP example:

const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});
myCheckoutComponents.initField("accountNumber", {
  target: "account-number",
});
myCheckoutComponents.mount();
// Later...
myCheckoutComponents.submit({
  paymentType: "electronicCheck",
  accountType: "CHECKING",
  mandates: ["mandate1", "mandate2"],
});

The submit function validates that the form is valid before submitting. When the paymentType is electronicCheck, supply an accountType and a non-empty mandates list.

The function rejects with the following errors:

submit function errors
Error Description
FORM_NOT_VALID Form is not valid.
INVALID_ACCOUNT_TYPE Invalid accountType provided for electronic check.
ACCOUNT_TYPE_REQUIRED accountType is required for electronic check.
MANDATE_REQUIRED mandates are required for electronic check.

The following example handles an error from submit().

myCheckoutComponents
  .submit({
    paymentType: "electronicCheck",
    accountType: "MISSING_VALUE",
  })
  .catch((error) => {
    console.error(error);
  });

Submit response

submit() returns a Promise that resolves after successful payment submission. The function rejects if submission fails or the underlying payment request fails. Refer to the preceding table for other errors.

On success, the resolved value depends on the backend response:

  • Nonce flow — When the session or intent is created as a NONCE_GENERATION request (transactionType NONCE_GENERATION), submit() does not authorize a payment. Instead, the backend tokenizes the payment method and submit() resolves with a nonce object.

This example shows the response structure for a nonce-generation request.

{ 
  nonceToken: string; 
  nonceExpiration: Date; 
  status: PayResponse_Status.SUCCESS; 
}
  • Standard flow — Otherwise, submit() resolves with the raw PayResponse.

Field validation error codes

The SDK emits this event as FieldValidationError. Refer to subscribe for details. The payload.message property contains a comma-separated list of raw validation codes, such as required or cardNumber, rather than a human-readable message. These codes are stable identifiers; map them to your localized text. A field emits multiple codes at once and emits updated codes after each change, including temporary errors while the consumer enters a value.

The following codes are field-specific:

Field validation error codes
Code Emitted by Description
required Every field The field is empty. This error is emitted on mount so that a freshly rendered form starts in an invalid state.
minLength Name, Security code The value is shorter than the field minimum. For example, name must be at least 4 characters. Card verification value (CVV) length depends on the card network — three for most networks and four for Amex.
maxLength Security code Longer than the network's required CVV length.
invalid Any text field with a custom validator Generic fallback when a validator fails without a more specific code.
cardNumber Card number Fails card-number Luhn or eligibility validation.
cardNetwork-<network> Card number

The detected network is not in your supported networks — for example, cardNetwork-visa, cardNetwork-amex, or cardNetwork-unknown when the network is not identified.

fetchingCardType Card number Transient — a card-type lookup is in flight. This error clears once the lookup resolves. Treat this as a pending state, not a hard error.
hasSpace Cardholder name The name has no space. Enter a first and last name.
isValidExpirationDate Expiration date The date is malformed or otherwise invalid.
isFutureExpiration Expiration date The date is valid but the card is expired.
isValidMonthExpiration Expiration month (dropdown) The selected month is invalid.
isValidYearExpiration Expiration year (dropdown) The selected year is invalid.
invalidExpirationMonthYear Expiration month/year (dropdowns) The selected month and year combination is in the past.
invalidRoutingNumber Routing number (ECP) Fails routing-number validation.
matchesField Confirm account/routing number (ECP) The confirm field does not match its source field.
Tip

Because required fires on mount and every field re-emits on change, clear the error styling on FieldValidationSuccess and apply it on FieldValidationError instead of treating the first required event as a user-facing error.

Event bus reference

Subscribe to events using the subscribe method.

Every announcement has the following structure.

interface Announcement {
  level: "error" | "warn" | "info"; // "command" / "debug" also exist internally
  namespace: string;
  message: string;
  payload?: Record<string, string>;
}
  • level — error (breaks or prevents payment; you must handle this), warn (affects UX but does not block payment), info (lifecycle or validation signals with no UX impact). The values command and debug also exist internally.
  • namespace — Where the event originated, for example, field, form, render, wallet, or unknown.
  • message — A fixed string you match against directly.
  • payload — Key-value pairs that vary by message; always includes instanceId.

Commonly used events

The following table describes the commonly used events:

Table 1: Commonly used events
Level Namespace Message Description
error field FieldValidationError A field became invalid. payload.message contains the validation code, payload.field identifies the field, and payload.target contains the DOM ID.
info field FieldValidationSuccess A field became valid.
info form FormValidity Overall form validity changed. payload.message is "true" or "false".
info form FormReady All fields have rendered successfully.
warn render MethodOfPaymentOptionNotSupported A wallet option was ignored — for example, buttonSizeMode on Apple Pay.
error form UnknownError An uncaught error propagated to the root. The form stopped rendering.
error unknown Unknown An unclassified error reached the root — this indicates a larger page problem.

Payload shapes

The following examples show the various payload structure.

// FieldValidationError
{
  field: string;
  message: string;
  target: string;
  instanceId: string;
}

// FieldValidationSuccess
{
  field: string;
  target: string;
  instanceId: string;
}

// FormValidity
{
  message: string;
  instanceId: string;
} // message is "true" / "false"

Shared-intent wallet events

In the shared-intent flow, the SDK publishes events in the wallet namespace, including:

  • WalletClicked
  • WalletUpdateRequested
  • WalletCompleted
  • CartUpdated
  • CartUpdateFailed
  • SessionAbortRequested
  • ShippingAddressChanged
  • ShippingMethodChanged

Subscribe to these events to coordinate calls to fetchUpdate and completeWallet.

Dynamic cart updates

If the final payable amount changes after checkout starts, update the checkout intent.

Use this pattern for scenarios such as:

  • Item changes in the cart
  • Tax recalculation after address selection
  • Shipping method changes that alter totals
  • Promotions or discounts applied during checkout
  • Inventory or pricing adjustments before payment confirmation

In these cases, your backend updates the existing checkout intent using:

PATCH /checkout/intent/{checkoutIntentId} (Update checkout intent)

After updating the intent, return the new cartVersion to the browser and call fetchUpdate before completing wallet payment or final submit. This ensures the consumer views the latest amount and prevents stale-cart payment failures.

Merchant integration flow for dynamic amounts

Follow these steps to update the checkout intent and refresh the cart when cart details change during checkout.

  1. The consumer starts checkout and your server creates the initial checkout intent.
  2. The page initializes Checkout Components with the checkoutSessionToken.
  3. A consumer action triggers a cart change (for example, shipping or discount update).
  4. Your backend recalculates totals and calls PATCH /checkout/intent/{checkoutIntentId} (Update checkout intent).
  5. The backend returns an updated cartVersion.
  6. Your backend returns the cartVersion to the browser.
  7. The frontend calls fetchUpdate with the new cartVersion.
  8. After fetchUpdate succeeds, proceed with completeWallet (wallet flow) or submit (form flow).

Implementation guidance

  • Use the base CheckoutComponents flow when totals are static.
  • Use the Update Checkout Intent API flow when totals change post-initialization.
  • Treat cartVersion as required synchronization data between your backend and Checkout Components.
  • Run fetchUpdate after a successful intent update and before payment completion.

The following example demonstrates a dynamic cart update with a shared-intent wallet flow, including initialization, event subscription, cart recalculation, and payment submission:

// 1. Initialize Checkout Components
const myCheckoutComponents = new CheckoutComponents({
  token: "checkout-session-token",
});

myCheckoutComponents.initField("cardNumber", {
  target: "card-number",
});

myCheckoutComponents.initMethodOfPayment("googlePay", {
  target: "google-pay-button",
  sharedIntent: true,
  context: { trackingId: "cart-item-123" },
});

myCheckoutComponents.mount();

// 2. Subscribe to wallet events
myCheckoutComponents.subscribe((msg) => {
  if (msg.message === "WalletClicked" && msg.payload.trackingId === "cart-item-123") {
    handleWalletClicked(msg.payload.trackingId);
  }
});

// 3. Handle shopper action that triggers a cart change (e.g., shipping selection)
document.getElementById("shipping-method").addEventListener("change", async (event) => {
  const shippingMethod = event.target.value;
  
  // Call your backend to recalculate totals
  const response = await fetch("/api/update-cart", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ shippingMethod }),
  });
  
  const { cartVersion } = await response.json();
  
  // Update the Checkout Components with the new cartVersion
  await myCheckoutComponents.fetchUpdate({ cartVersion });
  
  console.log("Cart updated with new cartVersion:", cartVersion);
});

// 4. Handle wallet completion
async function handleWalletClicked(trackingId) {
  try {
    // Fetch the latest cart from your backend
    const response = await fetch("/api/get-cart");
    const { cartVersion } = await response.json();
    
    // Update Checkout Components with the latest cartVersion
    await myCheckoutComponents.fetchUpdate({ cartVersion });
    
    // Now complete the wallet flow
    await myCheckoutComponents.completeWallet({ trackingId });
  } catch (error) {
    console.error("Wallet flow failed:", error.message);
  }
}

// 5. Submit payment
myCheckoutComponents
  .submit()
  .then(() => {
    console.log("Payment submitted successfully");
  })
  .catch((error) => {
    console.error("Payment failed:", error.message, error.cause);
  });

Failure handling

If the page attempts payment with an outdated cartVersion, Checkout rejects the request due to a cart-version mismatch. To avoid this:

  1. Update the intent.
  2. Return the latest cartVersion.
  3. Call fetchUpdate.
  4. Continue payment.

Apple Pay

Google Pay

Create a checkout session

Setup checkout intent

Update checkout intent

Is this page helpful?