Cross-Platform Billing Kit
Unified entitlement demo · fictional account

One Pro entitlement across Stripe and App Store billing.

The hard part is not checkout. It is keeping app access correct when web and native billing disagree. Cancel Stripe, refund the native purchase, or replay a stale webhook and watch the server-owned entitlement resolve the conflict.

Stripe web RevenueCat / iOS Supabase/Postgres truth signed webhooks
Entitlement lab
PRO ACTIVE
Current app authorizationPro

Two independent provider grants currently authorize Pro.

STRIPEsub_web_41active
REVENUECAT1000001941active
Run a lifecycle scenario
What just happened

Two rails can grant the same entitlement independently.

The app authorizes Pro because both provider-specific grants are live. Either provider can end without mutating the other.

Event evidence

Provider events change grants. Grants change access.

The demo keeps the provider records separate, then derives the aggregate entitlement exactly where the application needs it.

Authorization boundary
select public.has_entitlement(
  auth.uid(),
  'pro'
);

-- browser never chooses the grant
-- provider order never decides truth
-- stale retries cannot resurrect state
Shipped flow

Two money rails. One boring authorization check.

Each provider keeps its own verification and lifecycle rules. They meet only after normalization, at the server-owned entitlement layer.

Web · Stripe

existing SaaS friendly
01
Authenticated Checkout

Server authors user + entitlement metadata from verified Supabase Auth and an allowlisted price.

02
Signed webhook

Raw-body signature verification; current subscription-item periods; duplicate/stale guards.

03
Provider grant

active, trialing, grace, canceled-active, expired—stored independently from native billing.

iOS · RevenueCat

App Store purchases
01
Stable App User ID

Supabase UUID is the identity seam; purchase and Restore Purchases helpers are explicit.

02
Verified webhook

Authorization + HMAC verification. Ambiguous aliases stop for reconciliation instead of guessing.

03
Provider grant

renewal, cancellation, grace, refund, expiration, and reconcile normalize into the same ledger shape.

Inspect the hard parts

Open the implementation details you care about.

The demo stays compact; the buyer package contains the actual migration, Edge Functions, client helpers, and tests.

Existing Stripe customers
  • Backfill a trusted app-user → Stripe-customer mapping once.
  • New checkout reuses that mapping instead of creating duplicate customers.
  • Legacy webhook events can resolve through the verified mapping when newer metadata is absent.
Webhook ordering
  • Duplicate event IDs are ignored.
  • Older timestamps cannot overwrite newer provider state.
  • Same-time terminal states receive deterministic precedence.
Security / RLS
  • Clients cannot read provider grants or raw billing events.
  • Checkout identity is server-owned.
  • The browser can select a price but cannot choose the entitlement it grants.
Reconciliation boundary
  • RevenueCat transfers / alias ambiguity refuse to guess.
  • Unknown users and unmapped products fail closed.
  • Ambiguous events are queued for operator reconciliation.
Release evidence

What has actually been tested.

Provider-account smoke tests are deliberately separated from local integration evidence so the listing does not overclaim.

PASSNode + Deno gateLint, typecheck, 26 unit tests, Edge checks, secret scan, npm audit.
PASSReal Postgres/RLSMigration, RLS assertions, cross-provider aggregation, ordering guards.
PASSSigned HTTP webhooksValid provider signatures accepted; invalid Stripe/RevenueCat deliveries rejected.
BUYER SETUPMerchant sandboxYour own Stripe + RevenueCat/App Store sandbox must still be exercised before production.
What you receive

The implementation behind the demo.

Migration, Edge Functions, purchase/restore helpers, provider normalizers, RLS tests, webhook tests, setup docs, and commercial source license.

View all developer kits →