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

# Billing & Stripe API

> Internal reference for the Billing API — Stripe checkout, customer portal, webhooks, pricing, proration, usage credits, and plan validation.

> **Internal documentation for developers.** This page covers Stripe integration patterns, webhook handling, checkout flows, and plan-gating logic. Not intended for end users.

***

## Route Map

| Method | Endpoint                              | Handler File                              | Description                                   |
| ------ | ------------------------------------- | ----------------------------------------- | --------------------------------------------- |
| `POST` | `/api/stripe/create-checkout-session` | `stripe/create-checkout-session/route.ts` | Create a Stripe Checkout session              |
| `POST` | `/api/stripe/customer-portal`         | `stripe/customer-portal/route.ts`         | Create a Stripe Customer Portal session       |
| `GET`  | `/api/stripe/get-checkout-session`    | `stripe/get-checkout-session/route.ts`    | Retrieve a checkout session result            |
| `GET`  | `/api/stripe/prices`                  | `stripe/prices/route.ts`                  | List available plan prices                    |
| `POST` | `/api/stripe/proration-preview`       | `stripe/proration-preview/route.ts`       | Preview subscription proration before upgrade |
| `POST` | `/api/stripe/usage-credits`           | `stripe/usage-credits/route.ts`           | Add/manage usage credits                      |
| `POST` | `/api/stripe/validate-downgrade`      | `stripe/validate-downgrade/route.ts`      | Validate if downgrade is safe                 |
| `POST` | `/api/stripe/referral`                | `stripe/referral/route.ts`                | Apply referral discount                       |
| `POST` | `/api/stripe/webhook`                 | `stripe/webhook/route.ts`                 | Receive Stripe webhook events                 |

***

## Stripe Client Setup

The Stripe client is initialized in `@zappway/lib/stripe` (or similar). The API key is loaded from `STRIPE_SECRET_KEY` environment variable.

```ts theme={null}
import { stripe } from '@zappway/lib/stripe';
```

***

## Checkout Flow

```
1. User selects a plan in the UI
   POST /api/stripe/create-checkout-session { priceId, successUrl, cancelUrl }
   → Creates Stripe Checkout Session
   → Returns { url } for redirect

2. User completes payment on Stripe-hosted page
   → Stripe redirects to successUrl with ?session_id=...

3. App fetches session result
   GET /api/stripe/get-checkout-session?session_id=...
   → Validates payment success
   → Returns subscription status

4. Stripe fires webhook
   POST /api/stripe/webhook
   → Updates organization subscription in DB
```

***

## Webhook Handler

The webhook is the **source of truth** for subscription state. All plan changes must go through the webhook, not the checkout session response.

```ts theme={null}
// Webhook signature verification (mandatory)
const event = stripe.webhooks.constructEvent(
  body,
  req.headers.get('stripe-signature'),
  process.env.STRIPE_WEBHOOK_SECRET
);
```

**Key events handled:**

| Stripe Event                    | Action                              |
| ------------------------------- | ----------------------------------- |
| `checkout.session.completed`    | Activate subscription               |
| `customer.subscription.updated` | Update plan features                |
| `customer.subscription.deleted` | Downgrade to free tier              |
| `invoice.payment_failed`        | Flag payment failure, notify user   |
| `invoice.paid`                  | Reset usage counters for new period |

***

## Plan Gating

Plans gate access to features via the session's subscription metadata. The pattern used across the app:

```ts theme={null}
// Example: Personal Assistant plan gate
import { assertPersonalAssistantPlan } from '@zappway/lib/personal-assistant';
assertPersonalAssistantPlan(req.session); // throws 403 if plan doesn't include feature
```

Similar `assertXxxPlan` helpers exist for other premium features. They read from `req.session.organization.subscription` (loaded from DB via NextAuth callbacks).

***

## Proration Preview

Before upgrading, the UI calls proration preview to show the user the exact charge:

```ts theme={null}
// POST /api/stripe/proration-preview
// Body: { newPriceId: string }
// Returns: Stripe InvoicePreviewParams result
```

***

## Usage Credits

The `usage-credits` endpoint manages prepaid credits for usage-based billing (e.g., message volume). Credits are stored in the DB and decremented on each billable event.

***

## Validate Downgrade

Checks if the org's current resource usage is compatible with a lower plan:

```ts theme={null}
// POST /api/stripe/validate-downgrade
// Body: { targetPriceId: string }
// Returns: { canDowngrade: boolean, blockers: string[] }
// blockers example: ['You have 15 active agents, plan limit is 5']
```

***

## Environment Variables Required

| Variable                             | Description                                        |
| ------------------------------------ | -------------------------------------------------- |
| `STRIPE_SECRET_KEY`                  | Stripe secret key (server-side only)               |
| `STRIPE_WEBHOOK_SECRET`              | Webhook endpoint secret for signature verification |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key (client-side)               |

***

## Known Issues / Gotchas

* **Webhook idempotency:** Stripe can deliver the same event multiple times. All webhook handlers must be idempotent — use `stripe_event_id` as a dedup key stored in the DB.
* **Checkout session expiration:** Sessions expire after 24 hours. If the user abandons checkout and returns later, they need a new session.
* **`validate-downgrade`** must check ALL resource types that have plan limits (agents, skills, datastores, team members, etc.). If a new resource type gets a plan limit, this endpoint must be updated.
* **Customer Portal URL** expires after 5 minutes — generate it on-demand, never cache it.
