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

# Polar Checkout

> Create Polar checkout session for DROP subscription

## Endpoint

```
GET /api/polar/checkout
```

## Overview

Creates a Polar checkout session for DROP service subscription. Redirects to Polar checkout page.

## Request

Requires authentication via Bearer token.

### Query Parameters

<ParamField query="plan" type="string" required>
  Plan identifier: `"pro-lite"` or `"pro"`
</ParamField>

<ParamField query="billingCycle" type="string" required>
  Billing cycle: `"monthly"` or `"yearly"`
</ParamField>

## Response

Redirects to Polar checkout page (302 redirect).

## Implementation Details

### Code Reference

```19:103:nullpass_clean/src/app/api/polar/checkout/route.ts theme={null}
export async function GET(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request, { requested: 2 })
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  const searchParams = request.nextUrl.searchParams
  const plan = searchParams.get('plan')
  const billingCycle = searchParams.get('billingCycle')
  
  if (!plan || !billingCycle) {
    return errorResponse('Missing plan or billingCycle', 400, request.headers.get('origin'))
  }

  const productId = PRODUCT_IDS[plan]?.[billingCycle]
  
  if (!productId) {
    return errorResponse('Invalid plan or billingCycle', 400, request.headers.get('origin'))
  }

  const user = await prisma.user.findUnique({
    where: { id: auth.userId },
    select: { id: true, email: true },
  })

  if (!user) {
    return errorResponse('User not found', 404, request.headers.get('origin'))
  }

  const entitlement = await prisma.userServiceEntitlement.findUnique({
    where: {
      userId_service: {
        userId: auth.userId,
        service: 'DROP',
      },
    },
    select: { polarCustomerId: true },
  })

  const metadata = {
    plan,
    billingCycle,
    userId: user.id
  }

  let validCustomerId = null
  if (entitlement?.polarCustomerId) {
    try {
      const response = await fetch(`https://api.polar.sh/v1/customers/${entitlement.polarCustomerId}`, {
        headers: {
          'Authorization': `Bearer ${process.env.POLAR_ACCESS_TOKEN}`,
          'Content-Type': 'application/json'
        }
      })
      if (response.ok) {
        validCustomerId = entitlement.polarCustomerId
      }
    } catch (error) {
    }
  }

  const checkoutParams = new URLSearchParams({
    products: productId,
    metadata: JSON.stringify(metadata),
    customerEmail: user.email,
    ...(validCustomerId && { customerId: validCustomerId })
  })

  const checkoutHandler = Checkout({
    accessToken: process.env.POLAR_ACCESS_TOKEN!,
    successUrl: `${process.env.NEXT_PUBLIC_APP_URL || 'https://nulldrop.xyz'}/settings?tab=premium&success=true`,
    server: (process.env.POLAR_SERVER as "sandbox" | "production") || "production",
  })

  const modifiedRequest = new NextRequest(
    `${request.url.split('?')[0]}?${checkoutParams.toString()}`,
    request
  )

  return checkoutHandler(modifiedRequest)
}
```

## Status Codes

<ResponseField name="302" type="Found">
  Redirect to Polar checkout page
</ResponseField>

<ResponseField name="400" type="Bad Request">
  Missing or invalid plan/billingCycle parameters
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Missing or invalid authentication token
</ResponseField>

<ResponseField name="404" type="Not Found">
  User not found
</ResponseField>

## Example Request

```bash theme={null}
curl -X GET "https://auth.nullpass.xyz/api/polar/checkout?plan=pro&billingCycle=monthly" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

## Available Plans

* **pro-lite**: Pro Lite plan
  * `monthly`: Monthly billing
  * `yearly`: Yearly billing
* **pro**: Pro plan
  * `monthly`: Monthly billing
  * `yearly`: Yearly billing

## Environment Variables

<ResponseField name="DROP_PRO_LITE_MONTHLY" type="string" required>
  Polar product ID for Pro Lite monthly
</ResponseField>

<ResponseField name="DROP_PRO_LITE_YEARLY" type="string" required>
  Polar product ID for Pro Lite yearly
</ResponseField>

<ResponseField name="DROP_PRO_MONTHLY" type="string" required>
  Polar product ID for Pro monthly
</ResponseField>

<ResponseField name="DROP_PRO_YEARLY" type="string" required>
  Polar product ID for Pro yearly
</ResponseField>

<ResponseField name="POLAR_ACCESS_TOKEN" type="string" required>
  Polar API access token
</ResponseField>

<ResponseField name="POLAR_SERVER" type="string" default="production">
  Polar server: `"sandbox"` or `"production"`
</ResponseField>

<ResponseField name="NEXT_PUBLIC_APP_URL" type="string">
  App URL for success redirect (default: `https://nulldrop.xyz`)
</ResponseField>

## Notes

* Existing Polar customer ID is reused if available
* Metadata includes plan, billingCycle, and userId
* Success URL redirects to app settings page
* Only works for DROP service
