Skip to main content

Modular Checkout

Overview

The justifi-modular-checkout wrapper component serves as a container for checkout-related sub components. It manages the tokenization of payment methods, billing information, insurance, and overall form submission to complete the checkout. It also supports saving a payment method to a payment method group for future use.

Supported sub components

Required for checkout completion (add at least one of the following sub components):

Optional:

Note: In addition to the supported sub components, you can add any custom HTML elements or markup inside the justifi-modular-checkout. This allows you to render headings, descriptions, disclaimers, or other layout elements as needed.

Providing Billing Information

Billing information is required when collecting a new card or bank account and can be provided in one of the following ways:

  • Using the <justifi-billing-form-full /> sub component for complete billing address collection
  • Using the <justifi-card-billing-form-simple /> sub component for ZIP code only (card payments)
  • Using the <justifi-bank-account-billing-form-simple /> sub component for account owner name only (ACH payments)
  • Passing a billingInformation object directly to the submitCheckout method

For checkouts that use a saved payment method or a wallet/BNPL sub component (for example, <justifi-saved-payment-methods />, <justifi-apple-pay />, <justifi-sezzle-payment-method />, or <justifi-plaid-payment-method />), billing information is optional and is not validated by the modular checkout flow. The billingInformation object can take one of two forms:

Full billing address:

{
name: string;
address_line1: string;
address_line2?: string;
address_city: string;
address_state: string;
address_postal_code: string;
}

Only postal code:

{
address_postal_code: string;
}

Card Form Billing Information

When using <justifi-card-form />, billing information must be provided through either:

  • <justifi-billing-form-full /> sub component for complete billing address
  • <justifi-card-billing-form-simple /> sub component for ZIP code only
  • Passing the full billingInformation object directly to the submitCheckout method
  • Passing the billingInformation object with only address_postal_code to the submitCheckout method

Bank Account Form Billing Information

When using <justifi-bank-account-form />, billing information must be provided through either:

  • <justifi-billing-form-full /> sub component for complete billing address
  • <justifi-bank-account-billing-form-simple /> sub component for account owner name only
  • Passing the full billingInformation object directly to the submitCheckout method

Pre-filling Billing Information

Use fillBillingForm() to programmatically pre-populate billing fields, e.g. from saved customer data. Values persist when the user switches between payment methods.

const modularCheckout = document.querySelector('justifi-modular-checkout');

modularCheckout.fillBillingForm({
name: 'John Doe',
address_line1: '123 Main St',
address_city: 'Anytown',
address_state: 'NY',
address_postal_code: '12345',
});

All fields are optional except address_postal_code:

interface BillingFormFields {
name?: string;
address_line1?: string;
address_line2?: string;
address_city?: string;
address_state?: string;
address_postal_code: string; // required
}

Completing Checkout

To finalize the checkout process, include a submit button that calls the submitCheckout method on the justifi-modular-checkout wrapper component. This method:

  • handles validation, form submission and payment method tokenization
  • optionally accepts a billingInformation object (useful when not using a billing component)
  • executes preCompleteHook (if provided) after validation and tokenization (including Plaid exchange) and before checkout completion
  • emits a submit-event when the submission is successful
  • returns a promise (Promise<void>) and you should listen for submit-event to handle completion

If you passed a payment_method_group_id to the create checkout API and you want to give the user the option to save a newly entered payment method to that payment method group for future use, set the savePaymentMethod prop to true on the justifi-modular-checkout. This will automatically save the payment method to that payment method group after successful checkout.

Props, Events & Methods

NameTypeRequiredDefaultDescription
auth-tokenstringYes
checkout-idstringYes
preCompleteHook(data: CheckoutState, resolve: (data: CheckoutState) => void, reject: () => void) => voidNo

Events

  • error-event: Emitted when validation fails or an error occurs. Detail shape: { message: string; errorCode: string; severity?: 'info' | 'warning' | 'error' }. When the error originates from justifi-apple-pay, errorCode will be prefixed with APPLE_PAY_ (e.g. APPLE_PAY_NOT_SUPPORTED, APPLE_PAY_PAYMENT_FAILED).
  • submit-event: Emitted when checkout completes successfully. Detail shape: { checkout: any; message: string }.
  • checkout-changed: Emitted when checkout state changes. Detail shape: { availablePaymentMethodTypes: PAYMENT_METHODS[]; selectedPaymentMethod: { id?: string; type: PAYMENT_METHODS }; savedPaymentMethods: SavedPaymentMethod[] }.

Public methods

  1. fillBillingForm(fields) – Pre-populate billing form fields from saved customer data.
  2. submitCheckout(billingInformation?) – Validate, tokenize, and complete the checkout. Optionally accepts a billingInformation object.
  3. validate() – Validate all sub-component form fields; returns a promise resolving to boolean.
  4. setSelectedPaymentMethod(method) – Programmatically set the active payment method.

Pre-Complete Hook

You can provide a preCompleteHook function to inspect the checkout state before submission proceeds. This is useful for implementing custom validation, user confirmation dialogs, or conditional payment restrictions based on business rules.

The hook runs after validation and payment method tokenization (including Plaid exchange when applicable), and before the checkout is completed, so the state includes the latest values such as paymentToken when available.

The hook receives three parameters:

  • state: A CheckoutState object containing the current checkout information
  • resolve: A function to call to proceed with submission
  • reject: A function to call to stop submission
const checkout = document.querySelector('justifi-modular-checkout');

/**
* CheckoutState example (shape):
* {
* selectedPaymentMethod: { id?: string, type: 'new_card' | 'apple_pay' | 'google_pay' | ... } | undefined,
* paymentAmount: 5000,
* totalAmount: 5000,
* paymentCurrency: 'USD',
* paymentDescription: 'Order #123',
* savedPaymentMethods: [],
* savePaymentMethod: false,
* bnplEnabled: false,
* applePayEnabled: true,
* insuranceEnabled: false,
* disableBankAccount: false,
* disableCreditCard: false,
* disablePaymentMethodGroup: false,
* paymentToken: 'pm_123' // tokenized payment method id; Apple Pay, Google Pay and Plaid set this too (paymentMethodId)
* }
*/
checkout.preCompleteHook = (state, resolve, reject) => {
// Example: Require confirmation for large payments
if (state.totalAmount > 100000) {
const confirmed = confirm(
`Confirm payment of $${(state.totalAmount / 100).toFixed(2)}?`,
);
if (confirmed) {
resolve(state);
} else {
reject();
}
} else {
resolve(state);
}
};

Important: Assign the hook as a JavaScript property on the element (e.g., checkout.preCompleteHook = fn). Do not pass it as an HTML attribute (e.g., pre-complete-hook="..."); functions must be set on properties, not attributes.

See the CheckoutState type definition in the Public Types section below for the complete structure of the state object.

Validation

When submitCheckout is called, the justifi-modular-checkout wrapper component automatically validates all included sub components. If any required fields are missing or invalid, submission is blocked and validation errors will appear inline.

You can also trigger validation manually by calling the validate method on the justifi-modular-checkout wrapper component. This returns a promise that resolves to a boolean indicating whether the form is valid.

Setting Payment Method Programmatically

You can programmatically set the selected payment method by calling the setSelectedPaymentMethod method on the justifi-modular-checkout wrapper component. This method accepts either a saved payment method object or an object with a type field.

Important: The wrapper does not automatically select a default payment method. If you want a default to be selected (for example, the first saved method or a new card), explicitly call setSelectedPaymentMethod after initialization (e.g., on the first checkout-changed event).

import { PAYMENT_METHODS } from '@justifi/webcomponents';

const modularCheckout = document.querySelector('justifi-modular-checkout');

// Set to new card payment method
modularCheckout.setSelectedPaymentMethod({ type: PAYMENT_METHODS.NEW_CARD });

// Set to new bank account payment method
modularCheckout.setSelectedPaymentMethod({
type: PAYMENT_METHODS.NEW_BANK_ACCOUNT,
});

// Set to a saved payment method by id
modularCheckout.setSelectedPaymentMethod({
id: 'pm_123',
type: PAYMENT_METHODS.SAVED_CARD,
});

Checkout Changed Event

The justifi-modular-checkout wrapper component emits a checkout-changed event whenever any internal checkout state changes. The event detail includes availablePaymentMethodTypes, selectedPaymentMethod, and savedPaymentMethods.

const modularCheckout = document.querySelector('justifi-modular-checkout');
modularCheckout.addEventListener('checkout-changed', (event) => {
const {
availablePaymentMethodTypes,
selectedPaymentMethod,
savedPaymentMethods,
} = event.detail;
console.log('Available:', availablePaymentMethodTypes);
console.log('Selected:', selectedPaymentMethod);
console.log('Saved methods:', savedPaymentMethods);
});

availablePaymentMethodTypes includes:

  • saved_card, saved_bank_account: Saved methods from payment_method_group (respects disabling flags).
  • new_card, new_bank_account: Entry forms for new payment methods (respects disabling flags).
  • sezzle: BNPL is available when enabled by the account and not disabled.
  • plaid: Plaid is available when bank account verification is enabled for the checkout.

Insurance Integration

The justifi-modular-checkout wrapper component automatically integrates the justifi-season-interruption-insurance component.

  • Automatic Refresh: When insurance values change the checkout data is automatically refreshed when insurance values are updated
  • Feature Flag: If the checkout's payment_settings.insurance_payments is false, the wrapper will skip validating the insurance component. The justifi-season-interruption-insurance component will also render nothing and log a console warning indicating that insurance is disabled for the checkout.
<justifi-modular-checkout auth-token="123xyz" checkout-id="cho_1234567890">
<justifi-checkout-summary />
<justifi-card-form />
<justifi-billing-form-full />
<justifi-season-interruption-insurance />
</justifi-modular-checkout>

Public Types

import { PAYMENT_METHODS } from '@justifi/webcomponents';
import type {
SelectedPaymentMethod,
SavedPaymentMethod,
CheckoutChangedEventDetail,
CheckoutState,
} from '@justifi/webcomponents';

Authorization

Authorization is performed by passing a web component token as auth-token.

  • Web Component Token: These tokens are generated by your backend services using the Web Component Tokens API. Each token can be scoped to perform a set number of actions and is active for 60 minutes. When creating a web component token for this specific component you'll need to use the roles: write:checkout:checkout_id and write:tokenize:account_id. Make sure the value for account_id matches the account associated with the checkout.

<!DOCTYPE html>
<html dir="ltr" lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<title>justifi-modular-checkout</title>

<script
type="module"
src="https://cdn.jsdelivr.net/npm/@justifi/webcomponents@>=6.12.3/dist/webcomponents/webcomponents.esm.js"
></script>

<script
nomodule
src="https://cdn.jsdelivr.net/npm/@justifi/webcomponents@>=6.12.3/dist/webcomponents/webcomponents.js"
></script>

<style>
    ::part(font-family) {
      font-family: georgia;   
    }
      
    ::part(color) {
      color: darkslategray;
    }

    ::part(background-color) {
      background-color: transparent;
    }
  </style>

</head>

<body>
<justifi-modular-checkout auth-token="authToken" checkout-id="cho_1234567890">
  <justifi-checkout-summary />
  <justifi-card-form />
  <justifi-billing-form-full />
</justifi-modular-checkout>
</body>

<script>
(function () {
  const modularCheckout = document.querySelector('justifi-modular-checkout');

  modularCheckout.addEventListener('error-event', (event) => {
    // this event is emitted when validation fails or an error occurs during processing
    console.error('error-event', event.detail);
  });

  modularCheckout.addEventListener('submit-event', (event) => {
    // this event is emitted when checkout is successfully completed
    console.log('submit-event', event.detail);
  });

  modularCheckout.addEventListener('checkout-changed', (event) => {
    // this event is emitted when the checkout state changes
    // includes availablePaymentMethodTypes, selectedPaymentMethod, and savedPaymentMethods
    const {
      availablePaymentMethodTypes,
      selectedPaymentMethod,
      savedPaymentMethods,
    } = event.detail;
    console.log('Available payment methods:', availablePaymentMethodTypes);
    console.log('Selected payment method:', selectedPaymentMethod);
    console.log('Saved payment methods:', savedPaymentMethods);
  });
})();
</script>

</html>