How do Dattavani payments work, and where do they break?

Answer

Every payment, from Stripe or the App Store and Play via RevenueCat, ends as one row per parent in user_subscriptions. Access is computed from that row at read time. Most money-losing bugs are places where a webhook is acknowledged with 200 but nothing is written.

  1. Two rails feed one row: Stripe for US parents on iOS, RevenueCat for everyone else and all Android.1
  2. The RevenueCat webhook maps app_user_id to parent_profile.id; an anonymous id is acked and dropped.2
  3. user_subscriptions has UNIQUE(parent_id), so every event from either rail overwrites the same row.3
  4. On 18–21 Oct, 20 paid App Store and Play purchases were dropped this way.

Applies toBranch dev-navaratribackend 5fb3968mobile 26f25f13read-only analysis, 27 Oct 2026

1

How does money reach the database?

From tap to premium
Diagram structure
  • Parent App app
  • Billing route rail
  • Store or Stripe store
  • Webhook router hook
  • Billing services apply
  • user_subscriptions row
  • Membership check gate
  • app → rail · country
  • apply → row · upsert
  • hook → apply · parent found
  • rail → store · pay
  • row → gate · read
  • store → hook · event
  1. Parent App

    The app asks the backend which rail to use, using the store's storefront country.

  2. Billing route

    US on iOS can pick Stripe; everyone else and all Android use RevenueCat.

  3. Webhook router

    Stripe events are signature-checked; RevenueCat events use a Bearer secret.

  4. Billing services

    The parent is resolved, guards run, then the single row is upserted.

  5. Membership check

    Access is derived from status, is_trial and period end on every read.

2

Which page answers which question?

QuestionPage
What are the parts and how do they connect?01-system-overview.html
What happens inside the RevenueCat webhook?02-revenuecat-webhook.html
Which states can a subscription be in?03-subscription-lifecycle.html
Which tables and keys hold payment data?04-payments-data-model.html
How does an App Store or Play purchase flow?05-iap-purchase-sequence.html
How does Stripe checkout flow?06-stripe-checkout-sequence.html
What happened on 18–21 Oct?07-incident-timeline.html
Every bug with file and line../../payment-bug-analysis-2026-10-27.md

All pages sit in this folder. Each diagram node links to its pinned source lines.

3

Where can a paid event be lost?

BugRailEffect
Anonymous app_user_id acked as ghost parentRevenueCatP0Payer gets nothing
Claim written before apply; failed release blocks retriesRevenueCatP0Event lost after a DB error
Plan lookup ignores retired plansRevenueCatP0Renewals skipped, access lapses
No transaction or timestamp check on eventsRevenueCatP0Old event expires a newer subscription
Active RevenueCat check ignores period endStripeP0Stripe bills, nothing stored
Restore runs while RevenueCat is anonymousAppP0Receipt moves to an anonymous id
Config error cached as no parent for 5 minutesAppP1Purchases go anonymous
subscription.updated applied without re-fetchStripeP1Late event rolls a paid row back

The webhook claims the event before applying it,4 the active check reads status only,5 and plan lookups filter is_active.6

4

What should be fixed first?

Opening diagram…