Use RevenueCat for Capacitor IAP (Play + App Store); keep Stripe on web #100

Closed
opened 2026-08-03 12:17:17 -07:00 by westfarn · 0 comments
Owner

Goal

Sell subscriptions via store IAP on native Capacitor builds (Google Play + Apple App Store) through RevenueCat, and keep Stripe Checkout / Customer Portal for browser (non-native) users.

One entitlement model in the app: subscription API remains source of truth for gates (RAG, quotas, upgrade CTAs). Payment provider switches by platform.

Billing history: RevenueCat webhooks must upsert local Invoice/Payment rows (same as Stripe), so Account → Billing history shows store purchases with provider/store labels — not Stripe-only empty history.

Backend scope (paired chat_backend PR)

New monetization Django app

Rename/move the existing finance app → monetization (correct spelling). This is the home for all paid-access logic:

monetization/
  services/
    stripe.py          # Checkout + Customer Portal
    revenuecat.py      # RC webhook dispatch + ledger upserts
    plans.py
    quotas.py
  views / urls         # checkout, portal, invoices, payments, plans, subscription, webhooks
  • Preserve existing finance_* DB tables via Meta.db_table + SeparateDatabaseAndState (no data loss).
  • Keep URL alias /api/finance/ for compatibility; also mount /api/monetization/.
  • Extend UserSubscription.Source with revenuecat; Invoice/Payment Provider with revenuecat.
  • Add RevenueCat webhook endpoint (auth via shared secret) that activates/renews/cancels plans and writes Invoice/Payment for billing history.
  • Map RC product_idSubscriptionPlan (field or settings map).

Frontend (this repo)

Platform split via Capacitor.isNativePlatform():

  • Native → @revenuecat/purchases-capacitor purchase/restore
  • Web → existing Stripe checkout/portal

Billing history UI must render RevenueCat/store rows (provider badge; no Stripe hosted URL required).

Context

  • Capacitor Android + iOS ship (ai.hesychia.chat).
  • Web billing: BillingSection → Stripe checkout/portal.
  • Apple/Google require IAP inside the app; Stripe stays for browser only.

Approach

Platform split

import { Capacitor } from '@capacitor/core';
const useNativeIap = Capacitor.isNativePlatform();

Do not open Stripe Checkout from the native shell for paid subscriptions.

Client

  1. Add @revenuecat/purchases-capacitor.
  2. Init + logIn(appUserId) after auth (user pk string); logOut on sign-out.
  3. Branch BillingSection / SignUp / AuthCallback checkout CTAs.
  4. Restore purchases; hide Stripe portal for store subscribers.
  5. History table: show RC invoices/payments from API.

Ops

  • Play + App Store products; RevenueCat project, entitlements, offerings, webhook → backend.

Acceptance

  • monetization app owns Stripe + RevenueCat services/webhooks/APIs
  • Native IAP via RevenueCat; web via Stripe
  • RC webhooks update subscription and billing history ledger
  • FE history shows store purchases correctly
  • No Stripe Checkout from Capacitor shell for new paid subs
  • Docs for keys / sandbox

References

## Goal Sell subscriptions via **store IAP** on native Capacitor builds (Google Play + Apple App Store) through **RevenueCat**, and keep **Stripe Checkout / Customer Portal** for browser (non-native) users. One entitlement model in the app: subscription API remains source of truth for gates (RAG, quotas, upgrade CTAs). Payment provider switches by platform. **Billing history:** RevenueCat webhooks must upsert local Invoice/Payment rows (same as Stripe), so Account → Billing history shows store purchases with provider/store labels — not Stripe-only empty history. ## Backend scope (paired `chat_backend` PR) ### New `monetization` Django app Rename/move the existing `finance` app → **`monetization`** (correct spelling). This is the home for all paid-access logic: ``` monetization/ services/ stripe.py # Checkout + Customer Portal revenuecat.py # RC webhook dispatch + ledger upserts plans.py quotas.py views / urls # checkout, portal, invoices, payments, plans, subscription, webhooks ``` - Preserve existing `finance_*` DB tables via `Meta.db_table` + `SeparateDatabaseAndState` (no data loss). - Keep URL alias ` /api/finance/` for compatibility; also mount `/api/monetization/`. - Extend `UserSubscription.Source` with `revenuecat`; Invoice/Payment `Provider` with `revenuecat`. - Add RevenueCat webhook endpoint (auth via shared secret) that activates/renews/cancels plans **and** writes Invoice/Payment for billing history. - Map RC `product_id` → `SubscriptionPlan` (field or settings map). ### Frontend (this repo) Platform split via `Capacitor.isNativePlatform()`: - Native → `@revenuecat/purchases-capacitor` purchase/restore - Web → existing Stripe checkout/portal Billing history UI must render RevenueCat/store rows (provider badge; no Stripe hosted URL required). ## Context - Capacitor Android + iOS ship (`ai.hesychia.chat`). - Web billing: `BillingSection` → Stripe checkout/portal. - Apple/Google require IAP inside the app; Stripe stays for browser only. ## Approach ### Platform split ```ts import { Capacitor } from '@capacitor/core'; const useNativeIap = Capacitor.isNativePlatform(); ``` Do **not** open Stripe Checkout from the native shell for paid subscriptions. ### Client 1. Add `@revenuecat/purchases-capacitor`. 2. Init + `logIn(appUserId)` after auth (user pk string); `logOut` on sign-out. 3. Branch BillingSection / SignUp / AuthCallback checkout CTAs. 4. Restore purchases; hide Stripe portal for store subscribers. 5. History table: show RC invoices/payments from API. ### Ops - Play + App Store products; RevenueCat project, entitlements, offerings, webhook → backend. ## Acceptance - [ ] `monetization` app owns Stripe + RevenueCat services/webhooks/APIs - [ ] Native IAP via RevenueCat; web via Stripe - [ ] RC webhooks update subscription **and** billing history ledger - [ ] FE history shows store purchases correctly - [ ] No Stripe Checkout from Capacitor shell for new paid subs - [ ] Docs for keys / sandbox ## References - https://www.revenuecat.com/docs/getting-started/installation/capacitor - https://www.revenuecat.com/docs/integrations/webhooks - Related: #20, #21, #81
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ai_ml_operations/chat_web_app#100