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

# Polar Checkout

> Create a Polar Checkout session for subscription payments

<Info>
  This endpoint wraps the official `@polar-sh/nextjs` Checkout helper. It validates the query string, redirects users to Polar's hosted checkout, and returns them to `/success` when payment completes.
</Info>

* **Endpoint**: `GET /api/checkout`
* **File Location**: `src/app/api/checkout/route.ts`
* **Authentication**: Pricing UI enforces Supabase auth before building the checkout URL

## Query Parameters

<ParamField query="products" type="string" required>
  Comma-separated Polar product IDs. Usually a single ID such as `prod_monthly_...` or `prod_yearly_...`.

  **Source:** Polar Dashboard → Products → Copy product ID (or use `NEXT_PUBLIC_POLAR_PRODUCT_ID_*`).
</ParamField>

<ParamField query="customerEmail" type="string" optional>
  Prefills the email field in Polar Checkout. The pricing component passes the Supabase user's email.
</ParamField>

<ParamField query="metadata" type="string" optional>
  JSON string forwarded to Polar and later surfaced in webhook payloads. Sabo stores `{ "user_id": "uuid" }` so the webhook can link the Polar subscription back to the Supabase user.
</ParamField>

## Example Request

Because the endpoint performs a redirect, you typically build a URL and assign it to `window.location.href`:

```ts theme={null}
const checkoutUrl = new URL('/api/checkout', window.location.origin);
checkoutUrl.searchParams.set('products', planId);
checkoutUrl.searchParams.set('customerEmail', user.email ?? '');
checkoutUrl.searchParams.set('metadata', JSON.stringify({ user_id: user.id }));

window.location.href = checkoutUrl.toString();
```

## Example Response

```http theme={null}
HTTP/1.1 307 Temporary Redirect
Location: https://polar.sh/checkout/checkout_123...
```

<Note>
  Polar returns `307/308` redirects rather than JSON payloads. The browser navigates to the hosted checkout UI automatically.
</Note>

## How It Works

<Steps>
  <Step title="Proxy to Polar">
    `@polar-sh/nextjs` generates a handler that validates query parameters, injects your `POLAR_ACCESS_TOKEN`, and negotiates the redirect with Polar.
  </Step>

  <Step title="Success redirect">
    `successUrl` is set to `${NEXT_PUBLIC_SITE_URL}/success`. After payment, Polar sends the buyer there. The success page is purely informational—the webhook activates the subscription.
  </Step>

  <Step title="Webhook metadata">
    The metadata string travels with the subscription so `/api/webhooks/polar` can read `user_id` and associate the subscription + orders with the right Supabase user.
  </Step>
</Steps>

## Implementation

The default boilerplate uses `"sandbox"` for safety during development:

```ts src/app/api/checkout/route.ts theme={null}
import { Checkout } from "@polar-sh/nextjs";

export const GET = Checkout({
  accessToken: process.env.POLAR_ACCESS_TOKEN!,
  successUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/success`,
  server: "sandbox",
});
```

<Tip>
  **Production-ready version:** To switch dynamically based on environment, update the handler:

  ```ts src/app/api/checkout/route.ts (environment-driven) theme={null}
  import { Checkout } from "@polar-sh/nextjs";

  export const GET = Checkout({
    accessToken: process.env.POLAR_ACCESS_TOKEN!,
    successUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/success`,
    server: process.env.POLAR_SANDBOX === "true" ? "sandbox" : "production",
  });
  ```

  Then set `POLAR_SANDBOX=false` in production so Polar serves live checkout URLs.
</Tip>

## Frontend Integration

The pricing component handles authentication, reads the plan IDs from `src/lib/payments/plans.ts`, and then calls the endpoint:

```tsx src/components/marketing/pricing.tsx (excerpt) theme={null}
const handleCheckout = async (productId: string | null) => {
  if (!productId) return;

  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) {
    sessionStorage.setItem('signin-redirect-to', '/pricing');
    router.push('/sign-in');
    return;
  }

  const checkoutUrl = new URL('/api/checkout', window.location.origin);
  checkoutUrl.searchParams.set('products', productId);
  checkoutUrl.searchParams.set('customerEmail', user.email ?? '');
  checkoutUrl.searchParams.set(
    'metadata',
    JSON.stringify({ user_id: user.id })
  );

  window.location.href = checkoutUrl.toString();
};
```

## Testing

1. Set `POLAR_SANDBOX=true` and ensure ngrok/HTTPS tunneling is running for webhooks.
2. Sign in, visit `/pricing`, and click **Upgrade to Pro**.
3. Complete checkout using `4242 4242 4242 4242`.
4. Confirm `/success` loads and webhook logs show `subscription.created` followed by `order.paid`.

<Warning>
  Do not rely on the success page alone. Always keep `/api/webhooks/polar` configured so database records stay in sync even if the buyer closes the browser early.
</Warning>
