# SmoothSchedule Billing & Plans System

This document describes the architecture and capabilities of the billing and subscription management system.

## Overview

The billing system supports:
- **Plans with Versioning** - Grandfathering existing subscribers when prices change
- **Feature-Based Entitlements** - Boolean (on/off) and integer (limits) features
- **Add-On Products** - Purchasable extras that extend plan capabilities
- **Manual Overrides** - Per-tenant entitlement grants for support/promos
- **Immutable Invoices** - Snapshot-based billing records

## Seeding the Catalog

```bash
# Seed/update the catalog (idempotent)
docker compose -f docker-compose.local.yml exec django python manage.py billing_seed_catalog

# Drop existing and reseed (for fresh start)
docker compose -f docker-compose.local.yml exec django python manage.py billing_seed_catalog --drop-existing
```

---

## Current Plan Catalog

### Plans Overview

| Plan | Monthly | Annual | Target User |
|------|---------|--------|-------------|
| **Free** | $0 | $0 | Solo practitioners testing the platform |
| **Starter** | $19 | $190 | Small businesses getting started |
| **Growth** | $59 | $590 | Growing teams needing SMS & integrations |
| **Pro** | $99 | $990 | Established businesses needing API & analytics |
| **Enterprise** | $199 | $1,990 | Multi-location or white-label needs |

Annual pricing = ~10x monthly (2 months free)

### Feature Matrix - Boolean Features

| Feature | Free | Starter | Growth | Pro | Enterprise |
|---------|:----:|:-------:|:------:|:---:|:----------:|
| `email_enabled` | Yes | Yes | Yes | Yes | Yes |
| `online_booking` | Yes | Yes | Yes | Yes | Yes |
| `recurring_appointments` | Yes | Yes | Yes | Yes | Yes |
| `payment_processing` | - | Yes | Yes | Yes | Yes |
| `mobile_app_access` | - | Yes | Yes | Yes | Yes |
| `sms_enabled` | - | - | Yes | Yes | Yes |
| `custom_domain` | - | - | Yes | Yes | Yes |
| `integrations_enabled` | - | - | Yes | Yes | Yes |
| `api_access` | - | - | - | Yes | Yes |
| `masked_calling_enabled` | - | - | - | Yes | Yes |
| `advanced_reporting` | - | - | - | Yes | Yes |
| `team_permissions` | - | - | - | Yes | Yes |
| `audit_logs` | - | - | - | Yes | Yes |
| `custom_branding` | - | - | - | Yes | Yes |
| `white_label` | - | - | - | - | Yes |
| `remove_branding` | - | - | - | - | Yes |
| `multi_location` | - | - | - | - | Yes |
| `priority_support` | - | - | - | - | Yes |
| `dedicated_account_manager` | - | - | - | - | Yes |
| `sla_guarantee` | - | - | - | - | Yes |

### Feature Matrix - Integer Limits

| Limit | Free | Starter | Growth | Pro | Enterprise |
|-------|:----:|:-------:|:------:|:---:|:----------:|
| `max_users` | 1 | 3 | 10 | 25 | 0 (unlimited) |
| `max_resources` | 1 | 5 | 15 | 50 | 0 (unlimited) |
| `max_locations` | 1 | 1 | 3 | 10 | 0 (unlimited) |
| `max_services` | 3 | 10 | 25 | 100 | 0 (unlimited) |
| `max_customers` | 50 | 500 | 2000 | 10000 | 0 (unlimited) |
| `max_appointments_per_month` | 50 | 200 | 1000 | 5000 | 0 (unlimited) |
| `max_sms_per_month` | 0 | 0 | 500 | 2000 | 10000 |
| `max_email_per_month` | 100 | 500 | 2000 | 10000 | 0 (unlimited) |
| `max_storage_mb` | 100 | 500 | 2000 | 10000 | 0 (unlimited) |
| `max_api_calls_per_day` | 0 | 0 | 1000 | 10000 | 0 (unlimited) |

**Note:** `0` = unlimited for integer limits.

### Add-Ons

| Add-On | Price | Effect | Stackable | Eligible Plans |
|--------|-------|--------|:---------:|----------------|
| **SMS Boost** (+5,000 SMS) | $25/mo, $250/yr | +5,000 `max_sms_per_month` | Yes | Growth, Pro, Enterprise |
| **Extra Locations** (+5) | $29/mo, $290/yr | +5 `max_locations` | Yes | Growth, Pro, Enterprise |
| **Advanced Reporting** | $15/mo, $150/yr | Enables `advanced_reporting` | No | Starter, Growth |
| **API Access** | $20/mo, $200/yr | Enables `api_access`, 5K API calls/day | No | Starter, Growth |
| **Masked Calling** | $39/mo, $390/yr | Enables `masked_calling_enabled` | No | Starter, Growth |
| **White Label** | $99/mo, $990/yr | Enables `white_label`, `remove_branding`, `custom_branding` | No | Pro only |

**Stackable add-ons:** Integer values multiply by quantity purchased.

---

## Architecture

```
                    ┌─────────────────┐
                    │     Tenant      │
                    │   (Business)    │
                    └────────┬────────┘
                             │
                             │ has one
                             ▼
                    ┌─────────────────┐
                    │  Subscription   │
                    │                 │
                    │ - status        │
                    │ - trial_ends_at │
                    │ - period dates  │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
              ▼              ▼              ▼
     ┌─────────────┐  ┌───────────┐  ┌──────────────┐
     │ PlanVersion │  │ AddOns    │  │  Invoices    │
     │             │  │ (M2M)     │  │              │
     └──────┬──────┘  └───────────┘  └──────────────┘
            │
            │ belongs to
            ▼
     ┌─────────────┐
     │    Plan     │
     │             │
     │ - code      │
     │ - name      │
     └─────────────┘
```

## Core Models

### Plan

Logical grouping representing a tier (e.g., Free, Starter, Pro, Enterprise).

```python
Plan:
    code: str           # Unique identifier (e.g., "pro")
    name: str           # Display name (e.g., "Pro")
    description: str    # Marketing description
    display_order: int  # Sort order in UI
    is_active: bool     # Available for new signups
```

### PlanVersion

A specific version of a plan with pricing and features. Enables grandfathering.

```python
PlanVersion:
    plan: FK(Plan)
    version: int                        # Auto-incremented per plan
    name: str                           # "Pro Plan v2"

    # Availability
    is_public: bool                     # Visible in catalog
    is_legacy: bool                     # Hidden from new signups (grandfathered)
    starts_at: datetime                 # Optional availability window
    ends_at: datetime

    # Pricing (in cents)
    price_monthly_cents: int            # Monthly subscription price
    price_yearly_cents: int             # Annual subscription price

    # Transaction Fees
    transaction_fee_percent: Decimal    # Platform fee percentage
    transaction_fee_fixed_cents: int    # Fixed fee per transaction

    # Trial
    trial_days: int                     # Free trial duration

    # Communication Pricing (usage-based)
    sms_price_per_message_cents: int
    masked_calling_price_per_minute_cents: int
    proxy_number_monthly_fee_cents: int

    # Credit Auto-Reload Defaults
    default_auto_reload_enabled: bool
    default_auto_reload_threshold_cents: int
    default_auto_reload_amount_cents: int

    # Display Settings
    is_most_popular: bool               # Highlight in pricing page
    show_price: bool                    # Show price or "Contact us"
    marketing_features: list            # Bullet points for pricing page

    # Stripe Integration
    stripe_product_id: str
    stripe_price_id_monthly: str
    stripe_price_id_yearly: str
```

**Grandfathering Flow:**
1. Admin updates a plan version that has active subscribers
2. System automatically:
   - Marks old version as `is_legacy=True, is_public=False`
   - Creates new version with updated pricing/features
3. Existing subscribers keep their current version
4. New subscribers get the new version

### Feature

Single source of truth for all capabilities and limits.

```python
Feature:
    code: str           # Unique identifier (e.g., "sms_enabled")
    name: str           # Display name
    description: str    # What this feature enables
    feature_type: str   # "boolean" or "integer"
```

**Feature Types:**
- **Boolean**: On/off capabilities (e.g., `sms_enabled`, `api_access`)
- **Integer**: Numeric limits (e.g., `max_users`, `max_appointments_per_month`)

### PlanFeature (M2M)

Links features to plan versions with their values.

```python
PlanFeature:
    plan_version: FK(PlanVersion)
    feature: FK(Feature)
    bool_value: bool    # For boolean features
    int_value: int      # For integer features
```

### Subscription

Links a tenant to their plan version.

```python
Subscription:
    business: FK(Tenant)
    plan_version: FK(PlanVersion)
    status: str                     # "active", "trial", "past_due", "canceled", "paused"
    started_at: datetime
    current_period_start: datetime
    current_period_end: datetime
    trial_ends_at: datetime
    canceled_at: datetime
    stripe_subscription_id: str
```

### AddOnProduct

Purchasable extras that extend plan capabilities.

```python
AddOnProduct:
    code: str                       # Unique identifier
    name: str                       # Display name
    description: str
    price_monthly_cents: int        # Recurring price
    price_one_time_cents: int       # One-time purchase price
    is_active: bool
    stripe_product_id: str
    stripe_price_id: str
```

### SubscriptionAddOn (M2M)

Links add-ons to subscriptions.

```python
SubscriptionAddOn:
    subscription: FK(Subscription)
    addon: FK(AddOnProduct)
    status: str                     # "active", "canceled", "expired"
    activated_at: datetime
    expires_at: datetime            # For time-limited add-ons
```

### EntitlementOverride

Manual per-tenant feature grants (support tickets, promos, partnerships).

```python
EntitlementOverride:
    business: FK(Tenant)
    feature: FK(Feature)
    bool_value: bool
    int_value: int
    reason: str                     # "support_ticket", "promo", "partnership", "manual"
    notes: str                      # Admin notes
    granted_by: FK(User)
    expires_at: datetime            # Optional expiration
```

## Entitlement Resolution

The `EntitlementService` resolves effective entitlements with this precedence:

```
Override > Add-on > Plan
```

**Resolution Logic:**
1. Start with plan version features
2. Layer add-on features (add-ons can extend but not reduce)
3. Apply manual overrides (highest priority)

**API:**
```python
from smoothschedule.billing.services.entitlements import EntitlementService

# Get all effective entitlements for a tenant
entitlements = EntitlementService.get_effective_entitlements(tenant)
# Returns: {"sms_enabled": True, "max_users": 25, ...}

# Check a specific boolean feature
can_use_sms = EntitlementService.has_feature(tenant, "sms_enabled")

# Get a specific limit
max_users = EntitlementService.get_limit(tenant, "max_users")
```

## Seeded Features

The system seeds 30 features (20 boolean, 10 integer):

### Boolean Features (Capabilities)
| Code | Description |
|------|-------------|
| `sms_enabled` | Can send SMS notifications |
| `email_enabled` | Can send email notifications |
| `masked_calling_enabled` | Can use masked phone calls |
| `api_access` | Can access REST API |
| `custom_branding` | Can customize branding |
| `remove_branding` | Can remove "Powered by" |
| `custom_domain` | Can use custom domain |
| `multi_location` | Can manage multiple locations |
| `advanced_reporting` | Access to analytics dashboard |
| `priority_support` | Priority support queue |
| `dedicated_account_manager` | Has dedicated AM |
| `sla_guarantee` | SLA commitments |
| `white_label` | Full white-label capabilities |
| `team_permissions` | Granular team permissions |
| `audit_logs` | Access to audit logs |
| `integrations_enabled` | Can use third-party integrations |
| `mobile_app_access` | Field staff mobile app |
| `online_booking` | Customer self-booking |
| `payment_processing` | Accept payments |
| `recurring_appointments` | Recurring bookings |

### Integer Features (Limits)
| Code | Description |
|------|-------------|
| `max_users` | Maximum team members |
| `max_resources` | Maximum resources/equipment |
| `max_locations` | Maximum business locations |
| `max_services` | Maximum service types |
| `max_customers` | Maximum customer records |
| `max_appointments_per_month` | Monthly appointment limit |
| `max_sms_per_month` | Monthly SMS limit |
| `max_email_per_month` | Monthly email limit |
| `max_storage_mb` | File storage limit |
| `max_api_calls_per_day` | Daily API rate limit |

## Invoice System

Invoices capture immutable snapshots of billing data.

### Invoice
```python
Invoice:
    business: FK(Tenant)
    subscription: FK(Subscription)
    period_start: datetime
    period_end: datetime

    # Snapshot values (immutable)
    plan_code_at_billing: str
    plan_name_at_billing: str
    plan_version_id_at_billing: int

    # Amounts (in cents)
    subtotal_amount: int
    discount_amount: int
    tax_amount: int
    total_amount: int

    status: str                     # "draft", "pending", "paid", "failed", "refunded"
    currency: str                   # "USD"
    stripe_invoice_id: str
    paid_at: datetime
```

### InvoiceLine
```python
InvoiceLine:
    invoice: FK(Invoice)
    line_type: str                  # "plan", "addon", "usage", "credit", "adjustment"
    description: str
    quantity: int
    unit_amount: int
    subtotal_amount: int
    tax_amount: int
    total_amount: int

    # References (for audit trail)
    plan_version: FK(PlanVersion)   # If line_type="plan"
    addon: FK(AddOnProduct)         # If line_type="addon"
    feature_code: str               # If line_type="usage"
    metadata: JSON                  # Additional context
```

**Invoice Generation:**
```python
from smoothschedule.billing.services.invoicing import generate_invoice_for_subscription

invoice = generate_invoice_for_subscription(
    subscription,
    period_start,
    period_end
)
```

The service:
1. Creates invoice with plan snapshots
2. Adds line item for base plan
3. Adds line items for active add-ons
4. Calculates totals
5. Returns immutable invoice

## API Endpoints

### Public Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/billing/plans/` | GET | List available plans (catalog) |
| `/api/billing/addons/` | GET | List available add-ons |

### Authenticated Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/me/entitlements/` | GET | Current tenant's entitlements |
| `/api/me/subscription/` | GET | Current subscription details |
| `/api/billing/invoices/` | GET | List tenant's invoices |
| `/api/billing/invoices/{id}/` | GET | Invoice detail with line items |

### Admin Endpoints (Platform Admin Only)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/billing/admin/features/` | CRUD | Manage features |
| `/api/billing/admin/plans/` | CRUD | Manage plans |
| `/api/billing/admin/plans/{id}/create_version/` | POST | Create new plan version |
| `/api/billing/admin/plan-versions/` | CRUD | Manage plan versions |
| `/api/billing/admin/plan-versions/{id}/mark_legacy/` | POST | Mark version as legacy |
| `/api/billing/admin/plan-versions/{id}/subscribers/` | GET | List version subscribers |
| `/api/billing/admin/addons/` | CRUD | Manage add-on products |

## Stripe Integration Points

The system stores Stripe IDs for synchronization:

- `PlanVersion.stripe_product_id` - Stripe Product for the plan
- `PlanVersion.stripe_price_id_monthly` - Monthly recurring Price
- `PlanVersion.stripe_price_id_yearly` - Annual recurring Price
- `AddOnProduct.stripe_product_id` - Stripe Product for add-on
- `AddOnProduct.stripe_price_id` - Stripe Price for add-on
- `Subscription.stripe_subscription_id` - Active Stripe Subscription
- `Invoice.stripe_invoice_id` - Stripe Invoice reference

## Usage Example

### Creating a Plan with Features

```python
# 1. Create the plan
plan = Plan.objects.create(
    code="pro",
    name="Pro",
    description="For growing businesses",
    display_order=2,
    is_active=True
)

# 2. Create a plan version with pricing
version = PlanVersion.objects.create(
    plan=plan,
    version=1,
    name="Pro Plan",
    is_public=True,
    price_monthly_cents=2999,  # $29.99/month
    price_yearly_cents=29990,  # $299.90/year
    trial_days=14,
    is_most_popular=True,
    marketing_features=[
        "Unlimited appointments",
        "SMS reminders",
        "Custom branding",
        "Priority support"
    ]
)

# 3. Attach features
PlanFeature.objects.create(
    plan_version=version,
    feature=Feature.objects.get(code="sms_enabled"),
    bool_value=True
)
PlanFeature.objects.create(
    plan_version=version,
    feature=Feature.objects.get(code="max_users"),
    int_value=10
)
```

### Checking Entitlements in Code

```python
from smoothschedule.billing.services.entitlements import EntitlementService

def send_sms_reminder(tenant, appointment):
    # Check if tenant can use SMS
    if not EntitlementService.has_feature(tenant, "sms_enabled"):
        raise PermissionDenied("SMS not available on your plan")

    # Check monthly limit
    current_usage = get_sms_usage_this_month(tenant)
    limit = EntitlementService.get_limit(tenant, "max_sms_per_month")

    if limit and current_usage >= limit:
        raise PermissionDenied("Monthly SMS limit reached")

    # Send the SMS...
```

## Future Enhancements

Planned but not yet implemented:
- [ ] Stripe webhook handlers for subscription lifecycle
- [ ] Proration for mid-cycle upgrades/downgrades
- [ ] Usage-based billing for SMS/calling
- [ ] Credit system for prepaid usage
- [ ] Coupon/discount code support
- [ ] Tax calculation integration
- [ ] Dunning management for failed payments
