āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/_partials/billing/add-new-payment-method ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
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 are for the user.<AddPaymentMethodForm />: Renders the payment form and handles the submission logic. It uses usePaymentElement() to get the submit function and useUser() to get the user object. When the form is submitted, it first creates a payment token and then attaches it to the user.<CodeBlockTabs options={["<UserBillingPage />", "<AddPaymentMethodForm />"]}>
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>
)
}
'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>
)
}
</CodeBlockTabs>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā