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

# Checkout & Purchases

> Sell a product with a checkout URL and read what a customer owns

To sell a product, generate a checkout URL and redirect the customer to it. Hexclave runs the hosted checkout, receives the payment confirmation, and grants the product - you never write a webhook handler.

## Selling a product

The `createCheckoutUrl` method is available on both user and team objects.

> [tab] Client Component
>
> ```typescript title="app/components/purchase-button.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";  // replace `next` with the correct framework SDK package
>
> export default function PurchaseButton({ productId }: { productId: string }) {
>   const user = useUser({ or: 'redirect' });
>
>   const handlePurchase = async () => {
>     const checkoutUrl = await user.createCheckoutUrl({
>       productId,
>       returnUrl: window.location.href,
>     });
>     window.location.href = checkoutUrl;
>   };
>
>   return <button onClick={handlePurchase}>Purchase</button>;
> }
> ```

> [tab] Server Component
>
> ```typescript title="app/purchase/page.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function PurchasePage() {
>   const user = await hexclaveServerApp.getUser({ or: 'throw' });
>
>   const checkoutUrl = await user.createCheckoutUrl({
>     productId: "prod_premium_monthly",
>   });
>
>   return <a href={checkoutUrl}>Upgrade to Premium</a>;
> }
> ```

For team purchases, call `createCheckoutUrl` on the team object instead:

```typescript theme={null}
const team = user.useTeam(teamId);
const checkoutUrl = await team.createCheckoutUrl({ productId });
```

> [!NOTE]
>
> If you're using a non-JS backend (Python, Go, etc.), call the REST API directly: `POST /api/v1/payments/purchases/create-purchase-url` with `customer_type`, `customer_id`, and `product_id`. See the [REST API overview](https://docs.hexclave.com/api/overview.md) for details.

On the **server**, you can also pass an inline product definition instead of `productId`, or create a URL for a [custom customer](https://docs.hexclave.com/guides/apps/payments/customers.md):

```typescript theme={null}
import { hexclaveServerApp } from "@/hexclave/server";

const checkoutUrl = await hexclaveServerApp.createCheckoutUrl({
  customCustomerId: "external-org-123",
  productId: "prod_enterprise",
  returnUrl: "https://example.com/billing",
});
```

## Hosted checkout

`createCheckoutUrl` returns a hosted Hexclave page (`/purchase/{code}`). Price and quantity are chosen **on that page**, not in the URL. Stackable products show a quantity selector.

Checkout URLs expire after **24 hours**. If **Block new purchases** is on in **Payments -> Settings**, creating a URL and completing checkout both fail; existing subscriptions keep renewing.

A \*\*$0 recurring** price activates without collecting a card. One-time $0 prices cannot go through checkout.

[Free trials](https://docs.hexclave.com/guides/apps/payments/products-and-pricing.md#free-trials) collect a card up front and charge it when the trial ends. In **test mode**, checkout grants the product immediately and skips the trial.

## Creating a checkout URL from the dashboard

You don't have to generate URLs in code. **Create checkout** is available from:

* **Payments -> Customers** (user, team, or custom)
* A product's detail page, and product cards in **Payments -> Product Lines**
* The **Users** and **Teams** tables

The URL still expires in 24 hours. Send it to the customer, or open it yourself while testing.

## Checking what a customer owns

After a purchase, you'll want to know what the customer has. Check their product list, or check a specific [item balance](https://docs.hexclave.com/guides/apps/payments/items-and-entitlements.md).

```typescript theme={null}
// Client component (hook - re-renders on changes)
const products = user.useProducts();

// Server component
const products = await user.listProducts();
```

Each product in the list includes:

* `id` - The product ID (or `null` for inline products)
* `displayName` - The product name
* `quantity` - How many the customer owns (relevant for stackable products)
* `subscription` - `null` for one-time products, or an object with `subscriptionId`, `currentPeriodEnd`, `cancelAtPeriodEnd`, and `isCancelable` for subscriptions
* `switchOptions` - Other products in the same product line the customer could switch to

## Related

* [Items & Entitlements](https://docs.hexclave.com/guides/apps/payments/items-and-entitlements.md) - read and consume credits, seats, and quota
* [Subscriptions](https://docs.hexclave.com/guides/apps/payments/subscriptions.md) - manage recurring plans after purchase
* [Refunds](https://docs.hexclave.com/guides/apps/payments/refunds.md) - reverse a purchase from the dashboard
* [Granting Products](https://docs.hexclave.com/guides/apps/payments/granting-products.md) - give access without checkout
