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

# Stripe

> Wire Stripe as the active payment provider — keys, products, webhook, local testing.

Stripe is Vibestrap's default provider — best DX, deepest docs, lowest fees on
the typical NA/EU stack. The trade-off: you're not the merchant of record, so
you handle VAT and US sales tax yourself (Stripe Tax solves most of it for a
0.5% surcharge).

## Prerequisites

* A Stripe account ([stripe.com](https://stripe.com)) — free, no business
  entity required for test mode.
* The Stripe CLI for local webhook forwarding —
  [docs.stripe.com/stripe-cli](https://docs.stripe.com/stripe-cli).
* A live Postgres database (`pnpm db:push` already run).

## 1. Set the active provider

Open `src/config/site.ts`:

```ts theme={null}
payment: {
  provider: 'stripe' as 'stripe' | 'creem' | 'nowpayments',
  currency: 'usd',
},
```

This is already the default. If you've changed it, set it back to `'stripe'`.

## 2. Create products + prices in Stripe Dashboard

In the Stripe Dashboard go to **Products → Add product**. Create one product
per scene you want to sell. For the Vibestrap scaffold itself you need two
prices on the same product (or two separate products):

| Scene              | Description                             |
| ------------------ | --------------------------------------- |
| Vibestrap promo    | Limited-time price (e.g. \$49 one-time) |
| Vibestrap standard | Regular price (e.g. \$99 one-time)      |

For the demo SaaS the starter ships four tiers in `siteConfig.demoPlans`
that illustrate the common patterns: `lifetime_promo` + `lifetime_standard`
(one-time, reuse the Vibestrap price IDs) and `pro_monthly` + `pro_yearly`
(recurring, need their own Stripe prices). Add more tiers by extending the
array.

Copy each `price_…` ID — you'll paste them into env vars next.

## 3. Set env vars

In `.env.local`, fill in (names match `src/env.ts` exactly):

```bash theme={null}
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...        # see step 5
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

# Vibestrap product (the scaffold itself)
STRIPE_PRICE_VIBESTRAP_PROMO=price_...
STRIPE_PRICE_VIBESTRAP_STANDARD=price_...

# Demo subscription tiers (recurring; one-time tiers reuse the VIBESTRAP_* prices above)
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_YEARLY=price_...
```

Only the prices you actually expose in the UI need to be set; the rest can
stay empty.

## 4. Local webhook testing with Stripe CLI

```bash theme={null}
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
```

The CLI prints a webhook signing secret like `whsec_…`. Paste it into
`STRIPE_WEBHOOK_SECRET` in `.env.local` and restart `pnpm dev`. Now any test
payment you make will deliver an event to your local route, signed with the
correct secret.

To trigger an event without paying through the UI:

```bash theme={null}
stripe trigger checkout.session.completed
```

## 5. Production webhook endpoint

In the Stripe Dashboard: **Developers → Webhooks → Add endpoint**.

* **URL**: `https://your-domain.com/api/webhooks/stripe`
* **Events** (minimum): `checkout.session.completed`, `invoice.paid`,
  `customer.subscription.updated`, `customer.subscription.deleted`.
* After creating, click into the endpoint and copy its **Signing secret**
  (`whsec_…`). Set it as `STRIPE_WEBHOOK_SECRET` in your production env.

## Verify it works

1. Start dev with `stripe listen` running.
2. Open `/pricing`, click "Get Vibestrap" — you should land on Stripe Checkout.
3. Pay with `4242 4242 4242 4242`, any future expiry, any CVC, any ZIP.
4. You should be redirected back to your `successUrl`.
5. Check the database:
   ```sql theme={null}
   select id, provider, scene, status, amount from payment
   order by created_at desc limit 1;
   ```
   You should see `provider='stripe'`, `status='paid'`, your test amount.
6. Re-run `stripe trigger checkout.session.completed` — no duplicate row,
   thanks to the `payment.invoiceId` / `sessionId` idempotency check.

## Common pitfalls

* **Webhook secret mismatch** — the secret printed by `stripe listen` is
  ephemeral (rotates each session). Don't paste it into production. Use the
  one from the Dashboard endpoint.
* **Test vs live confusion** — test-mode `sk_test_*` and live-mode `sk_live_*`
  prices are not interchangeable. Each price ID exists in only one mode.
* **Tax not configured** — if you sell to the EU and don't enable Stripe Tax,
  you'll owe VAT out of pocket. Flip Stripe Tax on, or route those buyers
  through Creem instead.
* **Missing `successUrl` / `cancelUrl`** — `createCheckout` throws if either
  is empty. They must be absolute URLs (`https://…`).
* **Subscriptions without `subscription_data.metadata`** — recurring renewals
  emit `invoice.paid` events whose `metadata` is empty. The Stripe provider
  copies checkout `metadata` into `subscription_data.metadata` so renewals
  carry your `userId` / `scene` forward — don't strip that path.

## Official docs

* Stripe Docs: [stripe.com/docs](https://stripe.com/docs)
* Stripe CLI: [docs.stripe.com/cli](https://docs.stripe.com/cli)
* Webhooks reference: [docs.stripe.com/webhooks](https://docs.stripe.com/webhooks)
* Test cards: [docs.stripe.com/testing](https://docs.stripe.com/testing)
* Stripe Tax: [stripe.com/tax](https://stripe.com/tax)
