# StudyFlow Stripe + Auth + Entitlements Integration Plan

Last updated: 2026-03-19

This file defines how Stripe subscriptions, user authentication, and local entitlement enforcement should work together for StudyFlow.

It builds on:

- [PRODUCT_STRATEGY.md](/home/ovidiu/~devops-center/brainstorming/studyflow-gemini-aistudio/PRODUCT_STRATEGY.md)
- [PRICING_RESEARCH_2026-03-19.md](/home/ovidiu/~devops-center/brainstorming/studyflow-gemini-aistudio/PRICING_RESEARCH_2026-03-19.md)
- [BILLING_AND_ENTITLEMENTS_PLAN.md](/home/ovidiu/~devops-center/brainstorming/studyflow-gemini-aistudio/BILLING_AND_ENTITLEMENTS_PLAN.md)

## Goal

Create a subscription system that is:

- simple for users
- safe for margins
- easy to reason about operationally
- aligned with the authenticated SaaS product shell

The core rule is:

- Stripe is the source of truth for billing status
- the app database is the source of truth for what the user can do right now

## Recommended Stripe Integration Shape

Use:

- Stripe-hosted Checkout for subscription purchase
- Stripe Customer Portal for self-serve billing management
- Stripe webhooks for subscription state synchronization

Do not build a custom billing UI first.

Why:

- faster to ship
- less payment complexity
- fewer edge cases around upgrades, downgrades, payment failures, and cancellation timing

This aligns with Stripe’s subscription guidance and Customer Portal docs.

## Product and Billing Flow

### Public flow

1. User lands on StudyFlow landing page
2. User signs up or signs in
3. User enters the protected app shell in a no-plan state if no active subscription exists
4. User sees plan comparison, upgrade CTAs, and a one-document deterministic trial path
5. User clicks:
   - `Start Basic`
   - `Start Plus`
   - `Start Ultra`
6. App creates Stripe Checkout Session
7. User completes checkout on Stripe
8. User returns to StudyFlow
9. App ensures the Stripe customer/subscription is linked to the local user account
10. Local entitlements are refreshed
11. User gains access to paid document features

### Existing user upgrade flow

1. User clicks upgrade inside app
2. App launches Stripe Checkout or Portal depending on action
3. Stripe processes change
4. Webhook updates local subscription state
5. Entitlements refresh

### Existing user self-serve billing flow

1. User clicks `Manage billing`
2. App creates Stripe Customer Portal session
3. User manages card, invoices, cancelation, or plan change
4. Stripe sends webhooks
5. Local subscription mirror updates
6. Entitlements refresh

## Recommended App Boundary

### `studyflow-auth`

Owns:

- sign-up
- sign-in
- session cookies
- current user
- Stripe customer link on the user profile

### Billing module inside auth/app backend

Owns:

- create Checkout sessions
- create Customer Portal sessions
- receive Stripe webhooks
- map Stripe subscription state to local subscription state
- expose entitlements to the app

### `n8n`

Should not decide plan access.

It should only run workflows after the app/backend confirms the user is entitled to the requested action.

## Stripe Objects To Use

### Products

Create three products:

- `StudyFlow Basic`
- `StudyFlow Plus`
- `StudyFlow Ultra`

### Prices

Create one monthly recurring price for each product:

- `basic_monthly_usd_5`
- `plus_monthly_usd_9`
- `ultra_monthly_usd_12`

Later optional additions:

- yearly prices
- add-on credit packs

### Customer

Each user should have at most one Stripe Customer record linked in the app database.

### Subscription

Each paying user should have one active subscription record mapped locally.

## Recommended Local Identity Mapping

### `users`

Add:

- `stripe_customer_id`

### `subscriptions`

Mirror Stripe subscription state locally.

Fields:

- `user_id`
- `provider = stripe`
- `provider_customer_id`
- `provider_subscription_id`
- `plan_code`
- `status`
- `current_period_start`
- `current_period_end`
- `cancel_at_period_end`
- `latest_invoice_id`
- `last_synced_at`

### `plan_entitlements`

Local mapping of plan -> allowed features and monthly caps.

### `usage_counters`

Per-user per-billing-period usage snapshot.

### `usage_events`

Append-only ledger for usage.

### `usage_reservations`

Short-lived reservation rows for in-flight metered actions.

Use them to:

- prevent duplicate consumption
- protect async workflows
- finalize successful actions into usage events
- release failed or expired attempts cleanly

## Plan Code Mapping

Use stable app-side plan codes:

- `basic`
- `plus`
- `ultra`

Stripe price ids should map into those plan codes.

Never make frontend logic depend directly on Stripe price ids.

## Consumption Model

Stripe is not the source of truth for when usage burns.
Local backend logic is.

Recommended rule:

- entitlement check first
- reservation second
- consume on successful completion only
- release reservation on failure or timeout

This should apply to:

- no-plan deterministic trial
- paid uploads
- grounded chat
- study-pack generation
- infographic generation

## What The User Can Access By Plan

### No active plan

Unlocked:

- sign in
- app shell
- pricing and upgrade flows
- one limited-size deterministic document transform trial
- account settings

Locked:

- additional personal uploads after the trial is used
- grounded chat on user-owned content by default
- study-pack generation
- infographic generation

### Basic

Unlocked:

- document uploads
- deterministic workspace transform
- grounded chat
- dashboard and history

Locked:

- study-pack generation
- deep study mode

### Plus

Unlocked:

- everything in Basic
- standard study-pack generation

Locked:

- deep study mode using the smarter model

### Ultra

Unlocked:

- everything in Plus
- deep study-pack generation
- premium queue priority

## Stripe Checkout Design

Use Stripe-hosted Checkout for subscriptions.

Recommended flow:

1. authenticated user clicks a plan CTA from the app shell or landing page
2. backend verifies current user session
3. backend creates or reuses Stripe customer
4. backend creates Checkout Session in subscription mode
5. backend returns checkout URL
6. frontend redirects to Stripe

Important:

- if the user is already logged in, create Checkout using the existing user identity
- do not create orphan customers without linking them back to the current user
- if the user is not logged in, require auth before creating Checkout so the subscription has a clear app-side owner

## Stripe Customer Portal Design

Use Stripe Customer Portal for:

- payment method updates
- invoice history
- cancelation
- subscription changes if enabled

Recommended backend route:

- `POST /billing/portal-session`

Behavior:

1. verify session
2. load `stripe_customer_id`
3. create portal session
4. return redirect URL

## Webhook Strategy

Use a dedicated webhook endpoint:

- `POST /billing/webhooks/stripe`

Verify Stripe signatures.

Do not trust webhook JSON without signature validation.

## Webhook Events To Handle

Minimum recommended set:

- `checkout.session.completed`
  - useful to correlate purchase success and customer mapping
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.paid`
- `invoice.payment_failed`

Useful if enabled in your account/product setup:

- `entitlements.active_entitlement_summary.updated`

Important Stripe behavior:

- Stripe does not guarantee strict event ordering
- your handler must be idempotent
- if needed, retrieve the latest subscription/customer object from Stripe before writing final local state

## Local Subscription State Rules

### Grant access when

- subscription is active
- or trialing, if you later add trials

### Restrict access when

- subscription is canceled and period ended
- payment failed and your chosen grace policy has expired

### Suggested local statuses

- `active`
- `trialing`
- `past_due`
- `canceled`
- `incomplete`
- `incomplete_expired`
- `unpaid`

Keep raw Stripe status available too.

## Entitlement Refresh Strategy

On any relevant webhook:

1. verify event signature
2. store raw event id for deduplication
3. load current Stripe customer/subscription state if needed
4. update local subscription mirror
5. recompute current entitlements
6. ensure current billing-period usage row exists

## Entitlement Enforcement Path

Before expensive actions:

1. app backend checks session
2. app backend loads entitlements for current user
3. app backend checks remaining allowance for action
4. if allowed:
   - proceed
5. if not allowed:
   - return structured block response

Example actions:

- `document_upload`
- `grounded_chat_message`
- `study_pack_standard`
- `study_pack_deep`

## Recommended Grace Rules

To keep behavior simple:

### Payment failure

Recommended MVP behavior:

- keep access until Stripe/local status clearly leaves active/trialing
- rely on webhook-driven status change
- avoid inventing your own long grace periods initially

### Cancel at period end

Recommended:

- keep access until `current_period_end`
- at renewal boundary, downgrade entitlements automatically

## Upgrade and Downgrade Handling

### Upgrade

Recommended:

- take effect immediately after Stripe confirms new subscription state
- recompute entitlements immediately

### Downgrade

Recommended:

- apply at next billing period by default
- do not delete excess data immediately
- if user exceeds lower-plan allowances:
  - freeze new usage above limit
  - keep read access
  - prompt cleanup or re-upgrade

## Recommended Backend Routes

### Auth / account

- `GET /auth/me`

### Billing

- `POST /billing/checkout-session`
- `POST /billing/portal-session`
- `POST /billing/webhooks/stripe`
- `GET /billing/me`
- `GET /billing/entitlements`

### Usage

- `POST /entitlements/check`
- `POST /usage/record`

## Recommended Response Shapes

### `GET /billing/me`

```json
{
  "plan": "plus",
  "status": "active",
  "currentPeriodEnd": "2026-04-19T00:00:00Z",
  "cancelAtPeriodEnd": false
}
```

### `GET /billing/entitlements`

```json
{
  "plan": "plus",
  "limits": {
    "documents": 40,
    "groundedChatMessages": 600,
    "studyPacks": 15,
    "deepStudyPacks": 0
  },
  "used": {
    "documents": 8,
    "groundedChatMessages": 122,
    "studyPacks": 3,
    "deepStudyPacks": 0
  },
  "remaining": {
    "documents": 32,
    "groundedChatMessages": 478,
    "studyPacks": 12,
    "deepStudyPacks": 0
  },
  "currentPeriodEnd": "2026-04-19T00:00:00Z"
}
```

### blocked action response

```json
{
  "allowed": false,
  "reason": "study_pack_limit_reached",
  "plan": "plus",
  "upgradePlan": "ultra",
  "currentPeriodEnd": "2026-04-19T00:00:00Z"
}
```

## Best MVP Implementation Order

1. sign-up/sign-in/session flow
2. Stripe product + price setup
3. checkout session route
4. webhook endpoint with signature verification
5. local subscription mirror table
6. entitlements endpoint
7. plan-based UI locking
8. usage recording
9. customer portal route

This gets you to a real paid app fastest without overbuilding.

## What To Avoid

Do not start with:

- custom card form
- custom invoice management UI
- billing logic inside n8n
- frontend-only feature gating
- plan logic hardcoded only in React

## Final Recommendation

Use this product flow:

1. user lands on marketing site
2. user signs up or logs in
3. user starts a plan through Stripe Checkout
4. Stripe webhook confirms billing state
5. local app mirrors subscription state
6. app exposes entitlements
7. document/chat/study-pack actions are enforced against those entitlements

That gives StudyFlow a clean commercial foundation without muddying the workflow system.

## Sources

- Stripe subscriptions overview: https://docs.stripe.com/billing/subscriptions/overview
- Stripe webhooks for subscriptions: https://docs.stripe.com/billing/subscriptions/webhooks
- Stripe webhook signatures: https://docs.stripe.com/webhooks/signatures
- Stripe Checkout: https://docs.stripe.com/payments/checkout
- Stripe Customer Portal: https://docs.stripe.com/customer-management/integrate-customer-portal
- Stripe subscription integration design: https://docs.stripe.com/billing/subscriptions/designing-integration
