> This page is for version v2 (default).
> For other versions, use one of these documentation indexes:
> - v2 (default): https://docs.paid.ai/v-2/llms.txt
> - v1: https://docs.paid.ai/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paid.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paid.ai/_mcp/server.

# Checkout

> Create checkout sessions programmatically to collect payments from your customers.

If you want to integrate the checkout process directly into your application, you can use our API to create sessions programmatically. This gives you full control over when and how your customers are prompted to pay.

Checkout isn't just for one-time payments - when a customer completes checkout, Paid creates an order (subscription) that tracks their plan, billing cycle, and usage over time.

## Prerequisites

* A [Paid API key](https://app.paid.ai/settings/api-keys) and the Paid SDK installed in the service that creates checkout sessions.
* A product or plan the customer can purchase. You can find the product ID in [Products](https://app.paid.ai/products), or create one through the [Products API](/api-reference/api-reference/products/create-product).
* [Stripe connected](https://app.paid.ai/settings/billing) for the organization. On a test-mode organization, you can use a Stripe sandbox and pay with test payment methods.
* A success URL in your application and a stable customer identifier when the checkout should attach to an existing user or account.

## Creating a session

To create a checkout session, call the API with the products you want the customer to purchase and a URL to redirect them to after payment.

The API returns an object containing all the information about the session, including a `url` where you should redirect your customer so they can complete their order.

#### Node.js

```typescript
import { PaidClient } from "@paid-ai/paid-node";

const client = new PaidClient({ token: "YOUR_PAID_API_KEY" });

const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  externalCustomerId: currentUser.id, // your internal user/customer identifier
  successUrl: "https://example.com/success?checkout_id={CHECKOUT_ID}",
});

// Redirect your customer to this URL
redirect(checkout.url);
```

#### Python

```python
from paid import Paid

client = Paid(token="YOUR_PAID_API_KEY")

checkout = client.checkouts.create_checkout(
    products=[{"id": "prod_abc123"}],
    external_customer_id=current_user.id,
    success_url="https://example.com/success?checkout_id={CHECKOUT_ID}",
)

# Redirect your customer to this URL
redirect(checkout.url)
```

#### Go

```go
import (
    paid "github.com/paid-ai/paid-go"
    paidclient "github.com/paid-ai/paid-go/client"
    "github.com/paid-ai/paid-go/option"
)

client := paidclient.NewClient(option.WithToken("YOUR_PAID_API_KEY"))

checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    ExternalCustomerID: paid.String(currentUser.ID),
    SuccessURL:         "https://example.com/success?checkout_id={CHECKOUT_ID}",
})
if err != nil {
    return err
}

// Redirect your customer to this URL
http.Redirect(w, r, checkout.URL, http.StatusSeeOther)
```

#### Ruby

```ruby
require "paid"

client = Paid::Client.new(token: "YOUR_PAID_API_KEY")

checkout = client.checkouts.create_checkout(
  products: [{ id: "prod_abc123" }],
  external_customer_id: current_user.id,
  success_url: "https://example.com/success?checkout_id={CHECKOUT_ID}",
)

# Redirect your customer to this URL
redirect_to checkout.url
```

#### Java

```java
import ai.paid.api.PaidApiClient;
import ai.paid.api.resources.checkouts.types.*;

PaidApiClient client = PaidApiClient.builder()
    .token("YOUR_PAID_API_KEY")
    .build();

Checkout checkout = client.checkouts().createCheckout(
    CreateCheckoutRequest.builder()
        .addProducts(CheckoutProductInput.builder()
            .id("prod_abc123")
            .build())
        .externalCustomerId(currentUser.getId())
        .successUrl("https://example.com/success?checkout_id={CHECKOUT_ID}")
        .build()
);

// Redirect your customer to this URL
response.sendRedirect(checkout.getUrl());
```

The `{CHECKOUT_ID}` placeholder in `successUrl` is automatically replaced with the checkout's display ID (e.g. `chk_abc123`), so you can retrieve the result when the customer returns.

## Handling the return

After payment, verify the checkout before provisioning access. Check that the status is `completed` **and** that `externalCustomerId` matches the currently authenticated user. This prevents one user from using another's checkout ID to gain unauthorised access.

#### Node.js

```typescript
const checkoutId = req.query.checkout_id;
const checkout = await client.checkouts.getCheckout({ id: checkoutId });

if (
  checkout.status === "completed" &&
  checkout.externalCustomerId === currentUser.id
) {
  await provisionUserAccess(currentUser.id);
} else {
  showErrorMessage();
}
```

#### Python

```python
checkout_id = request.args.get("checkout_id")
checkout = client.checkouts.get_checkout(checkout_id)

if (
    checkout.status == "completed"
    and checkout.external_customer_id == current_user.id
):
    provision_user_access(current_user.id)
else:
    show_error_message()
```

#### Go

```go
checkoutID := r.URL.Query().Get("checkout_id")
checkout, err := client.Checkouts.GetCheckout(
    ctx,
    &paid.GetCheckoutRequest{ID: checkoutID},
)
if err != nil {
    return err
}

if checkout.Status == paid.CheckoutStatusCompleted &&
    checkout.ExternalCustomerID != nil &&
    *checkout.ExternalCustomerID == currentUser.ID {
    provisionUserAccess(currentUser.ID)
} else {
    showErrorMessage()
}
```

## Identifying the customer

There are three ways that customers become associated with a checkout.

**`externalCustomerId` (recommended for most integrations)**

Pass your own user identifier: a database ID, UUID, or email. On the first checkout for a given ID, Paid creates the customer automatically. On subsequent checkouts with the same ID, Paid resolves to the existing customer, so upgrades and plan changes work seamlessly without any extra setup.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  externalCustomerId: currentUser.id,
  successUrl: "https://example.com/success",
});
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    ExternalCustomerID: paid.String(currentUser.ID),
    SuccessURL:         "https://example.com/success",
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

**`customerId`**

If you've already created a customer in Paid, for example via the [Customers API](/api-reference/api-reference/customers/create-customer) or the dashboard, you can reference them directly by their Paid customer ID (`cus_...`).

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  customerId: "cus_xyz789",
  successUrl: "https://example.com/success",
});
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    CustomerID: paid.String("cus_xyz789"),
    SuccessURL: "https://example.com/success",
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

Only one of `customerId` or `externalCustomerId` may be provided.

**Anonymous**

If you don't have a customer yet, for example on a public pricing page, omit both. The checkout page will collect the customer's name and email, and Paid creates the customer record on completion.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/welcome",
});
```

#### Python

```python
checkout = client.checkouts.create_checkout(
    products=[{"id": "prod_abc123"}],
    success_url="https://example.com/welcome",
)
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL: "https://example.com/welcome",
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

## Multiple products

You can pass several products in a single session. If a product has plan tiers, the customer picks one during checkout. If it doesn't, it's shown directly with its base pricing.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [
    { id: "prod_platform" },
    { id: "prod_addon_storage" },
  ],
  successUrl: "https://example.com/success",
});
```

#### Python

```python
checkout = client.checkouts.create_checkout(
    products=[
        {"id": "prod_platform"},
        {"id": "prod_addon_storage"},
    ],
    success_url="https://example.com/success",
)
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_platform"},
        {ID: "prod_addon_storage"},
    },
    SuccessURL: "https://example.com/success",
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

## Automatic upgrades

If the `externalCustomerId` already has an active order for the product, Paid automatically handles the upgrade with proration. No need for separate upgrade logic. The same API call works for both new purchases and plan changes.

#### Node.js

```typescript
// Works for both new customers and upgrades
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  externalCustomerId: currentUser.id,
  successUrl: "https://example.com/success",
});
```

#### Python

```python
# Works for both new customers and upgrades
checkout = client.checkouts.create_checkout(
    products=[{"id": "prod_abc123"}],
    external_customer_id=current_user.id,
    success_url="https://example.com/success",
)
```

#### Go

```go
// Works for both new customers and upgrades
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    ExternalCustomerID: paid.String(currentUser.ID),
    SuccessURL:         "https://example.com/success",
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

## Expiration

By default, checkout sessions expire after 24 hours. You can override this by setting `expiresAt` to an ISO 8601 timestamp.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/welcome",
  expiresAt: "2026-04-01T00:00:00.000Z",
});
```

#### Python

```python
checkout = client.checkouts.create_checkout(
    products=[{"id": "prod_abc123"}],
    success_url="https://example.com/welcome",
    expires_at="2026-04-01T00:00:00.000Z",
)
```

#### Go

```go
expiresAt := paid.MustParseDateTime("2026-04-01T00:00:00.000Z")

checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL: "https://example.com/welcome",
    ExpiresAt:  &expiresAt,
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

## Metadata

You can attach arbitrary key-value metadata to a checkout session for your own tracking. This data is returned on the checkout object but is never shown to the customer.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/welcome",
  metadata: { campaign: "spring_launch", referrer: "pricing_page" },
});
```

#### Python

```python
checkout = client.checkouts.create_checkout(
    products=[{"id": "prod_abc123"}],
    success_url="https://example.com/welcome",
    metadata={"campaign": "spring_launch", "referrer": "pricing_page"},
)
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL: "https://example.com/welcome",
    Metadata: map[string]interface{}{
        "campaign": "spring_launch",
        "referrer": "pricing_page",
    },
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

## Settings

### Currency

Lock the checkout to a specific currency with `currency`. If omitted, the customer can pay in any currency supported by the selected plan.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/success",
  currency: "GBP", // customer will only be able to pay in GBP
});
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL: "https://example.com/success",
    Currency:   paid.String("GBP"), // customer will only be able to pay in GBP
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

### Single use

By default, checkout sessions are single-use. The link is archived once a session has been started. Set `singleUse: false` for a reusable link, for example on a public pricing page.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/success",
  singleUse: false,
});
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL: "https://example.com/success",
    SingleUse:  paid.Bool(false),
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```

### Collecting customer information

By default, Paid collects the customer's billing address (required for tax calculation). You can also request a phone number.

#### Node.js

```typescript
const checkout = await client.checkouts.createCheckout({
  products: [{ id: "prod_abc123" }],
  successUrl: "https://example.com/success",
  collectAddress: true, // default, required for tax
  collectPhone: true, // optional, off by default
});
```

#### Go

```go
checkout, err := client.Checkouts.CreateCheckout(ctx, &paid.CreateCheckoutRequest{
    Products: []*paid.CheckoutProductInput{
        {ID: "prod_abc123"},
    },
    SuccessURL:     "https://example.com/success",
    CollectAddress: paid.Bool(true), // default, required for tax
    CollectPhone:   paid.Bool(true), // optional, off by default
})
if err != nil {
    return err
}
fmt.Println(checkout.URL)
```