Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8225f3c
Initial setup
SarahSoutoul Sep 30, 2025
2ba89b4
Fix linting
SarahSoutoul Sep 30, 2025
f3aad91
More testing
SarahSoutoul Oct 1, 2025
f39f982
Add other hooks
SarahSoutoul Oct 1, 2025
7a43406
Remove testing code
SarahSoutoul Oct 1, 2025
d4ca96c
Add more hooks
SarahSoutoul Oct 1, 2025
955f9b8
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 1, 2025
fd54530
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 1, 2025
955a810
Adds code examples for SDKs
SarahSoutoul Oct 1, 2025
cca2ef1
Remove remix
SarahSoutoul Oct 1, 2025
95b98ea
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 1, 2025
ba4900e
Refinements
SarahSoutoul Oct 2, 2025
d485759
Uncomment the use auth parameters
NWylynko Oct 6, 2025
11efc0b
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 8, 2025
b91bc45
switch to new typedocs
NWylynko Oct 8, 2025
368aee1
Replace typedoc return with new location
SarahSoutoul Oct 8, 2025
63593e0
Add code examples billing hooks
SarahSoutoul Oct 8, 2025
409c4e8
Fix linting
SarahSoutoul Oct 8, 2025
54fd864
Billing hooks typedoc
SarahSoutoul Oct 9, 2025
6bd65ae
Add headings for billing hooks
SarahSoutoul Oct 10, 2025
6916dcb
Fix some issues billing hooks
SarahSoutoul Oct 10, 2025
7733c65
Refine code examples afte testing
SarahSoutoul Oct 13, 2025
412b293
Replace correct params for finalize method
SarahSoutoul Oct 13, 2025
9c66dff
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 14, 2025
333e2f8
Add astro example
SarahSoutoul Oct 14, 2025
4494898
Fixing build with latest changes
SarahSoutoul Oct 14, 2025
26ab0f6
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 15, 2025
1ed0325
Finalize updates
SarahSoutoul Oct 15, 2025
dd962c7
Fix linting
SarahSoutoul Oct 15, 2025
b8bed5b
Merge branch 'main' into ss/DOCS-10983
SarahSoutoul Oct 15, 2025
9576c44
broad docs review
alexisintech Oct 16, 2025
7b1bbcb
Fix headings for usePaymentElement
SarahSoutoul Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 148 additions & 67 deletions docs/_partials/billing/add-new-payment-method.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,161 @@ The following example demonstrates how to create a billing page where a user can
- **`<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 />"]}>
```tsx {{ filename: '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>
)
}
```

```tsx {{ filename: '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
}
<If sdk="nextjs">
<CodeBlockTabs options={["<UserBillingPage />", "<AddPaymentMethodForm />"]}>
```tsx {{ filename: '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>

setError(null)
setIsSubmitting(true)
<ClerkLoaded>
<PaymentElementProvider for="user">
<AddPaymentMethodForm />
</PaymentElementProvider>
</ClerkLoaded>
</div>
)
}
```

try {
// 1. Submit the form to the payment provider to get a payment token
const { data, error } = await submit()
```tsx {{ filename: 'app/user/billing/AddPaymentMethodForm.tsx', collapsible: true }}
'use client'
import { useUser } from '@clerk/nextjs'
import { usePaymentElement, PaymentElement } from '@clerk/nextjs/experimental'
import { useState } from 'react'

// Usually a validation error from stripe that you can ignore.
if (error) {
setIsSubmitting(false)
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
}

// 2. Use the token to add the payment source to the user
await user.addPaymentSource(data)
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)
// 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>
</If>

<If sdk="react">
<CodeBlockTabs options={["<UserBillingPage />", "<AddPaymentMethodForm />"]}>
```tsx {{ filename: 'src/pages/user/billing/page.tsx' }}
import { ClerkLoaded } from '@clerk/clerk-react'
import { PaymentElementProvider } from '@clerk/clerk-react/experimental'
import { AddPaymentMethodForm } from './AddPaymentMethodForm'

export default function Page() {
return (
<div>
<h1>Billing Settings</h1>

<ClerkLoaded>
<PaymentElementProvider for="user">
<AddPaymentMethodForm />
</PaymentElementProvider>
</ClerkLoaded>
</div>
)
}
```

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>
```tsx {{ filename: 'src/pages/user/billing/AddPaymentMethodForm.tsx', collapsible: true }}
import { useUser } from '@clerk/clerk-react'
import { usePaymentElement, PaymentElement } from '@clerk/clerk-react/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>
</If>
20 changes: 0 additions & 20 deletions docs/_partials/billing/use-checkout-options.mdx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,5 @@ For the custom flow that allows users to add a new payment method **during check
1. Use the [`usePaymentElement()`](/docs/reference/hooks/use-payment-element) hook to submit the form and create a payment token.
1. Use the [`useUser()`](/docs/reference/hooks/use-user) hook to attach the newly created payment method to the user.

<Tabs items={["Next.js"]}>
<Tab>
<Include src="_partials/billing/add-new-payment-method" />
</Tab>
</Tabs>
<Include src="_partials/billing/add-new-payment-method" />
</Steps>
Loading