> ## 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.

# Payments overview

> Start with one Stripe checkout, keep provider switching optional.

Vibestrap's payment system is designed for template users first: configure a
product, pass a `productId`, and let the starter handle checkout, webhooks,
orders, credits, licenses, and the billing portal.

## The quick path

For most products you only touch three places:

1. Create Products + Prices in Stripe.
2. Paste the price IDs into `.env.local`.
3. Adjust plans and packs in `src/config/site.ts`.

The checkout call stays small:

```ts theme={null}
await createCheckoutAction({ productId: 'pro' });
await createCheckoutAction({ productId: 'credits_standard' });
```

For links that should work before login, send buyers to:

```txt theme={null}
/checkout?productId=pro
```

Unauthenticated buyers are redirected to login, then returned to `/checkout`
and sent to the hosted checkout page automatically.

## Config layers

`siteConfig.billing` is the beginner-facing surface:

```ts theme={null}
billing: {
  enabled: true,
  defaultProductId: 'pro',
  successPath: '/settings/billing?status=success',
  cancelPath: '/pricing?status=canceled',
}
```

`siteConfig.demoPlans` defines the four demo SaaS tiers your app sells. `siteConfig.payment` is the advanced provider layer: Stripe is enabled
by default; Creem and NOWPayments can be added when you need alternate rails.

## Provider facade

Provider code still lives behind one contract:

```ts theme={null}
interface PaymentProvider {
  createCheckout(opts): Promise<{ id: string; url: string }>;
  createPortalLink(opts): Promise<{ url: string }>;
}
```

Stripe, Creem, and NOWPayments each normalize webhooks to the same
`NormalizedEvent` shape. The shared handler in
`src/payment/handlers/core.ts` inserts the payment row, grants credits, issues
licenses, and records affiliate commission.

## Idempotency

Provider retries are expected. The payment table has unique indexes on both
`invoiceId` and `sessionId`; the handler inserts with conflict protection and
then grants entitlements idempotently by `paymentId`.

That means replaying a webhook should not create duplicate payments, credits,
licenses, or affiliate commissions.

## Provider notes

| Provider    | Use it for                 | Notes                                                   |
| ----------- | -------------------------- | ------------------------------------------------------- |
| Stripe      | Default SaaS checkout      | Subscriptions, one-time payments, credit packs, portal. |
| Creem       | Alternate card/local rails | Useful when Stripe is not ideal for your market.        |
| NOWPayments | Crypto one-time payments   | No subscriptions; invoice flow only.                    |

## Verify it works

1. Set Stripe keys and price IDs in `.env.local`.
2. Run `pnpm dev`.
3. Open `/checkout?productId=pro`.
4. Complete a test payment.
5. Confirm a row appears in `payment` and the expected credits/license are
   granted.
6. Replay the webhook; the database should stay unchanged.
