docs: update subscription feature documentation - M7 (refs #55)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m58s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Failing after 17s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 6s

This commit is contained in:
Eric Gullickson
2026-01-18 16:52:50 -06:00
parent 56da99de36
commit 1cf4b78075
3 changed files with 304 additions and 126 deletions

View File

@@ -0,0 +1,55 @@
# backend/src/features/subscriptions/
Stripe payment integration for subscription tiers and donations.
## Files
| File | What | When to read |
| ---- | ---- | ------------ |
| `README.md` | Feature overview with architecture diagram | Understanding subscription flow |
## Subdirectories
| Directory | What | When to read |
| --------- | ---- | ------------ |
| `api/` | HTTP controllers and routes | API endpoint changes |
| `domain/` | Services and type definitions | Business logic changes |
| `data/` | Repository for database operations | Database queries |
| `external/stripe/` | Stripe API client wrapper | Stripe integration |
| `migrations/` | Database schema | Schema changes |
| `jobs/` | Scheduled background jobs | Grace period processing |
## Key Patterns
- Repository mapRow() converts snake_case to camelCase
- Webhook idempotency via stripe_event_id unique constraint
- Tier sync to user_profiles.subscription_tier on changes
- Grace period: 30 days after payment failure
- Vehicle selections for tier downgrades (not deleted, just gated)
## API Endpoints
### Subscriptions (Authenticated)
- GET /api/subscriptions - Current subscription
- POST /api/subscriptions/checkout - Create subscription
- POST /api/subscriptions/cancel - Schedule cancellation
- POST /api/subscriptions/reactivate - Cancel pending cancellation
- POST /api/subscriptions/downgrade - Downgrade with vehicle selection
- PUT /api/subscriptions/payment-method - Update payment
- GET /api/subscriptions/invoices - Billing history
### Donations (Authenticated)
- POST /api/donations - Create payment intent
- GET /api/donations - Donation history
### Webhooks (Public)
- POST /api/webhooks/stripe - Stripe webhook (signature verified)
## Environment Variables
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
- STRIPE_PRO_MONTHLY_PRICE_ID
- STRIPE_PRO_YEARLY_PRICE_ID
- STRIPE_ENTERPRISE_MONTHLY_PRICE_ID
- STRIPE_ENTERPRISE_YEARLY_PRICE_ID

View File

@@ -2,104 +2,181 @@
Stripe integration for subscription management, donations, and tier-based vehicle limits.
## Milestone 1: Core Infrastructure (COMPLETE)
## Architecture
### Database Schema
- `subscriptions` - User subscription records with Stripe integration
- `subscription_events` - Webhook event logging for idempotency
- `donations` - One-time payment tracking
- `tier_vehicle_selections` - User vehicle selections during tier downgrades
```
┌─────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────┘
```
### Type Definitions
- **Domain Types** (`domain/subscriptions.types.ts`): Core business entities and request/response types
- **Stripe Types** (`external/stripe/stripe.types.ts`): Simplified Stripe API response types
## Database Schema
### Data Access
- **Repository** (`data/subscriptions.repository.ts`): Database operations with proper snake_case to camelCase mapping
- Subscription CRUD operations
- Event logging with idempotency checks
- Donation tracking
- Vehicle selection management
### 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 |
### External Integration
- **Stripe Client** (`external/stripe/stripe.client.ts`): Stripe API wrapper with error handling
- Customer creation
- Subscription lifecycle management
- Payment intent creation for donations
- Webhook event verification
- Payment method updates
### 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 |
## Environment Variables
### 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 |
Required for Stripe integration:
- `STRIPE_SECRET_KEY` - Stripe API secret key
- `STRIPE_WEBHOOK_SECRET` - Stripe webhook signing secret
### 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
Defined in `user_profiles.subscription_tier` enum:
- `free` - Limited features (default)
- `pro` - Enhanced features
- `enterprise` - Full features
| 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 |
## Billing Cycles
## Grace Period
- `monthly` - Monthly billing
- `yearly` - Annual billing
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
## Subscription Status
## Downgrade Flow
- `active` - Subscription is active
- `past_due` - Payment failed, in grace period
- `canceled` - Subscription canceled
- `unpaid` - Payment failed, grace period expired
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
## Milestone 2: Service Layer + Webhook Endpoint (COMPLETE)
## Environment Variables
### Service Layer
- **SubscriptionsService** (`domain/subscriptions.service.ts`): Business logic for subscription management
- Get current subscription for user
- Create new subscription (Stripe customer + free tier record)
- Upgrade subscription (create Stripe subscription with payment method)
- Cancel subscription (schedule for end of period)
- Reactivate subscription (remove pending cancellation)
- Handle Stripe webhook events with idempotency
### Webhook Processing
- **Webhook Events Handled**:
- `customer.subscription.created` - Update subscription record with Stripe subscription ID
- `customer.subscription.updated` - Update status, tier, period dates
- `customer.subscription.deleted` - Mark as canceled, downgrade to free tier
- `invoice.payment_succeeded` - Clear grace period, mark active
- `invoice.payment_failed` - Set 30-day grace period
### API Endpoints
- **WebhooksController** (`api/webhooks.controller.ts`): Webhook event handler
- **Routes** (`api/webhooks.routes.ts`): PUBLIC endpoint with rawBody support
- POST /api/webhooks/stripe - Stripe webhook receiver (no JWT auth, signature verified)
### Integration
- Syncs subscription tier changes to `user_profiles.subscription_tier` via UserProfileRepository
- Uses environment variables for Stripe price IDs (PRO/ENTERPRISE, MONTHLY/YEARLY)
## Next Steps (Future Milestones)
- M3: API endpoints for subscription management (user-facing CRUD)
- M4: Frontend integration and subscription UI
- M5: Testing and documentation
## Database Migration
Run migrations:
```bash
npm run migrate:feature subscriptions
```
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...
STRIPE_ENTERPRISE_YEARLY_PRICE_ID=price_...
```
## Architecture Notes
## Files
- All database columns use snake_case
- All TypeScript properties use camelCase
- Repository `mapRow()` methods handle case conversion
- Prepared statements used for all queries (no string concatenation)
- Comprehensive error logging with structured logger
- Webhook idempotency via `stripe_event_id` unique constraint
| 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 |