> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nozle.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CheckoutButton

> A button that starts checkout for one plan through your BillingProvider and handles the result.

`CheckoutButton` starts checkout for one plan through `BillingProvider` and opens Razorpay, redirects, hands you a Stripe client secret, or reports a completed or scheduled change.

**When to use**

* A plan card or marketing section that sells one plan.
* An onboarding step that subscribes the new customer.
* Anywhere you need checkout without the full [`PricingTable`](/sdks/react/components/pricing-table).

**Light**

![CheckoutButton labelled Get Pro, light theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/light.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=1387d8c759d2d7c204fc4ba0892c404b)

**Dark**

![CheckoutButton labelled Get Pro, dark theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/dark.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=fc0b5e6acd1d8989906df74b930b6ab6)

**Minimal**

![CheckoutButton labelled Get Pro, minimal theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/minimal.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=b13590545f93100d39bf5258b1cf8274)

**Neobrutal**

![CheckoutButton labelled Get Pro, neobrutal theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/neobrutal.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=07f8ab4fc76327e8fa0fffc8f57b0717)

**Nozle**

![CheckoutButton labelled Get Pro, nozle theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/nozle.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=8dbc092bfe4b057d6d4cc0fc938733d1)

**Nozle Dark**

![CheckoutButton labelled Get Pro, nozleDark theme](https://mintcdn.com/nozle-d78f01d8/Ae2cAgwEfN_SPqzG/images/react/components/checkout-button/nozle-dark.png?fit=max\&auto=format\&n=Ae2cAgwEfN_SPqzG\&q=85\&s=455e28ec0522aa4cdfa1e49a0d73e646)

## Importing CheckoutButton

```tsx theme={null}
import {
  BillingProvider,
  CheckoutButton,
  type CompletedCheckoutResult,
  type CreateCheckout,
  type ScheduledCheckoutResult,
} from '@nozle-js/react'
```

## Rendering CheckoutButton

`CheckoutButton` must be inside a `BillingProvider` with `createCheckout`. Your merchant route creates the checkout with your secret key; see [Implement the merchant endpoint](/sdks/react/installation#implement-the-merchant-endpoint).

```tsx app/pricing/buy-pro.tsx theme={null}
'use client'
import { Checkout } from '@nozle-js/react'

const createCheckout: CreateCheckout = async (input) => {
  const response = await fetch('/api/billing/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(input),
  })
  if (!response.ok) throw new Error('Checkout could not be started')
  return response.json()
}

export function BuyPro() {
  return (
    <BillingProvider publishableKey="pk_nozle_..." createCheckout={createCheckout}>
      <CheckoutButton planCode="pro" label="Get Pro" returnUrl="https://app.example.com/settings/billing" />
    </BillingProvider>
  )
}
```

The button needs no customer data. It reads `createCheckout`, `verifyCheckout` and `getCheckoutStatus` from `BillingProvider`.

### What a click does

1. The button calls `createCheckout({ idempotencyKey, planCode, returnUrl, registerMandate? })`. It keeps one idempotency key per plan and reuses it when the customer clicks again, so a retry reopens the same pending checkout. A second click while the first is running does nothing.
2. For a `razorpay` result it loads Razorpay and opens it. A captured payment is checked with `verifyCheckout`, then polled with `getCheckoutStatus` up to 30 times, one second apart. A failed payment shows "Payment failed. You can retry checkout."
3. For a `processing` result it polls the same way and continues with the checkout it returns.
4. For a `hosted` result, or a result with a `url`, it redirects the page.
5. For a `stripe` result it passes the client secret to `onStripeClientSecret`.
6. For a `completed` or `scheduled` result it calls `onComplete` or `onScheduled`.

After a completed or scheduled result, reload the customer's billing data, for example with `useBillingRefresh()` from the [hooks](/sdks/react/hooks). See [checkout result types](/sdks/react/hooks#checkout-result-types).

## Examples

### Embedded Stripe checkout and results

Pass `onStripeClientSecret` and render [`Checkout`](/sdks/react/components/checkout) with the secret. `onComplete` and `onScheduled` report changes that need no payment step.

```tsx theme={null}
import { useState } from 'react'

export function BuyPro() {
  const [clientSecret, setClientSecret] = useState<string | null>(null)
  const [message, setMessage] = useState('')

  if (clientSecret) return <Checkout clientSecret={clientSecret} publishableKey="pk_live_stripe..." />
  return (
    <>
      <CheckoutButton
        planCode="pro"
        label="Get Pro"
        onStripeClientSecret={setClientSecret}
        onComplete={(result: CompletedCheckoutResult) => setMessage(`Subscribed: ${result.status}`)}
        onScheduled={(result: ScheduledCheckoutResult) => setMessage(`Change scheduled for ${result.effective_at}`)}
        onError={(error) => setMessage(error.message)}
      />
      {message && <p>{message}</p>}
    </>
  )
}
```

### Razorpay

For Razorpay, give the provider `verifyCheckout` and `getCheckoutStatus`. Each calls your backend, which scopes the request to the signed-in customer.

```tsx theme={null}
import type { CheckoutStatus, RazorpayVerification } from '@nozle-js/react'

const post = async <T,>(path: string, body: object): Promise<T> => {
  const response = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
  if (!response.ok) throw new Error(`Request failed: ${response.status}`)
  return response.json()
}

const createCheckout: CreateCheckout = (input) => post('/api/billing/checkout', input)
const verifyCheckout = (checkoutId: string, verification: RazorpayVerification) =>
  post<CheckoutStatus>('/api/billing/checkout/verify', { checkoutId, ...verification })
const getCheckoutStatus = (checkoutId: string) => post<CheckoutStatus>('/api/billing/checkout/status', { checkoutId })

export function RazorpayBuy() {
  return (
    <BillingProvider
      publishableKey="pk_nozle_..."
      createCheckout={createCheckout}
      verifyCheckout={verifyCheckout}
      getCheckoutStatus={getCheckoutStatus}
    >
      <CheckoutButton planCode="pro" registerMandate onSuccess={(paymentId) => console.log('paid', paymentId)} />
    </BillingProvider>
  )
}
```

## API reference

**planCode** `string` *required*

The plan to check out, sent to `createCheckout`.

**label** `string` (default: Get Started)

Button text. While checkout starts, the button reads "Loading..." and is disabled.

**returnUrl** `string`

Sent to `createCheckout`, where the provider returns the customer after payment. Defaults to the current URL.

**registerMandate** `boolean`

Sent to `createCheckout` when set, to ask for a recurring payment mandate. Not sent when left out.

**onStripeClientSecret** `(clientSecret: string) =\> void`

Fires for an embedded Stripe checkout and receives its client secret; render [`Checkout`](/sdks/react/components/checkout) with it. Without it, a Stripe result fails with "onStripeClientSecret is required for embedded Stripe checkout".

**onComplete** `(result: CompletedCheckoutResult) =\> void`

Fires for a `completed` result and receives it: the change took effect without a payment step, or a resumed checkout succeeded.

**onScheduled** `(result: ScheduledCheckoutResult) =\> void`

Fires for a `scheduled` result, such as a downgrade at renewal, and receives it.

**onSuccess** `(paymentId: string) =\> void`

Razorpay only. Fires when the payment is captured and the plan is fulfilled, and receives the Razorpay payment ID.

**onProcessing** `(status: CheckoutStatus) =\> void`

Fires with each status while the button waits for a payment to be confirmed, and receives that status.

**onDismiss** `() =\> void`

Razorpay only. Fires when the customer closes the Razorpay window without paying.

**onError** `(error: Error) =\> void`

Fires when checkout fails and receives the error. The message also shows next to the button in a `role="alert"` element.

**razorpayKeyId** `string`

Deprecated and not used. The Razorpay key comes from the checkout result.

**className** `string`

Class name on the button.

**style** `CSSProperties`

Inline styles on the button, on top of the theme's.

**appearance** `Appearance`

Theme, variables and element overrides. Defaults to the provider's. See [Appearance](/sdks/react/styling).

### States

| State | What shows |
| - | - |
| Starting checkout | "Loading..." on a disabled button. |
| Waiting for payment confirmation | "Payment is processing. Confirmation may take a little while." |
| Checkout failed | The error message next to the button in a `role="alert"` element. |
| No `createCheckout` on the provider | "BillingProvider createCheckout callback is required for checkout". |
| Outside a `BillingProvider` | The button throws. |

### Appearance

> **Element keys**
>
> `checkoutButton` (the button).
>
> Style them with `appearance.elements`. See [Elements](/sdks/react/styling#elements).

## Related

> **[Checkout](/sdks/react/components/checkout)**
>
> Renders the Stripe payment step for the client secret.

> **[PricingTable](/sdks/react/components/pricing-table)**
>
> Plan cards that start checkout the same way.

> **[UpgradeButton](/sdks/react/components/upgrade-button)**
>
> Confirms a plan change before checkout.

> **[Checkout result types](/sdks/react/hooks#checkout-result-types)**
>
> Every result `createCheckout` can return.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.