# Payments

Stripe, Polar and RevenueCat feed one server-verified entitlement model.

## Enable providers deliberately [#enable-providers-deliberately]

All providers start off. Set `BILLING_PROVIDERS` to the required comma-separated names: `stripe`, `polar`, `revenuecat`. Each listed provider must have its complete valid configuration. `BILLING_ENVIRONMENT` distinguishes SANDBOX from PRODUCTION; Stripe key mode must agree.

Stripe and Polar sell on the web. RevenueCat connects store purchases on mobile. If both web providers are enabled, `BILLING_CHECKOUT_PROVIDER` chooses new checkouts; existing customers retain their provider portal. Visibility separately uses `billing.web` and `billing.mobile`.

## Entitlements and webhooks [#entitlements-and-webhooks]

Paid Access belongs to the User, not an Organization. The server's entitlement read model projects enabled-provider rows in the current environment. Device-reported purchases alone grant nothing. RevenueCat reconciliation replaces only its provider rows; Stripe and Polar apply signed, deduplicated events in transactions.

| Provider | Webhook path | Server secrets |
| --- | --- | --- |
| Stripe | `/api/billing/stripe` | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_IDS |
| Polar | `/api/billing/polar` | POLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, POLAR_PRODUCT_IDS |
| RevenueCat | `/api/billing/revenuecat` | REVENUECAT_SECRET_API_KEY, REVENUECAT_WEBHOOK_AUTHORIZATION, REVENUECAT_WEBHOOK_SIGNING_SECRET |

Configure RevenueCat app and entitlement IDs too. Public platform SDK keys belong in the mobile environment; server keys do not. Raw webhook verification precedes payload parsing. A replay is a no-op, and a failed write rolls back its dedupe record so the provider can retry.

## Extend and test [#extend-and-test]

Add plans to the leaf `billing-plans.ts`, map provider product IDs in env, and add name/description translations. Checkout return URLs come from the server, never a client-supplied redirect. Test purchases in sandbox and verify `billing.status`, cancellation and webhook replay before enabling production visibility.

Stripe and Polar currently rely on webhooks plus the grace window; no scheduled pull reconciles their subscriptions. RevenueCat verification has a freshness bound. Native purchases require a development build with the SDK, not just a successful TypeScript check. See [ADR-0004](/docs/adr/0004-payments-providers).


## Key files [#key-files]

- [`packages/api/src/billing/settings.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/billing/settings.ts)
- [`packages/api/src/billing/webhook.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/billing/webhook.ts)
- [`packages/api/src/billing/status.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/billing/status.ts)
- [`packages/api/src/lib/billing-plans.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/lib/billing-plans.ts)
- [`apps/mobile/src/lib/purchases-sdk.ts`](https://github.com/CarlosZiegler/doro/blob/main/apps/mobile/src/lib/purchases-sdk.ts)
- [`docs/billing.md`](https://github.com/CarlosZiegler/doro/blob/main/docs/billing.md)

