# Subscription & Billing System The platform provides a comprehensive subscription and billing system for managing merchant subscriptions, feature-based usage limits, and payments through Stripe. ## Overview The billing system enables: - **Subscription Tiers**: Database-driven tier definitions with configurable feature limits - **Feature Provider Pattern**: Modules declare features and usage via `FeatureProviderProtocol`, aggregated by `FeatureAggregatorService` - **Dynamic Usage Tracking**: Quantitative features (orders, products, team members) tracked per merchant with dynamic limits from `TierFeatureLimit` - **Binary Feature Gating**: Toggle-based features (analytics, API access, white-label) controlled per tier - **Merchant-Level Billing**: Subscriptions are per merchant+platform, not per store - **Stripe Integration**: Checkout sessions, customer portal, and webhook handling - **Add-ons**: Optional purchasable items (domains, SSL, email packages) - **Capacity Forecasting**: Growth trends and scaling recommendations - **Background Jobs**: Automated subscription lifecycle management ## Architecture ### Key Concepts The billing system uses a **feature provider pattern** where: 1. **`TierFeatureLimit`** replaces hardcoded tier columns (`orders_per_month`, `products_limit`, `team_members`). Each feature limit is a row linking a tier to a feature code with a `limit_value`. 2. **`MerchantFeatureOverride`** provides per-merchant exceptions to tier defaults. 3. **Module feature providers** implement `FeatureProviderProtocol` to supply current usage data. 4. **`FeatureAggregatorService`** collects usage from all providers and combines it with tier limits to produce `FeatureSummary` records. ``` ┌──────────────────────────────────────────────────────────────┐ │ Frontend Page Request │ │ (Store Billing, Admin Subscriptions, Admin Store Detail) │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ FeatureAggregatorService │ │ (app/modules/billing/services/feature_service.py) │ │ │ │ • Collects feature providers from all enabled modules │ │ • Queries TierFeatureLimit for limit values │ │ • Queries MerchantFeatureOverride for per-merchant limits │ │ • Calls provider.get_current_usage() for live counts │ │ • Returns FeatureSummary[] with current/limit/percentage │ └──────────────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ catalog module │ │ orders module │ │ tenancy module │ │ products count │ │ orders count │ │ team members │ └────────────────┘ └────────────────┘ └────────────────┘ ``` ### Database Models All subscription models are in `app/modules/billing/models/`: | Model | Purpose | |-------|---------| | `SubscriptionTier` | Tier definitions with Stripe price IDs and feature codes | | `TierFeatureLimit` | Per-tier feature limits (feature_code + limit_value) | | `MerchantSubscription` | Per-merchant+platform subscription status | | `MerchantFeatureOverride` | Per-merchant feature limit overrides | | `AddOnProduct` | Purchasable add-ons (domains, SSL, email) | | `StoreAddOn` | Add-ons purchased by each store | | `StripeWebhookEvent` | Idempotency tracking for webhooks | | `BillingHistory` | Invoice and payment history | | `CapacitySnapshot` | Daily platform capacity metrics for forecasting | ### Feature Types Features come in two types: | Type | Description | Example | |------|-------------|---------| | **Quantitative** | Has a numeric limit with usage tracking | `max_products` (limit: 200, current: 150) | | **Binary** | Toggle-based, either enabled or disabled | `analytics_dashboard` (enabled/disabled) | ### FeatureSummary Dataclass The core data structure returned by the feature system: ```python @dataclass class FeatureSummary: code: str # e.g., "max_products" name_key: str # i18n key for display name limit: int | None # None = unlimited current: int # Current usage count remaining: int # Remaining before limit percent_used: float # 0.0 to 100.0 feature_type: str # "quantitative" or "binary" scope: str # "tier" or "merchant_override" ``` ### Services | Service | Location | Purpose | |---------|----------|---------| | `FeatureAggregatorService` | `app/modules/billing/services/feature_service.py` | Aggregates usage from module providers, resolves tier limits + overrides | | `BillingService` | `app/modules/billing/services/billing_service.py` | Subscription operations, checkout, portal | | `SubscriptionService` | `app/modules/billing/services/subscription_service.py` | Subscription CRUD, tier lookups | | `AdminSubscriptionService` | `app/modules/billing/services/admin_subscription_service.py` | Admin subscription management | | `StripeService` | `app/modules/billing/services/stripe_service.py` | Core Stripe API operations | | `CapacityForecastService` | `app/modules/billing/services/capacity_forecast_service.py` | Growth trends, projections | ### Background Tasks | Task | Location | Schedule | Purpose | |------|----------|----------|---------| | `reset_period_counters` | `app/modules/billing/tasks/subscription.py` | Daily | Reset order counters at period end | | `check_trial_expirations` | `app/modules/billing/tasks/subscription.py` | Daily | Expire trials without payment method | | `sync_stripe_status` | `app/modules/billing/tasks/subscription.py` | Hourly | Sync status with Stripe | | `cleanup_stale_subscriptions` | `app/modules/billing/tasks/subscription.py` | Weekly | Clean up old cancelled subscriptions | | `capture_capacity_snapshot` | `app/modules/billing/tasks/subscription.py` | Daily | Capture capacity metrics snapshot | ## API Endpoints ### Store Billing API Base: `/api/v1/store/billing` | Endpoint | Method | Purpose | |----------|--------|---------| | `/billing/subscription` | GET | Current subscription status | | `/billing/tiers` | GET | Available tiers for upgrade | | `/billing/usage` | GET | Dynamic usage metrics (from feature providers) | | `/billing/checkout` | POST | Create Stripe checkout session | | `/billing/portal` | POST | Create Stripe customer portal session | | `/billing/invoices` | GET | Invoice history | | `/billing/upcoming-invoice` | GET | Preview next invoice | | `/billing/change-tier` | POST | Upgrade/downgrade tier | | `/billing/addons` | GET | Available add-on products | | `/billing/my-addons` | GET | Store's purchased add-ons | | `/billing/addons/purchase` | POST | Purchase an add-on | | `/billing/addons/{id}` | DELETE | Cancel an add-on | | `/billing/cancel` | POST | Cancel subscription | | `/billing/reactivate` | POST | Reactivate cancelled subscription | The `/billing/usage` endpoint returns `UsageMetric[]`: ```json [ { "name": "Products", "current": 150, "limit": 200, "percentage": 75.0, "is_unlimited": false, "is_at_limit": false, "is_approaching_limit": true } ] ``` ### Admin Subscription API Base: `/api/v1/admin/subscriptions` | Endpoint | Method | Purpose | |----------|--------|---------| | `/tiers` | GET | List all subscription tiers | | `/tiers` | POST | Create a new tier | | `/tiers/{code}` | PATCH | Update a tier | | `/tiers/{code}` | DELETE | Delete a tier | | `/stats` | GET | Subscription statistics | | `/merchants/{id}/platforms/{pid}` | GET | Get merchant subscription | | `/merchants/{id}/platforms/{pid}` | PUT | Update merchant subscription | | `/store/{store_id}` | GET | Convenience: get subscription + usage for a store | ### Admin Feature Management API Base: `/api/v1/admin/subscriptions/features` | Endpoint | Method | Purpose | |----------|--------|---------| | `/catalog` | GET | Feature catalog grouped by category | | `/tiers/{code}/limits` | GET | Get feature limits for a tier | | `/tiers/{code}/limits` | PUT | Upsert feature limits for a tier | | `/merchants/{id}/overrides` | GET | Get merchant feature overrides | | `/merchants/{id}/overrides` | PUT | Upsert merchant feature overrides | The **feature catalog** returns features grouped by category: ```json { "features": { "analytics": [ {"code": "basic_analytics", "name": "Basic Analytics", "feature_type": "binary", "category": "analytics"}, {"code": "analytics_dashboard", "name": "Analytics Dashboard", "feature_type": "binary", "category": "analytics"} ], "limits": [ {"code": "max_products", "name": "Product Limit", "feature_type": "quantitative", "category": "limits"}, {"code": "max_orders_per_month", "name": "Orders per Month", "feature_type": "quantitative", "category": "limits"} ] } } ``` **Tier feature limits** use `TierFeatureLimitEntry[]` format: ```json [ {"feature_code": "max_products", "limit_value": 200, "enabled": true}, {"feature_code": "max_orders_per_month", "limit_value": 100, "enabled": true}, {"feature_code": "analytics_dashboard", "limit_value": null, "enabled": true} ] ``` ### Admin Store Convenience Endpoint `GET /api/v1/admin/subscriptions/store/{store_id}` resolves a store to its merchant and returns subscription + usage in one call: ```json { "subscription": { "tier": "professional", "status": "active", "period_start": "2026-01-01T00:00:00Z", "period_end": "2026-02-01T00:00:00Z" }, "tier": { "code": "professional", "name": "Professional", "price_monthly_cents": 9900 }, "features": [ { "name": "Products", "current": 150, "limit": null, "percentage": 0, "is_unlimited": true, "is_at_limit": false, "is_approaching_limit": false }, { "name": "Orders per Month", "current": 320, "limit": 500, "percentage": 64.0, "is_unlimited": false, "is_at_limit": false, "is_approaching_limit": false } ] } ``` ### Admin Platform Health API Capacity endpoints under `/api/v1/admin/platform-health`: | Endpoint | Method | Purpose | |----------|--------|---------| | `/platform-health/health` | GET | Full platform health report | | `/platform-health/capacity` | GET | Capacity-focused metrics | | `/platform-health/subscription-capacity` | GET | Subscription-based capacity vs usage | | `/platform-health/trends` | GET | Growth trends over time | | `/platform-health/recommendations` | GET | Scaling recommendations | | `/platform-health/snapshot` | POST | Manually capture capacity snapshot | ## Subscription Tiers ### Tier Structure Tiers are stored in the `subscription_tiers` table. Feature limits are stored separately in `tier_feature_limits`: ``` SubscriptionTier (essential) ├── TierFeatureLimit: max_products = 200 ├── TierFeatureLimit: max_orders_per_month = 100 ├── TierFeatureLimit: max_team_members = 1 ├── TierFeatureLimit: basic_support (binary, enabled) └── TierFeatureLimit: basic_analytics (binary, enabled) SubscriptionTier (professional) ├── TierFeatureLimit: max_products = NULL (unlimited) ├── TierFeatureLimit: max_orders_per_month = 500 ├── TierFeatureLimit: max_team_members = 3 ├── TierFeatureLimit: priority_support (binary, enabled) ├── TierFeatureLimit: analytics_dashboard (binary, enabled) └── ... ``` ### Admin Tier Management Administrators manage tiers at `/admin/subscription-tiers`: **Capabilities:** - View all tiers with stats (total, active, public, MRR) - Create/edit tiers with pricing and Stripe IDs - Activate/deactivate tiers - Assign features to tiers via slide-over panel with: - Binary features: checkbox toggles grouped by category - Quantitative features: checkbox + numeric limit input - Select all / Deselect all per category **Feature Panel API Flow:** 1. Open panel: `GET /admin/subscriptions/features/catalog` (all available features) 2. Open panel: `GET /admin/subscriptions/features/tiers/{code}/limits` (current tier limits) 3. Save: `PUT /admin/subscriptions/features/tiers/{code}/limits` (upsert limits) ### Per-Merchant Overrides Admins can override tier limits for individual merchants via the subscription edit modal: 1. Open edit modal for a subscription 2. Fetches feature catalog + current merchant overrides 3. Shows each quantitative feature with override input (or "Tier default" placeholder) 4. Save sends `PUT /admin/subscriptions/features/merchants/{id}/overrides` ## Frontend Pages ### Store Billing Page **Location:** `/store/{store_code}/billing` **Template:** `app/modules/billing/templates/billing/store/billing.html` **JS:** `app/modules/billing/static/store/js/billing.js` The billing page fetches usage metrics dynamically from `GET /store/billing/usage` and renders them with Alpine.js: ```html ``` **Page sections:** 1. **Current Plan**: Tier name, status, next billing date 2. **Usage Meters**: Dynamic usage bars from feature providers 3. **Change Plan**: Tier cards showing `feature_codes` list 4. **Payment Method**: Link to Stripe portal 5. **Invoice History**: Recent invoices with PDF links 6. **Add-ons**: Available and purchased add-ons ### Admin Subscriptions Page **Location:** `/admin/subscriptions` **Template:** `app/modules/billing/templates/billing/admin/subscriptions.html` **JS:** `app/modules/billing/static/admin/js/subscriptions.js` Lists all merchant subscriptions with: - Tier, status, merchant info, period dates - Features count column (from `feature_codes.length`) - Edit modal with dynamic feature override editor ### Admin Subscription Tiers Page **Location:** `/admin/subscription-tiers` **Template:** `app/modules/billing/templates/billing/admin/subscription-tiers.html` **JS:** `app/modules/billing/static/admin/js/subscription-tiers.js` Manages tier definitions with: - Stats cards (total, active, public, MRR) - Tier table (code, name, pricing, features count, status) - Create/edit modal (pricing, Stripe IDs, description, toggles) - Feature assignment slide-over panel (binary toggles + quantitative limit inputs) ### Merchant Subscription Detail Page **Template:** `app/modules/billing/templates/billing/merchant/subscription-detail.html` Shows subscription details with plan limits rendered dynamically from `tier.feature_limits`: ```html ``` ### Admin Store Detail Page **Template:** `app/modules/tenancy/templates/tenancy/admin/store-detail.html` **JS:** `app/modules/tenancy/static/admin/js/store-detail.js` The subscription section uses the convenience endpoint `GET /admin/subscriptions/store/{store_id}` to load subscription + usage metrics in one call, rendering dynamic usage bars. ## Stripe Integration ### Configuration Required environment variables: ```bash STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_TRIAL_DAYS=30 # Optional, default trial period ``` ### Setup Guide #### Step 1: Get API Keys 1. Go to [Stripe Dashboard](https://dashboard.stripe.com/apikeys) 2. Copy your **Publishable key** (`pk_test_...` or `pk_live_...`) 3. Copy your **Secret key** (`sk_test_...` or `sk_live_...`) #### Step 2: Create Webhook Endpoint 1. Go to [Stripe Webhooks](https://dashboard.stripe.com/webhooks) 2. Click **Add endpoint** 3. Enter your endpoint URL: `https://yourdomain.com/api/v1/webhooks/stripe` 4. Select events to listen to: - `checkout.session.completed` - `customer.subscription.created` - `customer.subscription.updated` - `customer.subscription.deleted` - `invoice.paid` - `invoice.payment_failed` 5. Click **Add endpoint** 6. Copy the **Signing secret** (`whsec_...`) - this is your `STRIPE_WEBHOOK_SECRET` #### Step 3: Local Development with Stripe CLI For local testing, use the [Stripe CLI](https://stripe.com/docs/stripe-cli): ```bash # Install Stripe CLI brew install stripe/stripe-cli/stripe # macOS # or download from https://github.com/stripe/stripe-cli/releases # Login to Stripe stripe login # Forward webhooks to your local server stripe listen --forward-to localhost:8000/api/v1/webhooks/stripe # The CLI will display a webhook signing secret (whsec_...) # Use this as STRIPE_WEBHOOK_SECRET for local development ``` #### Step 4: Create Products & Prices in Stripe Create subscription products for each tier: 1. Go to [Stripe Products](https://dashboard.stripe.com/products) 2. Create products for each tier (Essential, Professional, Business, Enterprise) 3. Add monthly and annual prices for each 4. Copy the Price IDs (`price_...`) and update your tier configuration ### Webhook Events The system handles these Stripe events: | Event | Handler | |-------|---------| | `checkout.session.completed` | Activates subscription, links customer | | `customer.subscription.updated` | Updates tier, status, period | | `customer.subscription.deleted` | Marks subscription cancelled | | `invoice.paid` | Records payment in billing history | | `invoice.payment_failed` | Marks past due, increments retry count | ### Webhook Endpoint Webhooks are received at `/api/v1/webhooks/stripe`: ```python # Uses signature verification for security event = stripe_service.construct_event(payload, stripe_signature) handler = StripeWebhookHandler() result = handler.handle_event(db, event) ``` The handler implements **idempotency** via `StripeWebhookEvent` records. Duplicate events (same `event_id`) are skipped. ## Add-ons ### Available Add-ons | Code | Name | Category | Price | |------|------|----------|-------| | `domain` | Custom Domain | domain | €15/year | | `ssl_premium` | Premium SSL | ssl | €49/year | | `email_5` | 5 Email Addresses | email | €5/month | | `email_10` | 10 Email Addresses | email | €9/month | | `email_25` | 25 Email Addresses | email | €19/month | ### Purchase Flow 1. Store selects add-on on billing page 2. For domains: enter domain name, validate availability 3. Create Stripe checkout session with add-on price 4. On webhook success: create `StoreAddOn` record ## Capacity Forecasting ### Subscription-Based Capacity Track theoretical vs actual capacity: ```python capacity = platform_health_service.get_subscription_capacity(db) # Returns: { "total_subscriptions": 150, "tier_distribution": { "essential": 80, "professional": 50, "business": 18, "enterprise": 2 }, "products": { "actual": 125000, "theoretical_limit": 500000, "utilization_percent": 25.0, "headroom": 375000 }, "orders_monthly": { "actual": 45000, "theoretical_limit": 300000, "utilization_percent": 15.0 } } ``` ### Growth Trends Analyze growth over time: ```python trends = capacity_forecast_service.get_growth_trends(db, days=30) # Returns growth rates, daily projections, monthly projections ``` ### Scaling Recommendations ```python recommendations = capacity_forecast_service.get_scaling_recommendations(db) # Returns: [ { "category": "capacity", "severity": "warning", "title": "Product capacity approaching limit", "description": "Currently at 85% of theoretical product capacity", "action": "Consider upgrading store tiers or adding capacity" } ] ``` ### Infrastructure Scaling Reference | Clients | vCPU | RAM | Storage | Database | Monthly Cost | |---------|------|-----|---------|----------|--------------| | 1-50 | 2 | 4GB | 100GB | SQLite | €30 | | 50-100 | 4 | 8GB | 250GB | PostgreSQL | €80 | | 100-300 | 4 | 16GB | 500GB | PostgreSQL | €150 | | 300-500 | 8 | 32GB | 1TB | PostgreSQL + Redis | €350 | | 500-1000 | 16 | 64GB | 2TB | PostgreSQL + Redis | €700 | | 1000+ | 32+ | 128GB+ | 4TB+ | PostgreSQL cluster | €1,500+ | ## Exception Handling Custom exceptions for billing operations (`app/modules/billing/exceptions.py`): | Exception | HTTP Status | Description | |-----------|-------------|-------------| | `PaymentSystemNotConfiguredError` | 503 | Stripe not configured | | `TierNotFoundError` | 404 | Invalid tier code | | `StripePriceNotConfiguredError` | 400 | No Stripe price for tier | | `NoActiveSubscriptionError` | 400 | Operation requires subscription | | `SubscriptionNotCancelledError` | 400 | Cannot reactivate active subscription | ## Testing Unit tests for the billing system: ```bash # Run billing service tests pytest tests/unit/services/test_billing_service.py -v # Run webhook handler tests pytest tests/unit/services/test_stripe_webhook_handler.py -v # Run feature service tests pytest tests/unit/services/test_feature_service.py -v # Run usage service tests pytest tests/unit/services/test_usage_service.py -v ``` ### Test Coverage - `BillingService`: Tier queries, invoices, add-ons - `StripeWebhookHandler`: Event idempotency, checkout completion, status mapping - `FeatureService`: Feature aggregation, tier limits, merchant overrides - `UsageService`: Usage tracking, limit checks ## Security Considerations - Webhook signatures verified before processing - Idempotency keys prevent duplicate event processing - Customer portal links are session-based and expire - Stripe API key stored securely in environment variables - Background tasks run with database session isolation ## Related Documentation - [Feature Gating System](../implementation/feature-gating-system.md) - Feature access control and UI integration - [Metrics Provider Pattern](../architecture/metrics-provider-pattern.md) - Protocol-based metrics from modules - [Capacity Monitoring](../operations/capacity-monitoring.md) - Detailed monitoring guide - [Capacity Planning](../architecture/capacity-planning.md) - Infrastructure sizing - [Stripe Integration](../deployment/stripe-integration.md) - Payment setup - [Subscription Tier Management](../guides/subscription-tier-management.md) - User guide for tier management