Table of Contents
04 — Subscriptions, billing and premium access
Technical reference. For task-based help, see the corresponding user guide.
Documentation status: complete for the current implementation
Reviewed: 2026-07-22
Primary users: families, coaches and the platform owner
Surfaces: mobile WebView, coach admin portal, owner portal, native wrapper and platform API
Purpose
This feature turns store, Stripe, coach-sponsored and manual access records into one trusted premium decision. Families can buy or restore direct access in the phone app or receive access through a coach. Coaches buy an unlimited client-sharing plan on the web. The platform—not the browser or native receipt alone—decides whether premium tools are available.
Activity tracking remains available when premium is inactive. Analysis and other explicitly premium experiences use the backend-resolved permission.
Product plans
| Audience | Choice | Current catalogue price | Purchase system |
|---|---|---|---|
| Family | Monthly | £6.99/month | App Store or Google Play through RevenueCat/native bridge |
| Family | Yearly | £50/year | App Store or Google Play through RevenueCat/native bridge |
| Coach | Monthly unlimited | £15.99/month | Stripe Checkout on the coach portal |
| Coach | Six-month unlimited | £71.99/6 months | Stripe Checkout on the coach portal |
| Coach | Yearly unlimited | £128.99/year | Stripe Checkout on the coach portal |
The family screen advertises a two-week free trial; actual localized price/trial eligibility shown by the store remains authoritative. Coach introductory offers, when returned by the platform, show their claim deadline, initial amount/duration and the selected cadence's automatic renewal terms.
Entry points
| Surface | Entry point | Function |
|---|---|---|
| Family mobile | /family/subscription | Plan selection, purchase/management and restore. |
| Family mobile | /family/access | Current premium source, coach access, expiry/grace and direct plan choices. |
| Family mobile | /analysis | Premium gate with subscription CTA when direct access is unavailable. |
| Coach mobile | /coach/subscription | Read-only status, renewal/cancellation detail and handoff to web billing. |
| Coach admin portal | /subscription | Stripe plan selection, Checkout, recovery, Customer Portal and cancellation. |
| Owner portal | /billing | Revenue detail and cumulative coach/family registration-cohort acquisition by day, month or quarter. |
Family access resolution
The family summary includes one resolved premium result. Common sources are:
directSubscription— verified family App Store/Google Play entitlement;coachInvite— an active coach assignment/support entitlement;platformPractice— practice policy-backed access; andmanualComp— explicitly granted manual access.
The result includes active, source, expiry and a disabledReason. The UI labels active, grace, expired, revoked, billing-inactive and organization-suspended states from that result instead of reconstructing billing rules locally.
Direct family purchase
- The screen requests the wrapper's live RevenueCat products and displays localized prices; catalogue prices are only a fallback display.
- The family selects monthly or yearly and starts the narrow native purchase bridge call using the platform user ID.
- A successful store response is sent to
POST /families/me/subscription/store/verify. - The server queries RevenueCat's active
premiumentitlement (including linked legacy IDs where applicable), updates direct access, and returns a fresh family summary. - Only after server verification succeeds does the WebView finish the native transaction and announce that premium tools are available.
- Cancellation/renewal lifecycle changes are later synchronized by RevenueCat webhooks and by explicit verification/restore.
Restore purchase
- Restore purchase asks RevenueCat for the signed-in family identity.
- If nothing active is found, the UI reports that no subscription was available without treating it as a crash.
- A found purchase follows the same server-verification path as a new purchase.
- Store account management opens the platform-specific external management URL; the app never attempts to cancel an Apple/Google subscription directly.
Native condition: a real family purchase or restore requires the supported phone wrapper and configured RevenueCat products. In an ordinary browser the plans can be displayed, but browser-local fallback data is not a verified store entitlement and cannot unlock server premium access.
Coach-sponsored access and grace
- Accepting an eligible coach invitation creates a
coachInviteentitlement tied to the assignment/support period. - Coach or organization billing becoming inactive disables premium analysis but does not prevent family tracking.
- When ordinary coach support ends or either side disconnects, coach read/edit access ends immediately and family premium enters a seven-day grace period.
- The family access screen shows the support-ended date, grace days remaining and grace end, then offers direct plans.
- Practice policy can preserve sponsored premium for specific platform-owned cases; this does not restore coach visibility to expired/revoked clients.
- An active direct store subscription is independent of coach access. A coach invite triggers the explicit store-conflict acknowledgement described in Feature 03.
Coach web billing
Status and capacity
GET /coach/subscription returns the latest subscription, current-family usage, capacity metadata, latest Checkout attempt and any introductory offer. Current-family usage includes active, paused and not-yet-started support that remains on the current client list. The normal plan uses an effectively unlimited family limit; finite capacity is retained only for deliberate legacy/manual test records.
Subscription statuses are active, trialing, pastDue, canceled and suspended. Provider is stripe or manual.
Start or recover Checkout
- Inactive coaches and coaches moving from manual access can select monthly, six-month or yearly cadence.
POST /coach/subscription/stripe/checkoutcreates a server-authorized Stripe Checkout URL with safe success/cancel return locations.- An incomplete/processing/action-required Checkout attempt is shown with the platform message, amount context and Return to Stripe checkout when a recovery URL is available.
- Stripe webhook verification, not the redirect alone, is the passive source of truth for subscription activation and future lifecycle changes.
Manage and cancel
- Active Stripe subscriptions with a customer ID can open Customer Portal through
POST /coach/subscription/stripe/portal. - Direct cancellation requires a confirmation. The platform/Stripe response normally marks
cancelAtPeriodEndand the UI states the date through which access continues and that no further renewal will occur. - Manual access is labelled explicitly and is not misrepresented as Stripe billing.
- The phone coach screen is status-led and read-only. It shows plan/status, payment or access-until information and opens the coach portal through a short-lived handoff URL. It never embeds Stripe or exposes native store purchase/restore controls for coaches.
Owner acquisition cohorts
GET /admin/billing/summary includes an acquisition object calculated by the trusted API. The browser renders that result and never reads users, entitlements or subscriptions directly from Supabase.
views.dailycontains 30 UTC days,views.monthlycontains 12 UTC months, andviews.quarterlycontains 8 UTC calendar quarters. Each window ends with the interval containinggeneratedAt;sinceis inclusive anduntilis the exclusive start of the next interval.- Every view has
mode=cumulative. EachperiodStartpoint contains the running total from the beginning of its selected window through that period. Counts exposeregistrationOnly,subscriptionsandactiveOrTrialingfor coaches and families. The narrowertrialingcount remains in the response for compatibility with the briefly released trial-only owner chart but is not the dashed series. - The original
cohortBasis=registrationMonth,interval=month,monthStart,window,totalsand 12-pointpointsfields remain non-cumulative for backward compatibility with a cached older owner frontend. - A user with the coach role is included in the coach cohort for
users.created_at; a user with the family role is included in the family cohort. A dual-role user contributes once to each applicable audience. - Coach
subscriptionsmeans the user has at least one production Stripe subscription record. Manual owner-granted access andstripeMode=sandboxare not conversions. - Family
subscriptionsmeans the user has at least one production direct-subscription entitlement. Production trial starts count;storeEnvironment=sandboxdoes not. activeOrTrialingis a subset ofsubscriptions, not a third additive acquisition category. A coach is included when their latest production Stripe record hasstatus=activeorstatus=trialing. A family is included when their latest production direct-subscription entitlement is effectively active; this includes paid active access and a current store trial.- Subscription classification is historical (ever subscribed), not current access. Canceled, expired or past-due production records remain conversions. A later conversion therefore moves the user from the registration-only cumulative line to the subscription cumulative line at their original registration period.
registrationOnlyis the complementary count for registered users without a qualifying production subscription.- Imported users are bucketed by the platform
users.created_atvalue. If an import could not preserve a legacy signup timestamp, the cohort reflects the platform import date.
The chart defaults to coaches and months, offers Coaches/Families and Days/Months/Quarters switches, and uses cumulative Subscription and Registration-only lines plus a dashed green Active-or-trialing subset line. Hovering or focusing a plotted point renders an SVG tooltip with the full UTC period label and all three cumulative values. The selected-audience totals include the cohort conversion percentage for the selected window. It is not a provider cash, invoice or proceeds report.
Premium feature gates
| Capability | Inactive premium behavior |
|---|---|
| Activity logging, histories and timers | Remains available. |
| Family home and baby profiles | Remain available. |
| Analysis and trends | Family sees plan choices/CTA; coach view receives an inactive-access state. |
| Coach invitation creation/redemption | Blocked when the coach subscription or organization is ineligible. |
| Coach family access | Depends on both assignment/support state and organization/billing state, not subscription alone. |
Development builds may explicitly allow inactive-premium analysis for test fixtures. That override is not a production entitlement and must not be used as an authorization rule.
Validation and user-visible states
| State | Expected behavior |
|---|---|
| Loading | Keep purchase/restore/Checkout actions disabled while the authoritative status is fetched. |
| Store products unavailable | Show catalogue choices where possible, but do not claim verified access. |
| Purchase cancelled by user | Return to the plan screen without creating a platform entitlement. |
| Store verification fails | Do not finish/announce access; present a retryable API error. |
| Restore finds nothing | Show a neutral “no active subscription” message. |
| Grace period | Keep premium active and show the exact support/grace dates and direct-subscription CTA. |
| Billing inactive or suspended organization | Keep tracking usable; block premium analysis and coach sharing. |
| Stripe Checkout incomplete | Show recovery status and recovery URL when supplied. |
| Cancellation scheduled | Keep access through the returned period end and label the non-renewing state. |
| Manual coach subscription | Show manual access; do not offer Stripe management as though a customer exists. |
Platform API contract
| Method and path | Role | Authentication |
|---|---|---|
GET /families/me | Return the family summary and resolved premium result. | Signed-in family/authorized subject |
POST /families/me/subscription/store/verify | Verify RevenueCat premium and synchronize direct access. | Signed-in family self-mode |
GET /coach/subscription | Return coach subscription, usage, Checkout attempt and offer state. | Signed-in coach |
POST /coach/subscription/stripe/checkout | Create cadence-specific Stripe Checkout. | Signed-in coach |
POST /coach/subscription/stripe/portal | Create a Stripe Customer Portal session. | Stripe-backed signed-in coach |
POST /coach/subscription/stripe/cancel | Schedule/carry out server-authorized cancellation. | Stripe-backed signed-in coach |
POST /coach/admin-portal-link | Create the mobile-to-web coach portal handoff. | Signed-in coach |
POST /billing/revenuecat/webhooks | Synchronize family store lifecycle. | Verified provider webhook |
POST /billing/stripe/webhooks | Synchronize coach Stripe lifecycle. | Signature-verified provider webhook |
GET /admin/billing/summary | Return owner revenue/access detail and coach/family acquisition cohorts. | Genuine super-admin |
The legacy POST /coach/subscription/store/verify remains for migrated coach store subscriptions. Current coach purchase UX is Stripe web billing only.
Authorization and security rules
- Native/store purchase output is evidence to verify, not an entitlement the client can assert.
- Stripe Checkout/Portal URLs are created server-side for the signed-in coach and safe return paths.
- Webhook signatures are verified before billing state is synchronized.
- Premium access is resolved with assignment, entitlement, organization and subscription state on every trusted request.
- Families cannot use coach billing routes; coaches cannot use native family store purchase controls.
- No service-role key, webhook secret, receipt token or Stripe customer secret is returned in these user-facing screens.
Acceptance checklist
- Family monthly/yearly products use localized store prices when available and only unlock after server verification.
- Restore handles both an active purchase and an empty result clearly.
- Coach-sponsored access follows the assignment/support period and enters the documented grace state after support ends.
- Inactive premium never blocks ordinary activity logging.
- Inactive coaches can select cadence and enter Stripe Checkout; incomplete Checkout can be resumed.
- Active Stripe coaches can open Portal and confirm cancellation; manual access is labelled separately.
- The coach phone screen performs a web handoff and contains no coach store/embedded Stripe purchase controls.
- All premium decisions come from platform responses rather than local billing flags.
- The owner acquisition graph returns ordered cumulative 30-day, 12-month and 8-quarter UTC series and switches between coach and family counts.
- Manual coach access and sandbox coach/family records remain excluded from acquisition subscription conversion.
Source evidence
Mobile web and wrapper
mobile-web/src/routes/FamilyAccessScreen.tsxmobile-web/src/components/FamilySubscriptionPlanGrid.tsxmobile-web/src/routes/CoachSubscriptionScreen.tsxmobile-web/src/routes/AnalysisScreen.tsxmobile-web/src/webview/native-messages.tsmobile-wrapper/src/native/coachSubscriptions.ts
Coach portal
admin-web/src/routes/CoachBillingPanel.tsxadmin-web/src/api/client.ts
Owner portal
owner-web/src/routes/OwnerAcquisitionChart.tsxowner-web/src/routes/OwnerBillingPanel.tsxowner-web/src/api/client.ts
Platform and contracts
platform/apps/api/src/routes/family-routes.tsplatform/apps/api/src/routes/coach-routes.tsplatform/apps/api/src/routes/store-subscription-routes.tsplatform/apps/api/src/services/admin-billing-summary-service.tsplatform/packages/shared/src/admin.tsplatform/packages/shared/src/billing.tsplatform/packages/shared/src/coach.tsplatform/packages/shared/src/access.tsplatform/docs/multi-tenancy/05-development-plan.md
