# 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
<template x-for="metric in usageMetrics" :key="metric.name">
    <div class="mb-4">
        <div class="flex justify-between text-sm mb-1">
            <span x-text="metric.name"></span>
            <span x-text="metric.is_unlimited ? metric.current + ' (Unlimited)' : metric.current + ' / ' + metric.limit"></span>
        </div>
        <div class="w-full bg-gray-200 rounded-full h-2">
            <div class="h-2 rounded-full"
                 :class="metric.percentage >= 90 ? 'bg-red-600' : metric.percentage >= 70 ? 'bg-yellow-600' : 'bg-purple-600'"
                 :style="`width: ${Math.min(100, metric.percentage || 0)}%`"></div>
        </div>
    </div>
</template>
```

**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
<template x-for="fl in (subscription?.tier?.feature_limits || [])" :key="fl.feature_code">
    <div class="p-4 bg-gray-50 rounded-lg">
        <p class="text-sm text-gray-500" x-text="fl.feature_code.replace(/_/g, ' ')"></p>
        <p class="text-xl font-bold" x-text="fl.limit_value || 'Unlimited'"></p>
    </div>
</template>
```

### 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 |
|-----------|-------------|-------------|
| `PaymentSystemNotConfiguredException` | 503 | Stripe not configured |
| `TierNotFoundException` | 404 | Invalid tier code |
| `StripePriceNotConfiguredException` | 400 | No Stripe price for tier |
| `NoActiveSubscriptionException` | 400 | Operation requires subscription |
| `SubscriptionNotCancelledException` | 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
