# 09 · Payments

https://exo.now/docs/stack/payments

> Stripe for web payments and RevenueCat for in-app purchases, normalized into one commercial record without making Exo a payment processor.



<StackFacts dimension="09 · Payments" platforms="[&#x22;stripe&#x22;, &#x22;revenuecat&#x22;]" status="Ingestion boundary and dashboard built; enabling real data per tenant needs human-approved provider configuration." />

## What it is [#what-it-is]

Payments and subscriptions are normalized into one commercial record and isolated from the
portable product core. Exo gives platform admins one private portfolio view while checkout,
entitlements, and provider-native subscription state stay with the tenant App
([revenue monitoring](../../../docs/revenue-monitoring.md)).

## Platform [#platform]

* **Stripe** is the payment, refund, dispute, and settlement source. It is Merchant of Record only
  for transactions through Stripe Managed Payments.
* **RevenueCat** is the app-store purchase and cross-platform entitlement source.

Both are **optional adapters** with the **App** as authority. A tenant that doesn't monetize
declares `commerce.profile: none` and stays fully conformant
([commerce contract](../../../docs/commerce-contract.md)).

## Boundary and replaceability [#boundary-and-replaceability]

* **Opt-in profiles:** `none`, `web-stripe` (Stripe Checkout and the Customer Portal own the web
  billing experience), `mobile-revenuecat`, and `hybrid`. `web-clerk-stripe` is accepted only in
  legacy schema 1.8 declarations.
* **Default governance for a monetized App:** `governanceModel: exo-managed`,
  `stripePaymentMode: managed-payments`, `merchantOfRecord: stripe`. This is a target policy, not
  evidence that billing is live.
* **What Exo stores:** non-secret provider coordinates, normalized events, source definitions,
  freshness, and reconciliation status. It never stores secrets, raw payment methods, full
  webhook payloads, or direct customer contact data.
* **Webhooks:** one URL per tenant and environment
  (`POST /api/billing/webhooks/{stripe|revenuecat}/:tenant?environment=staging`). HMAC
  verification uses a five-minute replay window, and tenant-scoped event keys make retries
  idempotent.
* **Commercial entitlements are not access grants.** An entitlement answers what a customer
  bought. An Exo Key grant answers whether an operator may access a resource.

<Callout title="Clerk Billing is gone" type="info">
  Clerk and Clerk Billing are removed from EXO
  ([ADR 0012](../../../docs/adr/0012-clerk-removed-convex-auth-everywhere.md)). Web billing is
  Stripe-only: Stripe Checkout and the Customer Portal own plans and subscriptions, and accounts
  key on the Exo ID user.
</Callout>

## Cost notes [#cost-notes]

* Stripe and RevenueCat fees at portfolio volume: **TBD**.
* **Reporting rules that protect the numbers:** currency totals are never converted or summed
  silently. Portfolio totals include only snapshots explicitly marked `include`. A RevenueCat
  cross-platform aggregate is excluded until overlap with Stripe is reconciled. Metrics a source
  cannot establish (such as MRR) show as unknown, never zero. Snapshots older than the freshness
  target (36 hours in the scaffold) are marked stale.

## Status [#status]

* **Built:** schema, secure ingestion boundary, and portfolio dashboard.
* **Pending per tenant:** product and plan selection, scoped RevenueCat V2 keys, webhook
  destinations, runtime secrets, and test events. Production activation is a separate approval.
