Skip to content

Instantly share code, notes, and snippets.

@benschac
Created December 18, 2025 16:29
Show Gist options
  • Select an option

  • Save benschac/574b8c2cf51d06d6b3ee85b791b89745 to your computer and use it in GitHub Desktop.

Select an option

Save benschac/574b8c2cf51d06d6b3ee85b791b89745 to your computer and use it in GitHub Desktop.

RevenueCat + Stripe Integration Plan

Overview

  • Goal: unify Stripe payment intents (web) and RevenueCat (mobile IAP) so subscribers are stored, verified, and fulfilled through the same payment service entry point at packages/api/src/services/payment/stripe.service.ts.
  • Scope: backend service layer, webhook handling, Supabase persistence, env/config wiring. Client changes (Expo/Next) are limited to calling the new API contracts that this plan introduces.
  • Out of scope: pricing strategy, UI redesign, or migrating historical transactions (capture them via a one-time sync instead).

Objectives & Success Criteria

  1. stripe.service.ts exposes a typed createPaymentSheet + confirmPayment flow that tags PaymentIntents with the RevenueCat product/entitlement metadata.
  2. New RevenueCat service + webhook keeps Supabase subscriptions (or an equivalent table) in sync for Apple/Google receipts and Stripe payments.
  3. API returns a normalized Entitlement object that downstream apps can trust regardless of purchase surface.
  4. Observability: structured logs + metrics for payment creation, webhook processing, entitlement grants/revocations, and error bubbling through tRPC.

Current State

  • packages/api/src/services/payment/stripe.service.ts only wires up createPaymentSheet, creates a fresh Stripe customer each call, and never persists payment metadata in Supabase.
  • No shared SKU catalog; RevenueCat is not referenced anywhere in the repo.
  • No webhooks or background jobs to reconcile status between Stripe, RevenueCat, and Supabase.

High-Level Deliverables

  • Shared product catalog module (JSON or TS constant) describing SKU id, platform (Stripe, Apple, Google), RevenueCat entitlement id, billing interval, and price.
  • Hardened Stripe service with helpers (getOrCreateCustomer, upsertPaymentRecord, attachEntitlementMetadata) and no non-null assertions.
  • New RevenueCat service + webhook handler to validate signatures, translate events to internal entitlement operations, and persist to Supabase via packages/api/src/services/payment.
  • Supabase schema additions (if missing): subscriptions, subscription_events, and optional payment_methods tables + triggers to keep audit log.
  • Documented runbooks + testing checklist inside this plan.

Implementation Plan

1. Product & Entitlement Modeling

  1. Inventory paid offerings (support tiers, credits, etc.) with Product + Entitlement IDs in RevenueCat dashboard.
  2. Create packages/api/src/services/payment/catalog.ts exporting a typed array of products (use discriminated unions instead of any).
  3. Update createPaymentSheet caller to pass a productId and compute amount/currency from the catalog instead of trusting client-provided values.
  4. Document mapping (Stripe price id ↔ RevenueCat entitlement id) inside the catalog and keep it as the single source of truth.

2. Environment & Secrets

  1. Add to .env.example and deployment secrets: REVENUECAT_API_KEY, REVENUECAT_WEBHOOK_SECRET, STRIPE_PRICE_<SKU>, and any publishable keys the clients need.
  2. Extend packages/api/src/config/payment.ts (create if missing) to read + validate these variables via Zod; surface typed config to services.
  3. Ensure secrets are loaded in Supabase Edge functions (if used) and CI to keep parity.

3. Stripe Service Enhancements (packages/api/src/services/payment/stripe.service.ts)

  1. Refactor createPaymentSheet to:
    • Reuse an existing Stripe customer (query Supabase by stripe_customer_id; create + persist only when absent).
    • Require a productId, derive amount/currency/description from the catalog, and stash the RevenueCat entitlement id inside metadata.
    • Return strongly typed values; remove ! non-null assertions, using explicit error guards instead.
  2. Add helpers:
    • getOrCreateCustomer({ supabase, stripe, userId })
    • createPaymentIntent({ product, customerId, metadata })
    • upsertPaymentRecord({ supabase, paymentIntent, source: 'stripe' })
  3. Expose a handleStripeWebhook function that validates signatures, persists events, and forwards successful payments to the RevenueCat sync service (step 4).
  4. Write unit tests around failure paths (customer creation, payment intent errors, webhook signature failures) using jest + Stripe mock client.

4. RevenueCat Service & Webhooks

  1. Create packages/api/src/services/payment/revenuecat.service.ts to:
    • Fetch customer info using RevenueCat REST API (with API key and app user id == userId).
    • Normalize webhook payloads (initial purchase, renewal, cancellation, billing issues) into an internal EntitlementEvent type.
    • Call shared entitlement helpers to upsert Supabase records and invalidate profile cache where needed.
  2. Add /api/webhooks/revenuecat (tRPC procedure or Next.js route) that verifies Revenuecat-Signature, deduplicates events, and publishes to a queue (Supabase channel or background job) if processing may be slow.
  3. Ensure the service can reconcile Stripe-originated purchases by looking up stripe_customer_id from metadata stored on PaymentIntents (step 3).

5. Supabase Data Model & Sync Jobs

  1. Create migrations for subscriptions (user_id, source, product_id, entitlement_id, status, period_end, stripe_customer_id, revenuecat_app_user_id) and subscription_events (for auditing).
  2. Add indexes on user_id, entitlement_id, and source+status to support queries.
  3. Consider a scheduled job (Edge Function cron) to reconcile entitlements by calling RevenueCat REST for active users and verifying Stripe subscription status in case webhooks fail.
  4. Run yarn supa generate after migrations to refresh types and update any affected services.

6. API & Client Touchpoints

  1. Add a paymentRouter procedure to fetch the normalized entitlement snapshot for the authenticated user (reads from Supabase, not Stripe/RevenueCat directly).
  2. Update Expo/Next clients to:
    • Request productId from the backend catalog or remote config.
    • Use RevenueCat SDK (native) for in-app purchases and send receipt/app user id to backend for verification.
    • Use Stripe PaymentSheet (web) via the existing createPaymentSheet endpoint, but now provide the productId only.
  3. Handle success/failure states consistently and surface entitlement status in UI once backend confirms.

7. Observability & Safety Nets

  1. Standardize logs (using existing logger utility) with payment_source, product_id, entitlement_id, user_id, and event_type.
  2. Emit metrics (Datadog/PostHog) for webhook success/failure counts, entitlement sync duration, and Stripe/RevenueCat API latency.
  3. Add alerting for stuck subscriptions (no renewal event seen within 2x billing cycle) and webhook signature failures.

Testing Strategy

  • Unit tests: Stripe + RevenueCat services (mock SDK/HTTP), catalog helpers, Supabase upsert logic.
  • Integration tests: Simulate end-to-end purchase via mocked Stripe PaymentIntent + RevenueCat webhook to ensure entitlements flip to active.
  • Manual QA:
    • iOS/Android purchase through RevenueCat sandbox → confirm entitlement in Supabase + UI.
    • Web card purchase → confirm PaymentIntent metadata, webhook, and entitlement sync.
    • Cancellation/refund flows on both providers update status to canceled/paused.

Rollout & Validation

  1. Deploy behind a feature flag (payments_unified_v1). Gate both API responses and client UI to avoid partial rollout.
  2. Run parallel logging (old vs new) for a few beta testers to ensure entitlements stay in sync.
  3. Once metrics show healthy flow for 24h, remove the flag and update documentation (CLAUDE*.md, onboarding guides).
  4. Retrospect after launch: capture lessons in docs/payments.md and schedule follow-up tasks (refund automation, analytics instrumentation) if needed.

Open Questions

  1. Do we already have a Supabase table for subscriptions, or should the migration introduce one?
  2. Are there legacy PayPal or App Store Server API flows that also need to emit RevenueCat entitlements?
  3. Should customer support tooling live in Supabase (SQL scripts) or in an internal admin UI to manually grant entitlements?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment