Turning on real billing takes three environment variables and one setting inside Stripe itself.
Nothing here is needed to develop against the app: the Local fake covers that. This is the checklist
for the point where you're ready to charge a real card.

## The three variables

- **`STRIPE_SECRET_KEY`**: start with a test key (`sk_test_...`) while you're integrating, and swap
  to a live key only when you're ready to charge real cards.
- **`STRIPE_WEBHOOK_SECRET`**: from your webhook endpoint at `/webhooks/stripe`. Locally, run
  `stripe listen --forward-to localhost:3000/webhooks/stripe` and use the secret it prints.
- **A price id per plan**, for example `STRIPE_PRICE_PRO`: the id of a recurring Price you create in
  Stripe, either directly in the dashboard or through the Stripe MCP server (see `.mcp.json`).

Secrets resolve `ENV` first, then Rails encrypted credentials (`bin/rails credentials:edit`) as a
fallback. Locally, put them in `.env` (copy `.env.example` as a starting point); in production,
inject them via Kamal secrets. See
[First Deploy Checklist](/docs/deployment/first-deploy-checklist).

## Set your statement descriptor before you charge anyone

By default, a customer's card statement shows your Stripe account's registered business name, not
your product's name. Someone who bought from `yourapp.com` sees an unfamiliar legal entity on their
statement, doesn't recognize it, and disputes the charge. Unrecognized descriptors are a leading
cause of chargebacks, and the risk compounds if one Stripe account serves more than one product.

For subscriptions, the descriptor comes from the Stripe **Product** (max 22 characters, shown in
capitals), with precedence running invoice, then product, then account default. Set it on each
product:

```ruby
Stripe::Product.update("prod_...", statement_descriptor: "YOURAPP.COM")
```

Your domain is usually the clearest choice: it's what the customer remembers paying. A product
created inline from `price_data` cannot carry a descriptor at all, which is the reason to create
real Products and Prices in Stripe and reference their ids from environment variables, rather than
building line items inline at checkout time.

## Webhook events to enable

Your Stripe webhook endpoint needs to receive subscription lifecycle events: created, updated,
canceled, and payment failures, at minimum. Missing one of these means the app's local billing
state can drift from what Stripe actually has on file, since `entitled?` reads that local state
rather than calling Stripe on every request. See
[First Deploy Checklist](/docs/deployment/first-deploy-checklist) for the full list this kit's own
deploy expects.

## Verify

With test-mode keys in place, walk through a full subscribe-and-cancel cycle on the pricing page
and confirm the webhook fires and updates entitlement correctly, before pointing the same setup at
live keys.

## Next

If you want to run a limited-time discount instead of (or alongside) your standard pricing, see
[Launch Pricing](/docs/billing-entitlements/launch-pricing).
