# Subscriptions Feature

Stripe integration for subscription management, donations, and tier-based vehicle limits.

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                     Frontend (React)                            │
│   SubscriptionPage / SubscriptionMobileScreen                   │
│   - TierCard, PaymentMethodForm, BillingHistory                 │
│   - VehicleSelectionDialog, DowngradeFlow, DonationSection      │
└──────────────────────────┬──────────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────────┐
│                     Backend API                                  │
│   /api/subscriptions/* - Subscription management                │
│   /api/donations/* - One-time donations                         │
│   /api/webhooks/stripe - Stripe webhook (public)                │
└──────────────────────────┬──────────────────────────────────────┘
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
┌─────────────────┐ ┌────────────┐ ┌─────────────────┐
│ Subscriptions   │ │  Stripe    │ │ User Profile    │
│ Service         │ │  Client    │ │ Repository      │
│                 │ │            │ │ (tier sync)     │
└────────┬────────┘ └─────┬──────┘ └─────────────────┘
         │                │
         ▼                ▼
┌─────────────────┐ ┌────────────┐
│ Subscriptions   │ │  Stripe    │
│ Repository      │ │  API       │
└────────┬────────┘ └────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│                     PostgreSQL                                   │
│   subscriptions, subscription_events, donations,                │
│   tier_vehicle_selections                                       │
└─────────────────────────────────────────────────────────────────┘
```

## Database Schema

### subscriptions
Main subscription data linked to user profile.
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| user_id | VARCHAR(255) | FK to user_profiles.auth0_sub |
| stripe_customer_id | VARCHAR(255) | Stripe customer ID |
| stripe_subscription_id | VARCHAR(255) | Stripe subscription ID (nullable for free) |
| tier | subscription_tier | free, pro, enterprise |
| billing_cycle | billing_cycle | monthly, yearly |
| status | subscription_status | active, past_due, canceled, unpaid |
| current_period_start | TIMESTAMP | Billing period start |
| current_period_end | TIMESTAMP | Billing period end |
| grace_period_end | TIMESTAMP | Grace period expiry (30 days after payment failure) |
| cancel_at_period_end | BOOLEAN | Pending cancellation flag |

### subscription_events
Webhook event log for idempotency.
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| subscription_id | UUID | FK to subscriptions |
| stripe_event_id | VARCHAR(255) | UNIQUE, prevents duplicate processing |
| event_type | VARCHAR(100) | Stripe event type |
| payload | JSONB | Full event payload |

### donations
One-time payment records.
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| user_id | VARCHAR(255) | FK to user_profiles.auth0_sub |
| stripe_payment_intent_id | VARCHAR(255) | UNIQUE |
| amount_cents | INTEGER | Amount in cents |
| currency | VARCHAR(3) | Currency code (default: usd) |
| status | donation_status | pending, succeeded, failed, canceled |

### tier_vehicle_selections
Tracks which vehicles user selected to keep during tier downgrade.
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| user_id | VARCHAR(255) | FK to user_profiles.auth0_sub |
| vehicle_id | UUID | FK to vehicles |
| selected_at | TIMESTAMP | Selection timestamp |

## API Endpoints

### Subscription Management (Authenticated)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/subscriptions | Get current subscription |
| POST | /api/subscriptions/checkout | Create Stripe subscription |
| POST | /api/subscriptions/cancel | Schedule cancellation at period end |
| POST | /api/subscriptions/reactivate | Cancel pending cancellation |
| POST | /api/subscriptions/downgrade | Downgrade with vehicle selection |
| PUT | /api/subscriptions/payment-method | Update payment method |
| GET | /api/subscriptions/invoices | Get billing history |

### Donations (Authenticated)
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/donations | Create donation payment intent |
| GET | /api/donations | Get donation history |

### Webhooks (Public)
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/webhooks/stripe | Stripe webhook (signature verified) |

## Webhook Events Handled

| Event | Action |
|-------|--------|
| customer.subscription.created | Update subscription with Stripe subscription ID |
| customer.subscription.updated | Sync status, tier, period dates |
| customer.subscription.deleted | Mark canceled, downgrade to free tier |
| invoice.payment_succeeded | Clear grace period, mark active |
| invoice.payment_failed | Set 30-day grace period |
| payment_intent.succeeded | Mark donation as succeeded |

## Subscription Tiers

| Tier | Price | Vehicle Limit | Features |
|------|-------|---------------|----------|
| Free | $0 | 2 | Basic tracking, standard reports |
| Pro | $1.99/mo or $19.99/yr | 5 | VIN decoding, OCR, API access |
| Enterprise | $4.99/mo or $49.99/yr | Unlimited | All features, priority support |

## Grace Period

When payment fails:
1. Subscription status set to `past_due`
2. Grace period set to 30 days from failure
3. Email notifications sent: immediate, day 23, day 29
4. Daily job at 2:30 AM checks expired grace periods
5. Expired subscriptions downgraded to free tier

## Downgrade Flow

When user downgrades to a tier with fewer vehicle allowance:
1. Check if current vehicle count > target tier limit
2. If yes, show VehicleSelectionDialog
3. User selects which vehicles to keep
4. Unselected vehicles become tier-gated (hidden, not deleted)
5. Selections saved to tier_vehicle_selections table
6. On upgrade, all vehicles become accessible again

## Configuration

### Secrets (loaded from files via config-loader)
Secrets are loaded from `/run/secrets/` (or `SECRETS_DIR` env var):
```
/run/secrets/stripe-secret-key      # Stripe API secret key
/run/secrets/stripe-webhook-secret  # Stripe webhook signing secret
```

### Environment Variables (docker-compose)
Price IDs are not secrets and are configured via environment variables:
```
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...
STRIPE_ENTERPRISE_YEARLY_PRICE_ID=price_...
```

## Files

| File | Purpose |
|------|---------|
| migrations/001_subscriptions_tables.sql | Database schema |
| domain/subscriptions.types.ts | TypeScript interfaces |
| domain/subscriptions.service.ts | Business logic |
| domain/donations.service.ts | Donation logic |
| data/subscriptions.repository.ts | Database operations |
| external/stripe/stripe.client.ts | Stripe API wrapper |
| external/stripe/stripe.types.ts | Stripe type definitions |
| api/subscriptions.controller.ts | HTTP handlers |
| api/subscriptions.routes.ts | Authenticated routes |
| api/donations.controller.ts | Donation handlers |
| api/donations.routes.ts | Donation routes |
| api/webhooks.controller.ts | Webhook handler |
| api/webhooks.routes.ts | Public webhook endpoint |
| jobs/grace-period.job.ts | Daily grace period expiration job |
