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:
| Method of payment | Supported regions | Supported currencies | Supported brands |
|---|---|---|---|
| Cards (credit and debit) |
|
|
|
| Electronic Check Processing (ECP) |
|
|
N/A |
| Apple Pay |
|
|
Card eligibility depends on the wallet, issuer, and merchant configuration. |
| Google Pay |
|
|
Card eligibility depends on the wallet, issuer, and merchant configuration.
|
Cards
Refer to the supported payment methods table for CheckoutComponents card support details.
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.
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:
| 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
- Create a checkout session on your server. Refer to Create a checkout session for instructions.
- 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.
- The browser initiates checkout with your server.
- Your server authenticates with the Checkout backend (certificate-based).
- On success, the backend returns an
accessToken. - Your server creates a checkout session or intent by sending a
POSTto/checkout/intent(Setup checkout intent) with theaccessToken(bearer),merchantId, order reference, andcartobject. - The backend returns a
checkoutSessionToken. - Your server sends the
checkoutSessionTokento the browser. - The page loads the
CheckoutComponentsbundle and initializes it with thecheckoutSessionToken. CheckoutComponentsrenders the fields (and any wallet buttons) in the browser.- The consumer clicks Pay or Submit.
CheckoutComponentssubmits the form data to the backend.- The backend returns
payResult. CheckoutComponentsrelays 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.
- The consumer starts checkout and your server creates the initial checkout intent.
- The page initializes
CheckoutComponentswith thecheckoutSessionToken. - A consumer action triggers a cart change (for example, items, shipping, or discount update).
- Your backend recalculates totals and calls
PATCH/checkout/intent/{checkoutIntentId}. (Update checkout intent) - The backend returns an updated
cartVersion. - Your backend returns the
cartVersionto the browser. - The frontend calls
fetchUpdatewith the newcartVersion. - After
fetchUpdatesucceeds, proceed withcompleteWalletfor a wallet flow orsubmitfor 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.
| 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. |
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
| 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 |
|---|---|
Card |
|
| ECP |
|
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:
| 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:
| 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.
| 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:
| 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.
| 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 |
initMethodOfPayment throws the following errors:
| 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 |
|
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:
| Error | Description |
|---|---|
TARGET_REQUIRED |
The |
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:
shippingAddressRequiredshippingTypeshippingMethodsallowedShippingCountriesrequiredShippingContactFieldsrequiredBillingContactFields
Use these callbacks to respond to shipping or authorization events:
onShippingAddressChangeonShippingMethodChangeonAuthorized
Checkout throws the following additional errors when these options or callbacks are invalid or missing:
| 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:
| 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.
| 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 |
|
ready |
The cart is updated and ready to open or complete the payment sheet. |
completing |
|
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:
- A button is clicked.
fetchUpdate(...).completeWallet(...).- 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 specifiedcartVersion. It returns a JavaScriptPromise, which tells your code when the operation succeeds or fails. ThePromiseresolves when the cart update completes and the wallet state changes fromupdatingtoready. If thecartVersionis stale or no checkout session token exists, thePromiserejects with IncorrectIdempotency, and Checkout emits the error through the event bus.completeWallet({ trackingId })opens the native payment sheet for a button that you registered withsharedIntent: true. It finds the button bytrackingIdand sets the wallet mutex state tocompleting. ThePromiserejects if no session token exists or no button matches thetrackingId.abortSession({ trackingId })aborts an active Apple Pay session for the specifiedtrackingId. 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,abortSessionhas no effect because the payment sheet has not opened. If your backend fails, do not callcompleteWallet.getActiveTrackingId()returns thetrackingIdfor the wallet interaction that holds the mutex. It returnsundefinedwhen 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:
| Parameter | Type | Description |
|---|---|---|
accountType |
string | Account type for electronic check payments. Required if payment type is electronicCheck. |
paymentType |
string |
|
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:
| 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_GENERATIONrequest (transactionTypeNONCE_GENERATION),submit()does not authorize a payment. Instead, the backend tokenizes the payment method andsubmit()resolves with anonceobject.
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 rawPayResponse.
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:
| 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, |
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. |
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 valuescommandanddebugalso exist internally.namespace— Where the event originated, for example,field,form,render,wallet, orunknown.message— A fixed string you match against directly.payload— Key-value pairs that vary by message; always includesinstanceId.
Commonly used events
The following table describes the 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:
WalletClickedWalletUpdateRequestedWalletCompletedCartUpdatedCartUpdateFailedSessionAbortRequestedShippingAddressChangedShippingMethodChanged
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.
- The consumer starts checkout and your server creates the initial checkout intent.
- The page initializes Checkout Components with the
checkoutSessionToken. - A consumer action triggers a cart change (for example, shipping or discount update).
- Your backend recalculates totals and calls
PATCH/checkout/intent/{checkoutIntentId}(Update checkout intent). - The backend returns an updated
cartVersion. - Your backend returns the
cartVersionto the browser. - The frontend calls
fetchUpdatewith the newcartVersion. - After
fetchUpdatesucceeds, proceed withcompleteWallet(wallet flow) orsubmit(form flow).
Implementation guidance
- Use the base
CheckoutComponentsflow when totals are static. - Use the Update Checkout Intent API flow when totals change post-initialization.
- Treat
cartVersionas required synchronization data between your backend and Checkout Components. - Run
fetchUpdateafter 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:
- Update the intent.
- Return the latest
cartVersion. - Call
fetchUpdate. - Continue payment.