Skip to main content
How billing works across the kit, how you turn it on with env only (no code changes), and how to test it with no RevenueCat account at all.

Architecture — the server is the source of truth

Two layers, one identity (app_user_id = the account’s immutable user.id, the same id push uses):
  • On device — the RevenueCat SDK makes purchases and gives an instant, offline copy of the user’s entitlements (fast UI gating).
  • On the server — RevenueCat posts a webhook for every purchase/renewal/cancellation. The API stores the result in the entitlements table. This is what server-enforced features trust; the device’s copy is only ever a cache of it.
Everything is off until configured, exactly like OneSignal/Sentry: no keys ⇒ the webhook 401s, the paywall hides, requireEntitlement denies. The kit builds and runs unchanged.
Subscriptions and lifetime (non-consumable) unlocks both collapse to one entitlements row per (user, entitlement). “Active” is derived, never stored. There is no consumable/credit ledger — deliberately out of scope.

Activation — env only

Client (shipwide_app, .env) API (shipwide_api, .env)

Dashboard checklist (the part env can’t do)

Store IAP inherently needs dashboard setup. The kit’s promise is no code changes, not “no setup”.
1

RevenueCat

Create a project; add an app per platform you sell on (Apple, Google, Web). Copy each public SDK key → the three VITE_ vars; a secret key → REVENUECAT_SECRET_KEY.
2

Stores

App Store Connect + Google Play Console — create the subscription + one-time products, agreements/banking, a subscription group, license testers.
3

Web billing engine

For the web/Tauri build, connect a RevenueCat web engine: Web Billing (Stripe-backed) or Paddle (a Merchant of Record handling global sales tax/VAT, available where Stripe isn’t). The client (@revenuecat/purchases-js) is the same either way — the engine is a dashboard choice.
4

Entitlement, offering, webhook

Create an entitlement pro and an offering; attach the store products. Set the webhook URL to https://<your-api>/v1/subscriptions/webhook with the Authorization value equal to REVENUECAT_WEBHOOK_AUTH.

Server & client surface

On the client: useRevenueCat() (one wrapper, three providers — native / web-Tauri / mock), useSubscriptionStore() (isPro, has(id)), and RevenueCatInitializer.vue (configures on boot, mirrors auth). Reference UI: Subscription.vue (paywall + manage) and PremiumExample.vue.

Mock mode — develop with no account

Set VITE_REVENUECAT_MOCK=true (also implicit in dev with no key). The mock provider serves fake offerings and simulates purchase/restore, so the entire paywall → unlock → manage flow works locally before any store/RevenueCat account exists. Nothing reaches a real store.

Compliance

Apple and Google require digital subscriptions to be sold through their IAP inside their apps. Web billing (Web Billing/Stripe or Paddle) is for the web build only; the iOS/Android builds must not surface a web purchase path — RevenueCat routes per platform automatically. On Capacitor 8 the native SDK is pinned to @revenuecat/purchases-capacitor 13.x.