Billing & Subscription | Cine Power Planner

Billing & Subscription

Stripe-powered subscription management, plan limits, and paywall enforcement.

Overview

The Billing system manages subscription tiers (Free and Pro) via Stripe integration. It enforces four counted free-tier limits — projects, calculator runs, time-tracking projects and templates — and gates cloud sync and collaboration behind Pro. There is no storage quota and no collaborator-slot limit: nothing in app/src defines either. Checkout sessions and the customer portal are handled by Edge Functions, and usage is tracked for paywall enforcement.

Requirements

Functional Requirements

IDRequirementAcceptance CriteriaPrioritySource
REQ-BIL-001The system shall enforce Free tier limits: 2 projects, 5 calculator runs, 2 time-tracking projects, 1 template1, no cloud sync, no collaborationCreate 3rd project → paywall modal; 6th calculator run → paywall; sync toggle → upgrade prompt; share button → upgrade promptMustpaywallLimits.js
REQ-BIL-002The system shall support upgrading to Pro tier via Stripe CheckoutClick Upgrade → redirect to Stripe Checkout; complete payment → return to app with Pro tier activeMuststripe-create-checkout-session
REQ-BIL-003The system shall route "Manage Subscription" to the rail that sold the subscription — the Stripe Customer Portal, or Apple's subscription screen for an IAP purchaseStripe-sold → redirect to Stripe Portal; Apple-sold → itms-apps:// deep link (REQ-IAP-012); cancel/resume/change plan → reflected in appMuststripe-create-portal-session
REQ-BIL-004The system shall respond to Stripe webhook events for subscription lifecycle changesSubscription created/updated/deleted → app reflects current status; no manual refresh neededMuststripe-webhook
REQ-BIL-005The system shall display a paywall modal with feature comparison when limits are hitHit limit → modal shows Free vs Pro features; Upgrade button → Stripe Checkout; Dismiss → stays on FreeMustPaywallModal
REQ-BIL-006The system shall track anonymous usage to attribute history to account on sign-upUse app anonymously → usage tracked; create account → usage history attributed to new accountShouldanonymous_usage_ledger
REQ-BIL-007The system shall display current plan status and usage in SettingsOpen Settings → plan name shown; usage bar (projects used/limit); upgrade/manage buttons visibleMustsettings
REQ-BIL-008The system shall support coupon codes and promotional pricingEnter coupon at checkout → discount applied; invalid code → error message; expired coupon → errorShouldstripe-create-checkout-session

Non-Functional Requirements

IDRequirementMetricPriority
NFR-BIL-001Subscription status shall be cached locally for 24 h so the paywall does not re-query on every checkPerformanceShould

Two latency NFRs (a 50 ms paywall check and 5 s webhook processing) were removed in v0.773.8: nothing measured either, and an unmeasured budget in a requirements table reads as a guarantee. The cache TTL above is real — SUBSCRIPTION_CACHE_TTL_MS in usePaywallModel.js.

Data Requirements

  • Subscription status: the user_entitlements Supabase table, read through subscriptionService.getSubscriptionStatus(userId) and cached client-side. It is not kept in Supabase user metadata — a client that could write its own metadata could grant itself Pro.
  • Usage tracking: anonymous_usage_ledger table for pre-signup tracking, checked by the anonymous-usage-check edge function
  • Edge Functions: stripe-create-checkout-session, stripe-create-portal-session, stripe-webhook, anonymous-usage-check. There is no get-subscription-status function; the client queries user_entitlements directly under RLS.
  • Client-side: @stripe/stripe-js for Checkout redirect

Constraints & Limits

TierProjects1Calculator runsTime-tracking projectsTemplatesCloud SyncCollaboration
Free2521
ProUnlimitedUnlimitedUnlimitedUnlimited

PDF export is not tier-limited. This table carried a "Watermark / Full" column until v0.773.8; there is no free-tier watermark anywhere in the app. Watermarks exist only on invoices, driven by document state (DRAFT, PENDING, WITHDRAWN — see app/src/data/pdf/invoice/applyWatermark.js), and nothing under app/src/data/pdf/ reads the subscription tier at all.

Offline Behavior

  • Subscription status cached locally — paywall enforcement works offline
  • Stripe Checkout and Portal require network (redirect to stripe.com)
  • Webhook events processed server-side (no offline impact)

Dependencies


Plan Comparison

Free Tier

  • Up to 2 projects, 5 calculator runs, 2 time-tracking projects, 1 template1
  • Full offline functionality
  • PDF export — complete, and not watermarked
  • The full accounting suite (see below)
  • No account required

Pro Tier

  • Unlimited projects, calculator runs, time-tracking projects and templates
  • Cloud sync across devices
  • Real-time collaboration
  • Priority support

The accounting suite is NOT a Pro feature. This list claimed it was "Pro-only since v0.441.0 (#256)" until v0.773.8. isAccountingEnabled() in app/src/data/auth/featureAccess.js returns true unconditionally — the suite graduated from its owner-only gate to general availability, and invoices, quotes, expenses, banking and tax filings are available on the Free tier. Nobody should buy Pro to unlock them.

Managing Your Subscription

Upgrading

  1. Hit a limit (or go to Settings → Account → Subscription)
  2. Click Upgrade to Pro
  3. Complete payment via Stripe Checkout
  4. Pro features activate immediately

Managing / Canceling

  1. Go to Settings → Account → Subscription
  2. Click Manage Subscription
  3. Where it takes you depends on which rail sold the subscription:
    • bought through Stripe → the Stripe Customer Portal opens; update payment, cancel, or resume
    • bought through Apple in-app purchase → an itms-apps:// deep link into Apple's Subscriptions screen, because Apple does not let a third party cancel an IAP (REQ-IAP-012)

Tips

  1. Start free: The Free tier is fully functional for small projects
  2. Upgrade when you need sync: Cloud backup is the biggest Pro advantage
  3. Cancel anytime: No long-term commitment, cancel from the Portal
  4. Usage shown in Settings: Monitor your project count before hitting limits

Last Updated: 2026-09-09 Version: 0.790.2

Footnotes

  1. Source of truth for all four free-tier limits is app/src/data/user/paywallLimits.js: FREE_PROJECT_LIMIT = 2, FREE_CALCULATOR_LIMIT = 5, FREE_TIME_TRACKING_LIMIT = 2, FREE_TEMPLATE_LIMIT = 1. usePaywallModel.js imports them (line 26) and exposes them as paywall.free*Limit; it does not define them. Each limit is mirrored server-side — that module's own header lists the migrations and the anonymous-usage-check function that enforce them. 2 3