Skip to main content

Pricing Implementation Plan

Purpose

This plan turns docs/app/product/pricing-strategy.md into an implementation sequence for the app's own SaaS subscription model.

This plan is specifically for:

  • agency -> aegi SaaS subscription
  • org/settings/billing
  • Polar-backed SaaS billing and entitlement enforcement

This plan is not for:

  • agency -> its clients billing through Stripe Connect
  • org/(dashboard)/billing
  • portal invoice/subscription payment flows for client accounts

That split already exists in the repo and must stay explicit.

Current Repo Reality

The current app already has a working SaaS billing baseline for organization subscriptions:

  • Polar is the active provider for org SaaS billing.
  • The app billing surface lives in apps/app/src/app/[locale]/org/(dashboard)/settings/billing/page.tsx.
  • Backend SaaS billing state lives under packages/backend/convex/org/billing.ts and packages/backend/convex/org/billing/*.
  • Plan definitions live in packages/backend/convex/lib/billingPlans.ts.
  • Subscription vocabulary and billing-state logic live in packages/backend/convex/lib/subscriptions.ts.
  • Shared entitlement baselines live in packages/capabilities/src/entitlements.ts.
  • Usage reporting already exposes plan state and entitlement visibility through packages/backend/convex/org/premium.ts and packages/backend/convex/org/premium/summary.ts.

Core Mismatch To Resolve First

The current starter billing model does not match the pricing strategy yet.

Current code assumes:

  • internal basic means the default non-paid baseline
  • paid tiers are pro, business, and enterprise
  • trial defaults are centered on pro
  • entitlement baselines assume the current starter tier ladder

The pricing strategy says:

  • there is no free plan
  • paid plans are Basic, Pro, and Business
  • Enterprise is not a standard self-serve base tier; custom enterprise behavior only appears for storage above +1 TB
  • plan value is defined by seats, storage, and feature differences

That means this is not just a copy change. It is a pricing-model migration.

1. Keep Polar for SaaS billing

Do not reopen provider choice as part of this work.

The repo already uses Polar for org SaaS billing and Stripe for agency-operated client billing. Keep that split.

2. Separate "no active subscription" from the paid Basic plan

Recommended internal model:

  • free or inactive = no paid subscription / fallback account state
  • basic = paid Basic plan from the pricing doc
  • pro = paid Pro plan
  • business = paid Business plan

Do not keep using basic to mean both "free baseline" and "paid Basic tier".

This change should be handled as a staged schema/code migration because basic is currently overloaded in validators, entitlement resolution, UI labels, usage summaries, audit logs, tests, and platform summaries.

3. Treat the pricing doc as the single source of truth for commercial packaging

The pricing strategy should define:

  • plan names
  • monthly and annual pricing
  • included seats
  • extra seat pricing rules
  • included storage
  • feature availability
  • trial rules
  • storage add-ons
  • upgrade/downgrade rules

Code should derive from a canonical pricing config instead of scattering these values across UI and business logic.

Canonical Decisions To Lock Before Coding

These need explicit answers before implementation starts. The recommended defaults below match the pricing doc and current repo shape.

A. Provider packaging model

Recommended:

  • one Polar product per base plan per billing interval
  • separate Polar products or add-on line items for storage packs
  • seat overages modeled in app state first, then synced to provider-backed add-on quantities if Polar supports the exact packaging cleanly

B. Seat counting rule

Recommended:

  • count active org staff users toward seats
  • count pending org staff invites toward reserved seats
  • do not count portal users toward staff seats
  • Basic blocks additional staff above included seats
  • Pro and Business allow extra paid seats

C. Downgrade behavior

Recommended:

  • block downgrades if current active staff + pending invites exceed the target tier's included seats plus purchased extra seats
  • block downgrades if current storage exceeds target included storage plus purchased storage add-ons
  • show exact remediation requirements before the user confirms the downgrade

D. Trial behavior

Recommended:

  • new org signup can start a 14-day trial on basic, pro, or business
  • upgrade trials remain one-time per tier per organization
  • no storage add-ons during trials
  • trial storage cap is 2 GB total regardless of trial tier
  • when trial storage exceeds 2 GB, block new uploads at the shared upload boundary

E. Support tier handling

Recommended:

  • store support level as pricing metadata only for now
  • do not build runtime feature gating for support response targets
  • expose it in billing UI and internal ops context only

Implementation Workstreams

Workstream 1: Canonical pricing model

Create one canonical pricing module for the app subscription model.

It should define:

  • plan ids: basic, pro, business
  • internal non-paid state: free or inactive
  • monthly and annual prices
  • included seats
  • extra seat price rules
  • included storage
  • storage add-on catalog
  • feature matrix
  • trial rules
  • self-serve availability

Primary targets:

  • packages/backend/convex/lib/subscriptions.ts
  • packages/backend/convex/lib/billingPlans.ts
  • packages/capabilities/src/entitlements.ts

Result:

  • all UI and billing logic read from one product-definition layer
  • no hardcoded plan matrix drift between docs, backend, and UI

Workstream 2: Subscription-tier migration

This is the riskiest part and should be done first in the backend.

Required changes

  • replace the current overloaded basic meaning with a distinct non-paid state
  • remove standard enterprise from the base self-serve ladder
  • update validators, TypeScript unions, entitlement mapping, plan comparisons, trial defaults, and status labels
  • update any stored records and derived summaries that reference the old tier set

Likely affected areas

  • packages/backend/convex/lib/subscriptions.ts
  • packages/backend/convex/lib/billingPlans.ts
  • packages/backend/convex/schema.ts
  • packages/backend/convex/org/billing/*
  • packages/backend/convex/platform/subscriptions*
  • packages/backend/convex/platform/directory/organizations.ts
  • apps/app/src/app/[locale]/_lib/convex/api/org/billing.ts
  • locale files in apps/app/messages/*.json
  • billing tests under packages/backend/tests/org/billing/*

Migration rule

Use a widen-migrate-narrow rollout:

  1. widen schema and code to understand both old and new tier vocabulary
  2. migrate existing organization subscription fields and dependent records
  3. switch UI and provider sync to the new vocabulary
  4. narrow schema and delete old-tier compatibility paths

Workstream 3: Entitlements and feature gating

Map the pricing doc's feature matrix onto canonical entitlements.

Current gap

Existing entitlements only cover:

  • custom domain
  • API access
  • webhooks
  • audit logs
  • data export
  • seat and portal-member limits

The pricing doc also introduces or sharpens:

  • remove aegi branding
  • full white-label portal
  • multiple internal teams
  • SSO
  • support level
  • storage limits

Add canonical entitlements for runtime-gated product behavior:

  • customDomain
  • removeBranding
  • fullWhiteLabelPortal
  • apiAccess
  • webhooks
  • multipleInternalTeams
  • sso
  • seatsIncluded
  • storageIncludedBytes

Do not add support-response-time enforcement as a runtime entitlement unless a real product workflow needs it.

Enforcement targets

  • portal branding controls
  • custom-domain settings
  • API and webhook surfaces
  • future internal-team management surfaces
  • future SSO setup surfaces
  • staff invite / activation flows
  • upload URL generation and file attach flows

Workstream 4: Seat model and enforcement

The repo already has entitlement vocabulary for seats, but not a complete commercial seat system.

Required capabilities

  • compute included seats from the current plan
  • compute purchased extra seats
  • compute consumed seats from active staff plus pending invites
  • show over/under state in usage and billing UI
  • block invite flows when the org exceeds seat allocation
  • support add/remove seat adjustments for Pro and Business
  • prevent Basic from purchasing seat add-ons

Backend additions

Add explicit organization billing fields or a dedicated add-on record for:

  • purchased extra seat count
  • effective seat limit snapshot
  • last synced provider quantity or add-on linkage

UI additions

Add seat reporting and management to:

  • org/settings/billing
  • org/settings/usage
  • any org staff invite/create surfaces that need preflight enforcement

Workstream 5: Storage metering and add-ons

This is mostly new work and should not be treated as a small follow-up.

Why this is a separate workstream

The product rules already treat storage as shared platform infrastructure across many business-owned file contexts. That means storage enforcement must happen in shared upload infrastructure, not just inside one route.

Required capabilities

  • canonical organization-level storage usage meter in bytes
  • canonical organization-level storage allocation in bytes
  • included storage from plan tier
  • purchased storage add-ons
  • 2 GB trial cap
  • upload-block enforcement when allocation is exceeded
  • self-serve add-on cap at +1 TB
  • custom-contact flow above +1 TB

Enforcement point

Do not enforce storage only in UI.

Enforce it at the shared backend upload boundary so all uploads respect the same limit, including:

  • Drive uploads
  • contract attachments
  • billing attachments
  • project files
  • other file-backed features
  • create an organization storage summary query/mutation layer in backend
  • derive usage from owned file assets / storage metadata
  • store purchased storage add-ons as billing-owned data
  • refuse new upload URL generation when allocation is exhausted
  • surface allocation and overage state in org/settings/usage and billing

Workstream 6: Billing page and checkout UX

Update the org SaaS billing surface to reflect the new commercial model.

Primary route:

  • apps/app/src/app/[locale]/org/(dashboard)/settings/billing/page.tsx

Required UX updates

  • show Basic / Pro / Business plan cards
  • show monthly and annual pricing
  • show current included seats and storage
  • show add-on state for seats and storage
  • show trial eligibility and upgrade-trial eligibility
  • show downgrade blockers before confirm
  • remove standard Enterprise plan references from the UI
  • preserve the current separation from Stripe client billing

Copy rule

All user-facing text changes must go through locale dictionaries in apps/app/messages/*.json or the relevant locale system in app code.

Workstream 7: Provider sync and webhook handling

Once the pricing model changes, provider reconciliation must stay correct.

Required updates

  • map new plan ids to provider products
  • support annual plan products
  • support add-on products or quantities for storage and possibly seats
  • keep app-side entitlement snapshots consistent when provider state changes
  • preserve cancellation, reactivation, plan change, and checkout return behavior

Important rule

Do not let add-ons corrupt the core plan snapshot.

The base subscription tier and add-on quantities must reconcile independently so the app can answer:

  • what base plan is active?
  • how many extra seats are purchased?
  • how much extra storage is purchased?
  • what is the effective limit right now?

Workstream 8: Usage reporting and policy enforcement

Usage is already visible in org/settings/usage, but it currently centers on plan state and feature visibility, not full commercial packaging.

Add to usage reporting

  • included seats
  • purchased extra seats
  • active seat usage
  • included storage
  • purchased storage add-ons
  • current storage usage
  • upload-block state
  • downgrade-block state

Add to enforcement surfaces

  • org invite flows
  • storage-backed upload flows
  • settings surfaces for gated features

Workstream 9: Tests and rollout safety

Test coverage required

Backend:

  • tier migration compatibility
  • trial eligibility and one-time upgrade-trial logic
  • seat allocation math
  • storage allocation math
  • downgrade blockers
  • add-on reconciliation
  • webhook/provider sync with base plan plus add-ons

App:

  • billing page renders correct plan matrix
  • usage page shows seat/storage allocation correctly
  • invite flow blocks when seat limit is reached
  • upload flow blocks when trial or paid storage limit is exceeded

Existing test areas to extend

  • packages/backend/tests/org/billing/*
  • apps/app/tests/org/billing/*
  • apps/app/tests/e2e/billing/*

Phase 1: Pricing model foundation

  • finalize canonical product definition
  • decide internal non-paid state name
  • map feature matrix to entitlements
  • map provider product structure for monthly, annual, and add-ons

Exit condition:

  • all pricing rules are representable in code without ambiguity

Phase 2: Backend tier migration

  • widen schema and unions
  • migrate basic/enterprise assumptions
  • update plan comparison and trial logic
  • update platform and usage summaries

Exit condition:

  • backend can correctly represent the new pricing ladder

Phase 3: Billing UX refresh

  • update org/settings/billing
  • update locale copy
  • update checkout and plan-change flows
  • show seat and storage allocation state

Exit condition:

  • org admins can understand and purchase the new plans in-app

Phase 4: Seat commercialization

  • implement seat accounting
  • enforce invite limits
  • support paid seat expansion for Pro and Business

Exit condition:

  • seat limits are both visible and enforced

Phase 5: Storage commercialization

  • implement storage metering
  • implement add-on purchases
  • enforce upload blocking at the shared backend boundary

Exit condition:

  • storage policy is enforced consistently across all upload surfaces

Phase 6: Hardening

  • expand automated tests
  • run migration rehearsal in non-production
  • verify provider reconciliation and rollback plan

Exit condition:

  • the pricing model is safe to launch without silent entitlement drift
  1. Pricing model decision pass

    • lock internal tier vocabulary
    • lock add-on packaging model
    • lock seat counting rules
    • lock downgrade rules
  2. Canonical pricing config

    • create one source of truth for plans, intervals, add-ons, and feature matrix
  3. Subscription vocabulary migration

    • remove overloaded basic
    • remove standard self-serve enterprise
    • update validators, snapshots, labels, and tests
  4. Entitlement expansion

    • add branding, white-label, SSO, internal-team, and storage entitlements
  5. Billing page refresh

    • update org settings billing UI for Basic / Pro / Business plus monthly/annual
  6. Seat enforcement

    • usage math, billing UI, and invite gating
  7. Storage enforcement

    • org allocation summary, add-ons, and upload-boundary blocking

Non-Goals For This Project

  • migrating org SaaS billing from Polar to Stripe
  • reopening agency-operated client billing architecture
  • rebuilding Stripe-hosted payment-method management for client billing
  • implementing platform-wide enterprise sales CRM workflows

Final Recommendation

Treat this as a structured pricing-platform project, not a copy refresh.

The correct order is:

  1. canonical pricing model
  2. backend tier migration
  3. entitlement update
  4. billing UI refresh
  5. seat commercialization
  6. storage commercialization
  7. test hardening and rollout rehearsal

If we do it in that order, we can keep the existing Polar SaaS billing baseline intact while safely moving the app to the pricing strategy in docs/app/product/pricing-strategy.md.