---
id: tokenize-payment-method
title: Tokenize Payment Method
description: Secure standalone card/bank tokenization form with built-in validation and iframe isolation.
sidebar_position: 1
---

import {
  CodeBlock,
  PropsMarkdownTable,
  PartsMarkdownTable,
  getWebcomponentsVersion,
  getComponentParts,
  getPropsFromDocs,
  ComponentBox,
  setUpMocks,
} from '../helpers';
import docsJson from '../docs.json';
import componentTreeJson from '../component-tree.json';
import '@justifi/webcomponents/dist/module/justifi-tokenize-payment-method';

{setUpMocks()}

## Overview

Component to render an entire form including a switch to use a credit card or bank
account, a submit button and all fields required for proper use. This component can be used standalone or as part of the modular checkout system.

## Props, Events & Methods

<PropsMarkdownTable
  props={getPropsFromDocs('justifi-tokenize-payment-method', docsJson)}
/>

### Events

- `submit-event`: fires when tokenization completes; `event.detail.response` is the full payload (see [Response payload](#response-payload)).
- `error-event`: fires if tokenization fails; payload includes `code`, `message`, and validation hints.

### Public methods

1. `tokenizePaymentMethod()` – programmatically trigger submission (returns a promise that resolves to the same payload emitted by `submit-event`).
2. `fillBillingForm(partialBillingDetails)` – prefill customers' billing information from saved data.
3. `validate()` – validate the form fields and return validation result.

### Response payload

Both `submit-event` (`event.detail.response`) and the `tokenizePaymentMethod()` promise resolve to the **full payment method object**, not just the token:

```typescript
interface PaymentMethodPayload {
  token?: string; // convenience: the payment method token (e.g. "pm_...")
  data?: {
    data?: {
      signature: string;
      customer_id: string;
      account_id: string;
      invalid_reason: string;
      // present for card payment methods:
      card?: {
        id: string;
        name: string;
        acct_last_four: number;
        brand: string;
        token: string;
        month: string;
        year: string;
        metadata: any;
        address_line1_check: string;
        address_postal_code_check: string;
        // BIN lookup details (when available):
        bin_details?: {
          type: 'Credit' | 'Debit' | 'Prepaid' | 'Unknown';
          card_brand: string;
          card_class: string;
          country: string;
          issuer: string;
          funding_source:
            | 'Charge'
            | 'Credit'
            | 'Debit'
            | 'Deferred Debit (Visa Only)'
            | 'Network Only'
            | 'Prepaid';
        };
      };
      // present for bank account payment methods:
      bank_account?: {
        id: string;
        account_owner_name: string;
        account_type: 'checking' | 'savings';
        bank_name: string;
        acct_last_four: number;
        token: string;
        metadata: any;
      };
    };
  };
  error?: { code: string; message: string; decline_code: string };
  validationError?: boolean;
}
```

> **Note:** `bin_details` is only populated for card payment methods and may be absent depending on the BIN lookup result. Always null-check before reading it (e.g. `response.data?.data?.card?.bin_details`). Bank accounts do not include `bin_details`.

# Authorization

---

<p>
  Authorization is performed by passing a web component token as{' '}
  <code>auth-token</code>.
</p>
<ul>
  <li>
    <strong>Web Component Token</strong>: These tokens are generated by your
    backend services using the [Web Component Tokens
    API](https://docs.justifi.tech/api-spec#tag/Web-Component-Tokens/operation/CreateWebComponentToken).
    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 role: `write:tokenize:account_id`. Make
    sure the value for `account_id` matches the prop you also pass separately.
  </li>
</ul>

# Security

---

The api endpoint associated with this component has the following security measures in place:

1. **Rate Limiting**: POST requests to are limited to 2 requests per 10 seconds.
2. **Token-based Request Limiting**: POST requests using web component token authentication are limited to 10 attempts per token.

These measures are in place to prevent abuse and ensure the security of the payment processing system.

**Note:** While `client-id` is still supported, we now recommend using web component tokens (`auth-token`) for enhanced security and flexibility.

# Usage Patterns

---

## Standalone Usage

When used standalone, the component provides its own submit button and handles all tokenization internally:

```html
<justifi-tokenize-payment-method
  account-id="acc_123"
  auth-token="authToken"
  payment-method-group-id="pmg_123"
/>
```

## External Control

Hide the built-in submit button and control tokenization externally:

```html
<justifi-tokenize-payment-method
  account-id="acc_123"
  auth-token="authToken"
  hide-submit-button="true"
/>

<button id="external-submit">Submit Payment</button>

<script>
  const tokenizer = document.querySelector('justifi-tokenize-payment-method');
  const submitBtn = document.getElementById('external-submit');

  submitBtn.addEventListener('click', async () => {
    await tokenizer.tokenizePaymentMethod();
  });
</script>
```

## 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.

```javascript
const tokenizer = document.querySelector('justifi-tokenize-payment-method');

tokenizer.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`:

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

# Integration with Modular Checkout

---

When used within the `justifi-modular-checkout` wrapper component, this component automatically adapts its behavior:

- **Auto-detection**: The component automatically detects if it's slotted within a modular checkout
- **Submit button hiding**: The submit button is automatically hidden when inside modular checkout
- **Shared authentication**: Uses authentication tokens from the parent modular checkout component
- **Coordinated validation**: Validation is coordinated by the modular checkout wrapper

```html
<justifi-modular-checkout
  auth-token="authToken"
  account-id="acc_123"
  checkout-id="cho_123"
>
  <justifi-tokenize-payment-method />
  <!-- Other modular checkout components -->
</justifi-modular-checkout>
```

# Example Usage

---

<ComponentBox>
  <justifi-tokenize-payment-method
    account-id="123"
    auth-token="123abc"
    payment-method-group-id="123"
    save-payment-method-label="Save this card for next time"
  />
</ComponentBox>

---

<CodeBlock>{`<!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-tokenize-payment-method</title>

  <script type="module" src="https://cdn.jsdelivr.net/npm/@justifi/webcomponents@${getWebcomponentsVersion()}/dist/webcomponents/webcomponents.esm.js"></script>

<script
  nomodule
  src="https://cdn.jsdelivr.net/npm/@justifi/webcomponents@${getWebcomponentsVersion()}/dist/webcomponents/webcomponents.js"
></script>

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

    ::part(color) {
      color: darkslategray;
    }

    ::part(background-color) {
      background-color: transparent;
    }

    ::part(button) {
      padding: 0.375rem 0.75rem;
      font-size: 16px;
      box-shadow: none;
      border-radius: 0px;
      line-height: 1.5;
      text-transform: none;
    }

    ::part(button-disabled) {
      opacity: 0.5;
    }

    ::part(input) {
      border-color: #555;
      border-width: 1px;
      border-bottom-width: 1px;
      border-left-width: 1px;
      border-right-width: 1px;
      border-top-width: 1px;
      border-radius: 0;
      border-style: solid;
      box-shadow: none;
      font-size: 1rem;
      font-weight: normal;
      line-height: 1.5;
      padding: 0.375rem 0.75rem;
    }

    ::part(input-focused) {
      border-color: #333;
      box-shadow: 0 0 0 0.25rem rgba(0, 0, 0, 0.25);
    }

    ::part(input-invalid) {
      border-color: #8a2a35;
      box-shadow: 0 0 0 0.25rem rgba(244, 67, 54, 0.25);
    }

    ::part(input-invalid-and-focused) {
      box-shadow: 0 0 0 0.25rem rgba(244, 67, 54, 0.25);
      border-color: #8a2a35;
    }

    ::part(input-radio) {
      background-color: #fff;
      border-color: #333;
    }

    ::part(input-checkbox) {
      border-color: #333;
    }
    
    ::part(input-checkbox-checked) {
      background-color: #000;
      border-color: #333;
    }

    ::part(input-checkbox-checked-focused) {
      background-color: #000;
      box-shadow: 0 0 0 0.25rem rgba(0, 0, 0, 0.25);
    }

    ::part(input-checkbox-focused) {
      background-color: #fff;
      box-shadow: 0 0 0 0.25rem rgba(0, 0, 0, 0.25);
    }

    ::part(button-primary) {
      color: #333;
      background-color: transparent;
      border-color: #333;
    }

    ::part(button-primary):hover {
      background-color: rgba(0, 0, 0, .05);
      border-color: #333;
      color: #333;
    }

    ::part(radio-list-item) {
      border-bottom: 1px solid #ddd;
    }
    
    ::part(radio-list-item):hover {
      background-color: #f9f9f9;
      cursor: pointer;
    }
    </style>

</head>

<body>
  <justifi-tokenize-payment-method
    account-id="acc_123"
    auth-token="authToken"
    payment-method-group-id="pmg_123"
    submit-button-text="Tokenize Payment Method"
  />
</body>

  <script>
    const justifiTokenizePaymentMethod = document.querySelector("justifi-tokenize-payment-method");

    // Handle successful tokenization
    justifiTokenizePaymentMethod.addEventListener("submit-event", (event) => {
      const response = event.detail.response;
      console.log("Tokenization successful:", response);
      
      if (response.token) {
        console.log("Payment method token:", response.token);
      }
      
      if (response.data) {
        console.log("Full payment method data:", response.data);
      }

      // BIN details (cards only; may be absent depending on BIN lookup)
      const binDetails = response.data?.data?.card?.bin_details;
      if (binDetails) {
        console.log("BIN details:", binDetails);
      }
    });

    // Handle errors
    justifiTokenizePaymentMethod.addEventListener("error-event", (event) => {
      console.error("Tokenization error:", event.detail);
    });

    // External tokenize button (when built-in submit button is hidden)
    document.getElementById("tokenize-button").addEventListener("click", async () => {
      try {
        const result = await justifiTokenizePaymentMethod.tokenizePaymentMethod();
        console.log("External tokenization result:", result);
      } catch (error) {
        console.error("External tokenization failed:", error);
      }
    });

    // Fill billing form programmatically
    document.getElementById("fill-billing-form").addEventListener("click", async () => {
      await justifiTokenizePaymentMethod.fillBillingForm({
        name: "John Doe",
        address_line1: "123 Main St",
        address_line2: "Apt 1",
        address_city: "Anytown",
        address_state: "NY", // Use 2-letter state code
        address_postal_code: "12345",
      });
      console.log("Billing form filled");
    }); 

    // Validate form
    document.getElementById("validate-form").addEventListener("click", async () => {
      const validation = await justifiTokenizePaymentMethod.validate();
      console.log("Validation result:", validation);
      
      if (validation.isValid) {
        console.log("Form is valid!");
      } else {
        console.log("Form has errors:", validation.errors);
      }
    });

  </script>

</html>`}</CodeBlock>

## Theming & Layout

<PartsMarkdownTable
  parts={getComponentParts(
    'justifi-tokenize-payment-method',
    componentTreeJson
  )}
/>
