Building Custom Payment Forms
Learn how to create fully-customized payment forms with secure input fields
The Payment Form SDK provides secure, PCI-compliant input fields that you can embed into custom-built payment forms. Unlike drop-in checkout solutions, the Payment Form gives you complete control over your form's HTML, styling, and user experience while keeping sensitive payment data secure through isolated secure input fields.
Use the Payment Form when you need full design control over your checkout experience. For a pre-built checkout UI, see Checkout Plugin. For hosted checkout pages, see Checkout Link.
Prerequisites
Before building payment forms, it's helpful to learn about the following topics.
When to Use Payment Forms
Use the Payment Form SDK when you need complete control over your checkout form's design and user experience while maintaining PCI compliance. The SDK provides secure input fields that handle sensitive card and bank account data, isolating this data from your application while giving you full control over the surrounding UI, layout, and styling.
Common use cases
- Custom-Branded Checkout: Build checkout experiences that perfectly match your brand guidelines and design system
- Complex Form Layouts: Create multi-step checkout flows or custom field arrangements
- Integrated Checkout Flows: Embed payment fields directly within existing forms alongside shipping, billing, and order details
- Multi-Language Forms: Build fully localized checkout experiences with custom labels and error messages
How Payment Forms Work
Payment Forms use a combination of regular HTML inputs and secure input fields:
-
Secure Input Fields: Sensitive data like card numbers, CVV, and bank account 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 amount, description, and billing address 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 transaction ID or payment method ID to your application.
PCI Compliance: Sensitive payment data never touches your server. The secure input fields communicate directly with Payload's PCI-compliant environment, keeping your application out of PCI scope.
Building a Payment Form
Creating a payment form requires three steps: building the HTML form, creating an intent on your backend, and initializing the form with JavaScript.
This example shows the complete flow for building a custom payment form:
- Backend: Create an intent with
type='payment_form'and configure payment details - Frontend HTML: Build your form with
pl-form="payment"attribute and secure input fields usingpl-inputattributes - Frontend JavaScript: Initialize with
Payload(token)andnew Payload.Form(formElement), or use React components like<PaymentForm>,<CardNumber>,<Expiry>, and<CardCode> - Event Handling: Listen for
successordeclinedevents (or use React props likeonSuccess,onDeclined)
The secure input fields (pl-input="card" or individual fields like pl-input="card_number")
render as iframes that capture sensitive data without exposing it to your application.
Secure Input Fields
The Payment 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:
This creates a single input field that accepts card number, expiration, and CVV in a combined interface. This is the simplest option for card payments.
Use individual card input fields
Individual card fields give you more control over layout:
This creates three separate input fields for card number, expiration, and CVV, allowing you to arrange them in custom layouts.
Use Google Pay for checkout
Google Pay allows customers to pay using cards stored in their Google account:
This creates a Google Pay button that opens Google's payment sheet. When the customer selects a
card from their Google Pay wallet and confirms, the payment is processed immediately. The
.googlepay() method activates the button and includes a callback to detect if Google Pay is
available on the device.
Google Pay Availability: Google Pay is available on Chrome, Edge, and other Chromium-based browsers. The callback function allows you to hide the button if Google Pay is not supported on the user's device.
Use Apple Pay for checkout
Apple Pay allows customers to pay using cards stored in their Apple Wallet:
This creates an Apple Pay button that opens Apple's payment sheet. When the customer authorizes
with Face ID, Touch ID, or passcode, the payment is processed immediately. The .applepay()
method activates the button and includes a callback to detect if Apple Pay is available on the
device.
Apple Pay Availability: Apple Pay is available on Safari for iOS, iPadOS, and macOS devices that support Apple Pay. The callback function allows you to hide the button if Apple Pay is not supported on the user's device.
Bank Account Input Fields
Use manual bank account entry
Manually enter routing number and account number for bank payments:
This creates secure input fields for routing number and account number, along with selects for account type (checking/savings) for bank payments.
Instant Verification: Payload automatically verifies bank accounts instantly, eliminating delays associated with micro-deposit verification. This enables immediate bank payment processing.
Use Plaid for instant bank payment
Plaid allows customers to pay using their bank account by logging into their bank:
This creates a button that opens Plaid's Link interface. When the customer selects their bank
and logs in, the payment is processed immediately using their connected bank account. The
.plaid() method activates the button and includes a callback to detect if Plaid is available
and configured.
Bank Login Experience: Plaid provides a streamlined experience where customers authenticate directly with their bank instead of manually entering account details.
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 payment details, billing information, and custom data.
Payment Details
Common payment detail fields:
-
amount- Payment amount -
description- Payment description -
processing_id- Processing account ID (for routing payments to specific accounts)
Billing Information
Common billing fields:
-
account_holder- Name on card/account -
billing_address[postal_code]- Postal/ZIP code -
billing_address[line1]- Street address -
billing_address[city]- City -
billing_address[state]- State/province -
billing_address[country]- Country code
Customer Information
For linking payments to customer accounts:
Use customer_id or account_id to associate the payment with an existing customer account.
Handling Form Events
The Payment Form SDK emits events throughout the payment lifecycle. Listen for these events to handle successful payments, errors, and validation.
Success Events
form.on('success', (evt) => {
console.log('Payment successful:', evt.transaction_id)
window.location.href = `/payment/success?txn=${evt.transaction_id}`
})Error Events
form.on('declined', (evt) => {
console.error('Payment declined:', evt.message)
alert('Your payment was declined. Please try a different payment method.')
})
form.on('error', (evt) => {
console.error('Payment error:', evt.message)
alert(`Payment failed: ${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')
})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:
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:
This is useful when integrating with CSS frameworks like Bootstrap or Tailwind.
Schema Reference
The following fields are available when configuring the Payment Form intent:
Intent Configuration
Configuration options passed when creating the payment form intent on your backend. For complete API reference, see Intents API Reference.
payment_formpayment_templateamountattrsclearing_timinginstant, same_day, next_daydescriptionfunding_timingstandard, instant, rapidinvoice_allocationsorder_numberreceiveraccount_idmethod_idsenderaccount_idmethodaccount_defaultsfundingall, deposits, withdrawspayingall, payments, payoutsaccount_holderaccount_idbank_accountcurrencyUSD, CADbilling_addressaddress_line_1address_line_2citycountry_codepostal_codestate_provinceidtransfer_typesend_only, receive_only, two_waysourcekeyed, googlepay, applepay, checkstatusvalueauthorized, processedtransfersNext Steps
Enhance your payment form implementation
Store Payment Methods
Create and manage payment methods to enable saved cards and bank accounts for faster checkout and recurring payments.
Handle Refunds
Process refunds and voids for cancellations and returns after payments complete.
Monitor Payment Events
Subscribe to webhook notifications to track payment status and automate order fulfillment.
Related articles
- Checkout Plugin - Pre-built checkout UI alternative
- Checkout Link - Hosted checkout page alternative
- Google Pay - Google Pay integration guide
- Apple Pay - Apple Pay integration guide
- Intents API Reference
- Transactions API Reference
- Payment Methods API Reference