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

# Stripe setup

> Wire up subscriptions and credit packs with one script.

Lavendly ships a one-shot setup script that creates every Stripe
product, price, and webhook endpoint the API needs. Run it once per
environment (sandbox + production) and the billing flow is live.

## What it creates

| Lookup key                 | Kind      | Display          | Price (USD) | Grants        |
| -------------------------- | --------- | ---------------- | ----------: | ------------- |
| `lavendly_starter_monthly` | recurring | Lavendly Starter |   \$29 / mo | Starter plan  |
| `lavendly_creator_monthly` | recurring | Lavendly Creator |   \$89 / mo | Creator plan  |
| `lavendly_studio_monthly`  | recurring | Lavendly Studio  |  \$249 / mo | Studio plan   |
| `lavendly_pack_100`        | one-time  | 100 credits      |        \$12 | +100 credits  |
| `lavendly_pack_500`        | one-time  | 500 credits      |        \$55 | +500 credits  |
| `lavendly_pack_1500`       | one-time  | 1500 credits     |       \$149 | +1500 credits |
| `lavendly_pack_5000`       | one-time  | 5000 credits     |       \$400 | +5000 credits |

Every price uses a stable `lookup_key`, so re-running the script never
creates duplicates, it finds existing prices and skips them.

## One-shot flow

<Steps>
  <Step title="Get a sandbox key">
    Stripe dashboard → <a href="https://dashboard.stripe.com/test/apikeys">test mode → API keys</a>.

    Use the **Secret key** (starts with `sk_test_…`) or a Restricted key
    (`rk_test_…`) with these permissions enabled:

    * Products → Write
    * Prices → Write
    * Webhook endpoints → Write *(only if you pass `--webhook`)*
  </Step>

  <Step title="Drop it into .env.local">
    ```bash theme={null}
    STRIPE_SECRET_KEY=sk_test_…
    ```

    `STRIPE_API_KEY=rk_test_…` is also accepted if that's the var name
    your tooling uses.
  </Step>

  <Step title="Dry run">
    ```bash theme={null}
    set -a; source .env.local; set +a
    npm run stripe:setup
    ```

    Prints exactly what it would create. No API mutation.
  </Step>

  <Step title="Apply and write back the price IDs">
    ```bash theme={null}
    npm run stripe:setup -- --apply --write
    ```

    Creates every product + price (idempotent via `lookup_key`), then
    appends or updates the `STRIPE_PRICE_*` block in `.env.local`.
  </Step>

  <Step title="Register the webhook (optional, recommended)">
    ```bash theme={null}
    npm run stripe:setup -- --apply --write \
      --webhook https://api.yourdomain.com/v1/stripe/webhook
    ```

    Creates the endpoint subscribed to:

    * `checkout.session.completed`
    * `customer.subscription.deleted`
    * `customer.subscription.paused`
    * `invoice.payment_failed`

    The signing secret is printed on first creation, copy it into
    `STRIPE_WEBHOOK_SECRET`. On re-runs it's stored in the dashboard
    (Webhooks → endpoint → Signing secret → click to reveal).
  </Step>

  <Step title="Restart the API to pick up the new env">
    ```bash theme={null}
    docker compose up -d --build lavendly-api
    ```
  </Step>

  <Step title="Verify">
    Open the canvas, click any "Generate" CTA on a workflow that
    requires more credits than you have, and the inline buy-credits
    sheet should pop with three real Stripe Checkout tiles.
  </Step>
</Steps>

## Going to production

The exact same script with a live key:

```bash theme={null}
# Verify .env.local has STRIPE_SECRET_KEY=sk_live_…
node scripts/stripe-setup.mjs --live              # dry run
node scripts/stripe-setup.mjs --live --apply --write \
  --webhook https://api.yourdomain.com/v1/stripe/webhook
```

The `--live` flag is REQUIRED for live keys. Without it the script
refuses to run, even in dry mode. There's no auto-detection, you
explicitly opt in.

## Re-running safely

The script is idempotent. Re-running it:

* Finds each existing product by name → reuses it
* Finds each existing price by `lookup_key` → reuses it
* Finds the existing webhook by URL → updates the subscribed events

If you want to **change** a price (say bumping Starter from $29 to
$39), the script will not touch the existing price (Stripe prices are
immutable once created). Instead:

1. Archive the old price in the Stripe dashboard.
2. Pick a new `lookup_key` (e.g. `lavendly_starter_monthly_v2`) in
   `scripts/stripe-setup.mjs`.
3. Re-run with `--apply`.
4. Update the corresponding `STRIPE_PRICE_*` env var.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Stripe returned 401 Invalid API Key">
    Your key isn't a Stripe secret/restricted key. Check the prefix,
    valid forms are `sk_test_`, `sk_live_`, `rk_test_`, `rk_live_`.
  </Accordion>

  <Accordion title="Stripe returned 403 on /products or /prices">
    Restricted key with insufficient permissions. Edit the key in the
    Stripe dashboard and grant **Write** on Products + Prices (and
    Webhook endpoints if you use `--webhook`).
  </Accordion>

  <Accordion title="Refusing to run against a LIVE Stripe key">
    Pass `--live` explicitly. The script never assumes you want to
    touch your live account.
  </Accordion>
</AccordionGroup>
