Pay
Creating Payment Methods
Payment Method Form

Building Custom Payment Method Forms

Learn how to create fully-customized forms for saving payment methods with secure input fields


The Payment Method Form SDK provides secure, PCI-compliant input fields that you can embed into custom-built forms for collecting and saving payment method details. Unlike payment forms that immediately process a transaction, the Payment Method Form saves card or bank account information for future use without charging the customer. This gives you complete control over your form's HTML, styling, and user experience while keeping sensitive payment data secure through isolated secure input fields.

Prerequisites

Before building payment method forms, it's helpful to learn about the following topics.


When to Use Payment Method Forms


Use the Payment Method Form SDK when you need to save payment method details for future use without immediately processing a payment. This is ideal for customer wallet management, subscription setup, and recurring payment scenarios where you want customers to save their payment information upfront.

By collecting payment details separately from transactions, you enable faster checkout experiences, recurring billing, and flexible payment management. The SDK handles sensitive data securely while giving you full control over the form design, layout, and user flow.

Common use cases

  • Customer Wallet Management: Allow customers to add, update, and manage saved payment methods in account settings
  • Subscription Setup: Collect payment methods during subscription signup without immediate billing
  • Trial Period Enrollment: Save payment method during trial signup for automatic conversion to paid
  • Recurring Payment Setup: Enable customers to save payment methods for automatic billing cycles
  • Multiple Payment Methods: Let customers add and manage multiple payment options
  • Payment Method Updates: Replace expired cards or update bank account information
  • Tokenization for Future Use: Securely store payment details for future one-click purchases

How Payment Method Forms Work


Payment Method Forms use a combination of regular HTML inputs and secure input fields:

Secure Input Fields

Sensitive data like card numbers, CVV, account numbers, and routing numbers are rendered in isolated iframes that communicate with Payload's secure environment. These fields appear as <div> elements with special attributes.

Regular Input Fields

Non-sensitive data like account holder name, billing address, and account type use standard HTML <input> elements with special attributes to identify them to the SDK.

Form Submission

When the form is submitted, the SDK collects data from both secure and regular fields, sends sensitive data directly to Payload's servers, and returns a payment method ID to your application.

Building a Payment Method Form


Creating a payment method form requires three steps: building the HTML form, creating an intent on your backend, and initializing the form with JavaScript.

import payload
from flask import Flask, jsonify
 
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
app = Flask(__name__)
 
 
@app.route('/create-payment-method-intent', methods=['POST'])
def create_intent():
    # Create an intent for a payment method form
    intent = pl.Intent.create(
        type="payment_method_form",
        payment_method_form={
            "payment_method_template": {
                "account_id": "acct_customer123",
                # Optionally configure defaults
                'transfer_type': 'two_way',
            }
        },
    )
 
    # Return the client token to the frontend
    return jsonify({'client_token': intent.token})
 
 
 
if __name__ == "__main__":
    app.run(port=3000)
<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Combined card input field -->
    <div pl-input="card" placeholder="Card number / MM / YY / CVV"></div>
 
    <!-- Account holder name -->
    <input type="text" pl-input="account_holder" placeholder="Name on card" />
 
    <!-- Submit button -->
    <button type="submit">Save Payment Method</button>
  </form>
 
  <script src="basic-form.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-method-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment method form
    const form = new Payload.Form(
      document.getElementById('payment-method-form')
    )
 
    // Listen for success
    form.on('success', (evt) => {
      console.log('Payment method created:', evt.payment_method_id)
      window.location.href = '/account/payment-methods?success=true'
    })
 
    // Listen for errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert(evt.message)
    })
  })

This example shows the complete flow for building a custom payment method form:

  1. Backend: Create an intent with type='payment_method_form' and configure payment method defaults
  2. Frontend HTML: Build your form with pl-form="payment_method" attribute and secure input fields using pl-input attributes
  3. Frontend JavaScript: Initialize with Payload(token) and new Payload.Form(formElement), or use React components like <PaymentMethodForm>, <CardNumber>, <RoutingNumber>
  4. Event Handling: Listen for success or error events (or use React props like onSuccess, onError)

The secure input fields render as iframes that capture sensitive data without exposing it to your application.

Secure Input Fields


The Payment Method Form SDK provides secure input components for capturing sensitive payment data. These fields are rendered as isolated iframes that communicate directly with Payload's secure environment.

Card Input Fields

Use a single combined card input

The combined card input provides all card fields in one input element:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Single combined card input -->
    <div pl-input="card" placeholder="Card number / MM / YY / CVV"></div>
 
    <button type="submit">Save Card</button>
  </form>
 
  <script src="single-card.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))
  .on('success', (evt) => {
    console.log('Card saved:', evt.payment_method_id)
  })

This creates a single input field that accepts card number, expiration, and CVV in a combined interface. This is the simplest option for collecting card details.

Use individual card input fields

Individual card fields give you more control over layout:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Individual card input fields -->
    <div>
      <label>Card Number</label>
      <div pl-input="card_number" placeholder="1234 5678 9012 3456"></div>
    </div>
 
    <div style="display: flex; gap: 1rem;">
      <div style="flex: 1;">
        <label>Expiration</label>
        <div pl-input="expiry" placeholder="MM / YY"></div>
      </div>
 
      <div style="flex: 1;">
        <label>CVV</label>
        <div pl-input="card_code" placeholder="123"></div>
      </div>
    </div>
 
    <button type="submit">Save Card</button>
  </form>
 
  <script src="split-card.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))
  .on('success', (evt) => {
    console.log('Card saved:', evt.payment_method_id)
  })

This creates three separate input fields for card number, expiration, and CVV, allowing you to arrange them in custom layouts that match your design system.

Use Google Pay for card collection

Google Pay allows customers to save payment methods using their Google account:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
  <script src="https://pay.google.com/gp/p/js/pay.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" required />
 
    <label>Billing ZIP Code</label>
    <input type="text" pl-input="billing_address[postal_code]" required />
 
    <div id="google-pay-button"></div>
  </form>
 
  <script src="googlepay.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-method-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment method form
    const form = new Payload.Form(document.getElementById('payment-method-form'))
 
    // Create Google Pay button
    const paymentsClient = new google.payments.api.PaymentsClient({
      environment: 'PRODUCTION' // Use 'TEST' for testing
    })
 
    const button = paymentsClient.createButton({
      onClick: () => {} // Handled by Payload.googlepay
    })
 
    document.getElementById('google-pay-button').appendChild(button)
 
    // Activate Google Pay on the button
    form.googlepay(button, function(active) {
      if (!active) {
        // Google Pay is not available on this device
        button.style.display = 'none'
        console.log('Google Pay is not available')
      }
    })
 
    // Handle successful payment method creation
    form.on('success', (evt) => {
      console.log('Payment method created:', evt.payment_method_id)
      window.location.href = '/account/payment-methods?success=true'
    })
 
    // Handle errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert('Failed to save payment method: ' + evt.message)
    })
  })

This creates a Google Pay button that opens Google's payment sheet. When the customer selects a card from their Google Pay wallet, it's automatically saved as a payment method. The .googlepay() method activates the button and includes a callback to detect if Google Pay is available on the device.

Use Apple Pay for card collection

Apple Pay allows customers to save payment methods using their Apple Wallet:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
  <style>
    .apple-pay-button {
      -webkit-appearance: -apple-pay-button;
      -apple-pay-button-type: plain;
      -apple-pay-button-style: black;
      width: 100%;
      height: 44px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" required />
 
    <label>Billing ZIP Code</label>
    <input type="text" pl-input="billing_address[postal_code]" required />
 
    <div id="apple-pay-button" class="apple-pay-button"></div>
  </form>
 
  <script src="applepay.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-method-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment method form
    const form = new Payload.Form(document.getElementById('payment-method-form'))
 
    // Get the Apple Pay button element
    const button = document.getElementById('apple-pay-button')
 
    // Activate Apple Pay on the button
    form.applepay(button, function(active) {
      if (!active) {
        // Apple Pay is not available on this device
        button.style.display = 'none'
        console.log('Apple Pay is not available')
      }
    })
 
    // Handle successful payment method creation
    form.on('success', (evt) => {
      console.log('Payment method created:', evt.payment_method_id)
      window.location.href = '/account/payment-methods?success=true'
    })
 
    // Handle errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert('Failed to save payment method: ' + evt.message)
    })
  })

This creates an Apple Pay button that opens Apple's payment sheet. When the customer authorizes with Face ID, Touch ID, or passcode, their selected card is saved as a payment method. The .applepay() method activates the button and includes a callback to detect if Apple Pay is available on the device.

Bank Account Input Fields

Use manual bank account entry

Manually enter routing number and account number:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Secure bank account input fields -->
    <div>
      <label>Routing Number</label>
      <div pl-input="routing_number" placeholder="000000000"></div>
    </div>
 
    <div>
      <label>Account Number</label>
      <div pl-input="account_number" placeholder="Account Number"></div>
    </div>
 
    <!-- Account type selection -->
    <div>
      <label>Account Type</label>
      <select pl-input="account_type" required>
        <option value="">Select account type</option>
        <option value="checking" selected>Checking</option>
        <option value="savings">Savings</option>
      </select>
    </div>
 
    <!-- Account class (optional) -->
    <div>
      <label>Account Class</label>
      <select pl-input="account_class">
        <option value="">Select account class</option>
        <option value="personal">Personal</option>
        <option value="business">Business</option>
      </select>
    </div>
 
    <button type="submit">Save Bank Account</button>
  </form>
 
  <script src="bank-account.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))
  .on('success', (evt) => {
    console.log('Bank account saved:', evt.payment_method_id)
  })

This creates secure input fields for routing number and account number, along with selects for account type (checking/savings) and account class (personal/business).

Use Plaid for instant bank connection

Plaid allows customers to connect their bank accounts by logging into their bank:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" required />
 
    <button type="button" id="plaid-button">
      Connect Bank Account via Plaid
    </button>
  </form>
 
  <script src="plaid.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-method-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment method form
    const form = new Payload.Form(document.getElementById('payment-method-form'))
 
    // Get the Plaid button element
    const plaidButton = document.getElementById('plaid-button')
 
    // Activate Plaid on the button
    form.plaid(plaidButton)
 
    // Handle successful payment method creation
    form.on('success', (evt) => {
      console.log('Bank account connected:', evt.payment_method_id)
      window.location.href = '/account/payment-methods?success=true'
    })
 
    // Handle errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert('Failed to connect bank account: ' + evt.message)
    })
  })

This creates a button that opens Plaid's Link interface. When the customer selects their bank and logs in, their bank account is instantly connected and verified. The .plaid() method activates the button and includes a callback to detect if Plaid is available and configured.

Regular Input Fields


Non-sensitive data uses standard HTML inputs with pl-input attributes. These fields can be any HTML input type and are used for account holder information, billing details, and configuration.

Account Holder Information

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Secure card input -->
    <div pl-input="card" placeholder="Card number / MM / YY / CVV"></div>
 
    <!-- Account holder name -->
    <div>
      <label>Name on Card</label>
      <input
        type="text"
        pl-input="account_holder"
        placeholder="John Doe"
        required
      />
    </div>
 
    <button type="submit">Save Payment Method</button>
  </form>
 
  <script src="account-holder.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))

The account holder name is required for most payment methods and should match the name on the card or bank account.

Billing Address

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Secure card input -->
    <div pl-input="card" placeholder="Card number / MM / YY / CVV"></div>
 
    <!-- Billing address fields -->
    <div>
      <label>Street Address</label>
      <input
        type="text"
        pl-input="billing_address[street_address]"
        placeholder="123 Main St"
      />
    </div>
 
    <div>
      <label>Apartment, suite, etc. (optional)</label>
      <input
        type="text"
        pl-input="billing_address[unit_number]"
        placeholder="Apt 4B"
      />
    </div>
 
    <div>
      <label>City</label>
      <input
        type="text"
        pl-input="billing_address[city]"
        placeholder="San Francisco"
      />
    </div>
 
    <div style="display: flex; gap: 1rem;">
      <div style="flex: 1;">
        <label>State/Province</label>
        <input
          type="text"
          pl-input="billing_address[state_province]"
          placeholder="CA"
        />
      </div>
 
      <div style="flex: 1;">
        <label>ZIP/Postal Code</label>
        <input
          type="text"
          pl-input="billing_address[postal_code]"
          placeholder="94102"
          required
        />
      </div>
    </div>
 
    <div>
      <label>Country</label>
      <select pl-input="billing_address[country_code]">
        <option value="US">United States</option>
        <option value="CA">Canada</option>
      </select>
    </div>
 
    <button type="submit">Save Payment Method</button>
  </form>
 
  <script src="billing-address.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))

Common billing fields:

  • billing_address[postal_code] - Postal/ZIP code (required for AVS)
  • billing_address[address_line_1] - Street address
  • billing_address[city] - City
  • billing_address[state_province] - State/province
  • billing_address[country_code] - Country code

Transfer Type Configuration

Control how the payment method can be used:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <!-- Bank account fields -->
    <div pl-input="routing_number" placeholder="Routing Number"></div>
    <div pl-input="account_number" placeholder="Account Number"></div>
 
    <select pl-input="account_type" required>
      <option value="checking" selected>Checking</option>
      <option value="savings">Savings</option>
    </select>
 
    <!-- Transfer type configuration -->
    <div>
      <label>How will this account be used?</label>
      <select pl-input="transfer_type" required>
        <option value="two_way">Both payments and payouts</option>
        <option value="send_only">Only for making payments</option>
        <option value="receive_only">Only for receiving payouts</option>
      </select>
    </div>
 
    <button type="submit">Save Bank Account</button>
  </form>
 
  <script src="transfer-type.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))

Transfer types:

  • send_only - Can only be used for payments (default for cards)
  • receive_only - Can only be used for receiving payouts (common for bank accounts)
  • two_way - Can be used for both payments and payouts

Handling Form Events


The Payment Method Form SDK emits events throughout the payment method creation lifecycle. Listen for these events to handle successful saves, errors, and validation.

Success Events

form.on('success', (evt) => {
  console.log('Payment method created:', evt.payment_method_id)
  window.location.href = `/account/payment-methods?success=true`
})

Error Events

form.on('error', (evt) => {
  console.error('Payment method error:', evt.message)
  alert(`Failed to save payment method: ${evt.message}`)
})

Validation Events

form.on('invalid', (evt) => {
  console.log('Invalid:', evt.message)
  if (evt.target) evt.target.classList.add('pl-invalid')
})
 
form.on('valid', (evt) => {
  if (evt.target) evt.target.classList.remove('pl-invalid')
})

Setting Account Defaults


Configure payment methods as defaults for specific transaction types. This enables automatic payment method selection for recurring billing, invoice payments, and payouts.

import payload
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
# Create intent with payment method set as default
intent = pl.Intent.create(
    type='payment_method_form',
    payment_method_form={
        'payment_method_template': {
            'account_id': 'acct_customer123',
            # Set as default for payments
            'account_defaults': {
                'paying': 'payments'
            }
        }
    }
)
 
# Or set as default for payouts
payout_intent = pl.Intent.create(
    type='payment_method_form',
    payment_method_form={
        'payment_method_template': {
            'account_id': 'acct_customer123',
            'account_defaults': {
                'paying': 'payouts'
            }
        }
    }
)
 
# Or set as default for both
both_intent = pl.Intent.create(
    type='payment_method_form',
    payment_method_form={
        'payment_method_template': {
            'account_id': 'acct_customer123',
            'account_defaults': {
                'paying': 'all'
            }
        }
    }
)

Default configurations:

  • account_defaults.paying: 'payments' - Default for collecting payments
  • account_defaults.paying: 'payouts' - Default for receiving payouts
  • account_defaults.paying: 'all' - Default for both payments and payouts

When a payment method is set as the default, transactions can reference only the account ID without specifying a payment method ID.

Pre-filling Form Values


Reduce form friction by pre-filling known information like account holder name or billing address from the customer's profile.

import payload
 
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
# Create intent with pre-filled values
intent = pl.Intent.create(
    type='payment_method_form',
    payment_method_form={
        'payment_method_template': {
            'account_id': 'acct_customer123',
            # Pre-fill account holder from customer profile
            'account_holder': 'John Doe',
            # Pre-fill known billing address
            'billing_address': {
                'address_line_1': '123 Main St',
                'city': 'San Francisco',
                'state_province': 'CA',
                'postal_code': '94102',
                'country_code': 'US',
            },
            # Set transfer type
            'transfer_type': 'two_way',
        }
    },
)
 
print(f"Client token: {intent.token}")

Pre-fill values in the intent's payment_method_template:

  • account_holder - Name of the account holder
  • billing_address - Known billing address
  • bank_account.currency - Bank account currency (USD/CAD)
  • transfer_type - Payment method usage restrictions

Styling Secure Input Fields


You can style secure input fields to match your form's design using several approaches. While secure inputs are rendered in isolated iframes for PCI compliance, the SDK provides multiple ways to apply custom styling.

Extend Payload Classes

Style secure inputs by targeting Payload's default CSS classes:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="styles.css" />
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <div>
      <label>Card Number</label>
      <div pl-input="card_number" placeholder="1234 5678 9012 3456"></div>
    </div>
 
    <div style="display: flex; gap: 1rem;">
      <div style="flex: 1;">
        <label>Expiration</label>
        <div pl-input="expiry" placeholder="MM / YY"></div>
      </div>
 
      <div style="flex: 1;">
        <label>CVV</label>
        <div pl-input="card_code" placeholder="123"></div>
      </div>
    </div>
 
    <div>
      <label>Name on Card</label>
      <input type="text" pl-input="account_holder" placeholder="John Doe" class="pl-input" />
    </div>
 
    <button type="submit">Save Payment Method</button>
  </form>
 
  <script src="style-extend-classes.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))
/* Extend Payload's default classes */
 
/* Base input styling */
.pl-input {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 16px;
  padding: 12px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  transition: border-color 0.2s;
}
 
/* Secure field specific styling */
.pl-input-sec {
  background-color: #fafafa;
}
 
/* Focused input state */
.pl-input.pl-focus {
  border-color: #3b82f6;
  outline: 2px solid rgba(59, 130, 246, 0.1);
  outline-offset: 0;
}
 
/* Invalid input state */
.pl-input.pl-invalid {
  border-color: #ef4444;
  background-color: #fef2f2;
}
 
/* Form layout */
form[pl-form="payment_method"] {
  max-width: 500px;
  margin: 0 auto;
}
 
form[pl-form="payment_method"] > * {
  margin-bottom: 16px;
}
 
form[pl-form="payment_method"] label {
  display: block;
  margin-bottom: 6px;
  font-weight: 500;
  color: #374151;
}
 
form[pl-form="payment_method"] button[type="submit"] {
  width: 100%;
  padding: 12px;
  background-color: #3b82f6;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}
 
form[pl-form="payment_method"] button[type="submit"]:hover {
  background-color: #2563eb;
}

Payload provides default classes that you can extend with custom CSS rules:

  • pl-input - Base input styling
  • pl-input-sec - Secure field styling
  • pl-focus - Focused input state
  • pl-invalid - Invalid input state

Override Default Classes

Replace Payload's default classes with your own:

<!DOCTYPE html>
<html>
<head>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="custom-styles.css" />
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <div class="container mt-5">
    <form id="payment-method-form" pl-form="payment_method">
      <div class="mb-3">
        <label class="form-label">Card Number</label>
        <div pl-input="card_number" placeholder="1234 5678 9012 3456"></div>
      </div>
 
      <div class="row mb-3">
        <div class="col">
          <label class="form-label">Expiration</label>
          <div pl-input="expiry" placeholder="MM / YY"></div>
        </div>
 
        <div class="col">
          <label class="form-label">CVV</label>
          <div pl-input="card_code" placeholder="123"></div>
        </div>
      </div>
 
      <div class="mb-3">
        <label class="form-label">Name on Card</label>
        <input type="text" pl-input="account_holder" placeholder="John Doe" class="form-control" />
      </div>
 
      <button type="submit" class="btn btn-primary w-100">Save Payment Method</button>
    </form>
  </div>
 
  <script src="style-override-classes.js"></script>
</body>
</html>
Payload('your_token')
new Payload.Form(document.getElementById('payment-method-form'))
/* Override Payload's default classes with Bootstrap styling */
 
.pl-input {
  /* Use Bootstrap form-control class styling */
  display: block;
  width: 100%;
  padding: 0.375rem 0.75rem;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  color: #212529;
  background-color: #fff;
  background-clip: padding-box;
  border: 1px solid #ced4da;
  appearance: none;
  border-radius: 0.375rem;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
 
.pl-input.pl-focus {
  color: #212529;
  background-color: #fff;
  border-color: #86b7fe;
  outline: 0;
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
 
.pl-input.pl-invalid {
  border-color: #dc3545;
  padding-right: calc(1.5em + 0.75rem);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right calc(0.375em + 0.1875rem) center;
  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}
 
.pl-input.pl-invalid:focus {
  border-color: #dc3545;
  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}

This is useful when integrating with CSS frameworks like Bootstrap or Tailwind.

Schema Reference


The following fields are available when configuring the Payment Method Form intent:

Intent Configuration

Configuration options passed when creating the payment method form intent on your backend:

payment_method_form
object
Configuration for the payment method form intent. Use this type to create a native form for collecting and saving payment method details (cards, bank accounts) without immediately charging. The payment method can then be used for future transactions. Returns a token for rendering with the SDK. Required when type is payment_method_form.
payment_method_template
object
Pre-filled payment method configuration including transfer type, billing address, account holder, currency settings, and account defaults. These values populate the payment method form and determine how the saved payment method can be used. Allows you to set sensible defaults for the payment method being collected.
account_defaults
object
Default transaction types that this payment method will be used for. Controls whether the payment method is the default for paying or funding transactions, and which specific types of transactions it applies to.
funding
enum[string]
Controls which funding transaction types this payment method can be used for. Possible values: "all" (use for all funding transactions), "deposits" (use only for receiving deposits), or "withdraws" (use only for withdrawals). Determines the default usage for receiving funds.
Values: all, deposits, withdraws
paying
enum[string]
Controls which paying transaction type this payment method will be the default for. Possible values: "all" (use for all paying transactions), "payments" (use only for sending payments), or "payouts" (use only for receiving payouts). Determines the default usage for sending funds. If the account default is set for payments, that payment method will be used for any automatic invoice payments.
Values: all, payments, payouts
account_holder
string
Pre-filled name of the account holder for the payment method. For bank accounts, this is the name on the account. For cards, this is the cardholder name. When provided, this value will be used to populate the form.
account_id
string
The ID of the account that will own this payment method. Links the payment method to a specific customer or processing account. Used to associate the payment method with the correct account when it is created.
bank_account
object
Configuration specific to bank account payment methods. Contains bank account-specific settings such as the currency.
currency
enum[string]
The currency for bank account transactions. Must be either "USD" (US Dollars) or "CAD" (Canadian Dollars). Determines the currency for transactions using this bank account.
Values: USD, CAD
billing_address
object
Pre-filled billing address for the payment method. When provided, these values will be used to populate the address fields in the checkout form. Customers may be able to edit these values depending on checkout configuration.
address_line_1
string
Street address of the address
address_line_2
string
Unit number of the address
city
string
City of the company
country_code
string
Country code of the address
postal_code
string
Postal code of the address
state_province
string
State of the address
id
string
The unique identifier of an existing payment method to update. When provided, the form will be used to update the payment method, such as revalidating the CVV, updating the expiration date, or changing the billing address.
transfer_type
enum[string]
The transfer capabilities for this payment method. Possible values: "send_only" (can only send payments/fund payouts), "receive_only" (can only receive payouts/deposits), or "two_way" (can both send and receive). Controls how the payment method can be used for transactions.
Values: send_only, receive_only, two_way

Next Steps

Enhance payment processing and payment method management


Process Transactions

Use Payment API to process payments with saved payment methods, send Payouts to saved bank accounts, and set up Recurring Payments for automatic billing with saved methods.

Verify and Manage Payment Methods

Implement Payment Method Verification to verify bank accounts and cards, use Payment Method API to programmatically manage payment methods, and explore Updating Payment Methods to modify existing methods.

Build Payment Interfaces

Create Payment Form for collecting payment and processing immediately, integrate Checkout Plugin for pre-built checkout UI, and add Digital Wallets like Plaid, Apple Pay, and Google Pay.


Related articles