Build a custom flow for adding a new payment method
This guide will walk you through how to build a custom user interface that allows users to add a new payment method to their account. This is a common feature in a user's billing or account settings page, allowing them to pre-emptively add a payment method for future use.
For the custom flow that allows users to add a new payment method during checkout, see the dedicated guide.
Enable billing features
To use billing features, you first need to ensure they are enabled for your application. Follow the Billing documentation to enable them and set up your plans.
Add payment method flow
To add a new payment method for a user, you must:
- Set up the <PaymentElementProvider /> to create a context for the user's payment actions.
- Render the <PaymentElement /> to display the secure payment fields from your provider.
- Use the usePaymentElement() hook to submit the form and create a payment token.
- Use the useUser() hook to attach the newly created payment method to the user.
The following example demonstrates how to create a billing page where a user can add a new payment method. It is split into two components:
-
<UserBillingPage />
: Sets up the<PaymentElementProvider />
, which specifies that the payment actions within its children arefor
theuser
. -
<AddPaymentMethodForm />
: Renders the payment form and handles the submission logic. It usesusePaymentElement()
to get thesubmit
function anduseUser()
to get theuser
object. When the form is submitted, it first creates a payment token and then attaches it to the user.app /user /billing /page.tsx import { ClerkLoaded } from '@clerk/nextjs' import { PaymentElementProvider } from '@clerk/nextjs/experimental' import { AddPaymentMethodForm } from './AddPaymentMethodForm' export default function Page() { return ( <div> <h1>Billing Settings</h1> <ClerkLoaded> <PaymentElementProvider for="user"> <AddPaymentMethodForm /> </PaymentElementProvider> </ClerkLoaded> </div> ) }
app /user /billing /AddPaymentMethodForm.tsx 'use client' import { useUser } from '@clerk/nextjs' import { usePaymentElement, PaymentElement } from '@clerk/nextjs/experimental' import { useState } from 'react' export function AddPaymentMethodForm() { const { user } = useUser() const { submit, isFormReady } = usePaymentElement() const [isSubmitting, setIsSubmitting] = useState(false) const [error, setError] = useState<string | null>(null) const handleAddPaymentMethod = async (e: React.FormEvent) => { e.preventDefault() if (!isFormReady || !user) { return } setError(null) setIsSubmitting(true) try { // 1. Submit the form to the payment provider to get a payment token const { data, error } = await submit() // Usually a validation error from stripe that you can ignore. if (error) { setIsSubmitting(false) return } // 2. Use the token to add the payment source to the user await user.addPaymentSource(data) // 3. Handle success (e.g., show a confirmation, clear the form) alert('Payment method added successfully!') } catch (err: any) { setError(err.message || 'An unexpected error occurred.') } finally { setIsSubmitting(false) } } return ( <form onSubmit={handleAddPaymentMethod}> <h3>Add a new payment method</h3> <PaymentElement /> <button type="submit" disabled={!isFormReady || isSubmitting}> {isSubmitting ? 'Saving...' : 'Save Card'} </button> {error && <p style={{ color: 'red' }}>{error}</p>} </form> ) }
Feedback
Last updated on