Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fa53b9b
add billing guide and reference page
panteliselef Jul 8, 2025
1b0b52a
fix build
panteliselef Jul 8, 2025
0ca1396
format
panteliselef Jul 8, 2025
fc60f8c
add payment element
panteliselef Jul 8, 2025
aa288df
add payment method guide
panteliselef Jul 8, 2025
ae95ca1
fix incorrect usage
panteliselef Jul 9, 2025
4acef19
example with existing methods
panteliselef Jul 9, 2025
ce5d418
include control components
panteliselef Jul 9, 2025
a357c20
add custom flow to overview
panteliselef Jul 9, 2025
d28d1bd
Update docs/hooks/use-checkout.mdx
panteliselef Jul 10, 2025
f150280
Merge branch 'refs/heads/main' into elef/billing-apis
panteliselef Jul 14, 2025
e436fac
add usePlans
panteliselef Jul 14, 2025
ff92e0e
Merge branch 'main' into elef/billing-apis
panteliselef Jul 17, 2025
e54e936
formatting
panteliselef Jul 17, 2025
aeced27
add useSubscription
panteliselef Jul 17, 2025
c80172b
Merge branch 'main' into elef/billing-apis
panteliselef Jul 22, 2025
22af4b4
add experimental callout
panteliselef Jul 22, 2025
bf80eab
change callouts of custom flows
panteliselef Jul 22, 2025
1d27701
fix
panteliselef Jul 22, 2025
f2f0bdb
remove invalid example
panteliselef Jul 22, 2025
47e2294
docs review
SarahSoutoul Jul 24, 2025
4eb9c22
Merge branch 'main' into elef/billing-apis
panteliselef Jul 25, 2025
ac51109
fix paths
panteliselef Jul 25, 2025
0777d63
address comment about return object
panteliselef Jul 25, 2025
2ed5291
improve useCheckout snippets
panteliselef Jul 26, 2025
9e0c688
format
panteliselef Jul 26, 2025
d242237
improve ref types
panteliselef Jul 29, 2025
0bdaf66
docs review pt 1
alexisintech Jul 29, 2025
29dbaba
fix broken hash
alexisintech Jul 29, 2025
c800db0
small updates
alexisintech Jul 29, 2025
3184963
address review comments
panteliselef Jul 30, 2025
6505dd4
Merge branch 'main' into elef/billing-apis
panteliselef Jul 30, 2025
10a43ac
docs review pt2
alexisintech Jul 30, 2025
36edb29
add custom flow callout to custom flow guides
alexisintech Jul 30, 2025
4a6e665
fix links
alexisintech Jul 30, 2025
2072c32
address infinite rendering
panteliselef Jul 30, 2025
c2c4a62
replace fallback
panteliselef Jul 30, 2025
d4d854c
update custom flow
alexisintech Jul 30, 2025
92116f8
replace statuses
panteliselef Aug 1, 2025
2da7f14
update for prop usage
panteliselef Aug 1, 2025
7e4bfca
resolve todos
panteliselef Aug 5, 2025
ac3e452
Merge branch 'main' into elef/billing-apis
panteliselef Aug 5, 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
82 changes: 82 additions & 0 deletions docs/_partials/billing/add-new-payment-method.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 />"]}>
```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
}

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>
3 changes: 3 additions & 0 deletions docs/_partials/billing/api-experimental-guide.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!WARNING]
>
> This guide is using experimental APIs and subject to change while Clerk Billing is under Beta. To mitigate potential disruptions, we recommend [pinning](https://docs.renovatebot.com/dependency-pinning/#what-is-dependency-pinning) your SDK and `clerk-js` package versions.
20 changes: 20 additions & 0 deletions docs/_partials/billing/use-checkout-options.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Properties>
- `for?`
- `'organization'`

Specifies if the checkout is for an organization. If omitted, the checkout defaults to the current user.

---

- `planId`
- `string`

The ID of the subscription plan to check out (e.g. `cplan_xxx`).

---

- `planPeriod`
- `'month' | 'annual'`

The billing period for the plan.
</Properties>
4 changes: 2 additions & 2 deletions docs/billing/b2b-saas.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Clerk billing for B2B SaaS
description: Clerk billing is a feature that allows you to create and manage plans and features for your application.
sdk: nextjs, react, expo, react-router, astro, tanstack-react-start, remix, nuxt, vue, js-frontend, expressjs, fastify, js-backend
sdk: nextjs, react, expo, react-router, astro, tanstack-react-start, remix, nuxt, vue, js-frontend, expressjs, fastify, js-backend
---

<Include src="_partials/billing/billing-experimental" />
Expand Down Expand Up @@ -42,7 +42,7 @@ You can add a feature to a plan when you are creating a plan. To add it after a

## Create a pricing page

You can create a pricing page by using the [`<PricingTable />`](/docs/components/pricing-table) component. This component displays a table of plans and features that customers can subscribe to. **It's recommended to create a dedicated page**, as shown in the following example.
You can create a pricing page by using the [`<PricingTable />`](/docs/components/billing/pricing-table) component. This component displays a table of plans and features that customers can subscribe to. **It's recommended to create a dedicated page**, as shown in the following example.

<Include src="_partials/billing/create-an-organization-pricing-table" />

Expand Down
4 changes: 2 additions & 2 deletions docs/billing/b2c-saas.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Clerk billing for B2C SaaS
description: Clerk billing is a feature that allows you to create and manage plans and features for your application.
sdk: nextjs, react, expo, react-router, astro, tanstack-react-start, remix, nuxt, vue, js-frontend, expressjs, fastify, js-backend
sdk: nextjs, react, expo, react-router, astro, tanstack-react-start, remix, nuxt, vue, js-frontend, expressjs, fastify, js-backend
---

<Include src="_partials/billing/billing-experimental" />
Expand Down Expand Up @@ -42,7 +42,7 @@ You can add a feature to a plan when you are creating a plan. To add it after a

## Create a pricing page

You can create a pricing page by using the [`<PricingTable />`](/docs/components/pricing-table) component. This component displays a table of plans and features that users can subscribe to. **It's recommended to create a dedicated page**, as shown in the following example.
You can create a pricing page by using the [`<PricingTable />`](/docs/components/billing/pricing-table) component. This component displays a table of plans and features that users can subscribe to. **It's recommended to create a dedicated page**, as shown in the following example.

<Include src="_partials/billing/create-a-pricing-table" />

Expand Down
7 changes: 7 additions & 0 deletions docs/billing/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ Clerk billing allows your customers to purchase recurring subscriptions to your

- [Billing for B2B SaaS](/docs/billing/b2b-saas)
- To charge companies or organizations

---

- [Build a simple checkout page](/docs/custom-flows/checkout-new-payment-method)
- To charge users with a new payment method

---
</Cards>
33 changes: 33 additions & 0 deletions docs/custom-flows/add-new-payment-method.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Build a custom flow for adding a new payment method
description: Learn how to use the Clerk API to build a custom flow for adding a new payment method to a user's account.
---

<Include src="_partials/custom-flows-callout" />

<Include src="_partials/billing/api-experimental-guide" />

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](/docs/custom-flows/checkout-new-payment-method).

<Steps>
### Enable billing features

To use billing features, you first need to ensure they are enabled for your application. Follow the [Billing documentation](/docs/billing/overview) to enable them and set up your plans.

### Add payment method flow

To add a new payment method for a user, you must:

1. Set up the [`<PaymentElementProvider />`](/docs/hooks/use-payment-element) to create a context for the user's payment actions.
1. Render the [`<PaymentElement />`](/docs/hooks/use-payment-element) to display the secure payment fields from your provider.
1. Use the [`usePaymentElement()`](/docs/hooks/use-payment-element) hook to submit the form and create a payment token.
1. Use the [`useUser()`](/docs/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>
</Steps>
174 changes: 174 additions & 0 deletions docs/custom-flows/checkout-existing-payment-method.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
title: Build a custom checkout flow with an existing payment method
description: Learn how to use the Clerk API to build a custom checkout flow that allows users to checkout with an existing payment method.
---

<Include src="_partials/custom-flows-callout" />

<Include src="_partials/billing/api-experimental-guide" />

This guide will walk you through how to build a custom user interface for a checkout flow that allows users to checkout **with an existing payment method**. For the custom flow that allows users to **add a new payment method** during checkout, see the [dedicated guide](/docs/custom-flows/checkout-new-payment-method).

<Steps>
## Enable billing features

To use billing features, you first need to ensure they are enabled for your application. Follow the [Billing documentation](/docs/billing/overview) to enable them and setup your plans.

## Checkout flow

To create a checkout session with an existing payment method, you must:

1. Set up the checkout provider with plan details.
1. Initialize the checkout session when the user is ready.
1. Fetch and display the user's existing payment methods.
1. Confirm the payment with the selected payment method.
1. Complete the checkout process and redirect the user.

<Tabs items={["Next.js"]}>
<Tab>
The following example:

1. Uses the [`useCheckout()`](/docs/hooks/use-checkout) hook to initiate and manage the checkout session.
1. Uses the [`usePaymentMethods()`](/docs/hooks/use-payment-methods) hook to fetch the user's existing payment methods.
1. Assumes that you have already have a valid `planId`, which you can acquire in many ways:
- [Copy from the Clerk Dashboard](https://dashboard.clerk.com/last-active?path=billing/plans?tab=user).
- Use the [Clerk Backend API](TODO).
- Use the new [`usePlans()`](/docs/hooks/use-plans) hook to get the plan details.

This example is written for Next.js App Router but can be adapted for any React-based framework.

```tsx {{ filename: 'app/checkout/page.tsx' }}
'use client'
import * as React from 'react'
import { SignedIn, ClerkLoaded } from '@clerk/nextjs'
import { CheckoutProvider, useCheckout, usePaymentMethods } from '@clerk/nextjs/experimental'
import { useMemo, useState } from 'react'

export default function CheckoutPage() {
return (
<CheckoutProvider planId="cplan_xxx" planPeriod="month">
<ClerkLoaded>
<SignedIn>
<CustomCheckout />
</SignedIn>
</ClerkLoaded>
</CheckoutProvider>
)
}

function CustomCheckout() {
const { checkout } = useCheckout()
const { status } = checkout

if (status === 'awaiting_initialization') {
return <CheckoutInitialization />
}

return (
<div className="checkout-container">
<CheckoutSummary />
<PaymentSection />
</div>
)
}

function CheckoutInitialization() {
const { checkout } = useCheckout()
const { start, status, fetchStatus } = checkout

if (status !== 'awaiting_initialization') {
return null
}

return (
<button onClick={start} disabled={fetchStatus === 'fetching'} className="start-checkout-button">
{fetchStatus === 'fetching' ? 'Initializing...' : 'Start Checkout'}
</button>
)
}

function PaymentSection() {
const { checkout } = useCheckout()
const { data, isLoading } = usePaymentMethods({
for: 'user',
pageSize: 20,
})

const { isConfirming, confirm, finalize, error } = checkout

const [isProcessing, setIsProcessing] = useState(false)
const [paymentMethodId, setPaymentMethodId] = useState<string | null>(null)

const defaultMethod = useMemo(() => data?.find((method) => method.isDefault), [data])

const submitSelectedMethod = async () => {
const paymentSourceId = paymentMethodId || defaultMethod?.id
if (isProcessing || !paymentSourceId) return
setIsProcessing(true)

try {
// Confirm checkout with payment method
await confirm({ paymentSourceId })
// Complete checkout and redirect
finalize({ redirectUrl: '/dashboard' })
} catch (error) {
console.error('Payment failed:', error)
} finally {
setIsProcessing(false)
}
}

if (isLoading) {
return <div>Loading...</div>
}

return (
<>
<select
defaultValue={defaultMethod?.id}
onChange={(e) => {
const methodId = e.target.value
const method = data?.find((method) => method.id === methodId)
if (method) {
setPaymentMethodId(method.id)
}
}}
>
{data?.map((method) => (
<option key={method.id}>
**** **** **** {method.last4} {method.cardType}
</option>
))}
</select>

{error && <div>{error.message}</div>}

<button type="button" disabled={isProcessing || isConfirming} onClick={submitSelectedMethod}>
{isProcessing || isConfirming ? 'Processing...' : 'Complete Purchase'}
</button>
</>
)
}

function CheckoutSummary() {
const { checkout } = useCheckout()
const { plan, totals } = checkout

if (!plan) {
return null
}

return (
<div>
<h2>Order Summary</h2>
<span>{plan.name}</span>
<span>
{totals.totalDueNow.currencySymbol} {totals.totalDueNow.amountFormatted}
</span>
</div>
)
}
```
</Tab>
</Tabs>
</Steps>
Loading