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. 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 │ Frontend (React) │
- `donations` - One-time payment tracking │ SubscriptionPage / SubscriptionMobileScreen │
- `tier_vehicle_selections` - User vehicle selections during tier downgrades │ - 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 ## Database Schema
- **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
### Data Access ### subscriptions
- **Repository** (`data/subscriptions.repository.ts`): Database operations with proper snake_case to camelCase mapping Main subscription data linked to user profile.
- Subscription CRUD operations | Column | Type | Description |
- Event logging with idempotency checks |--------|------|-------------|
- Donation tracking | id | UUID | Primary key |
- Vehicle selection management | 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 ### subscription_events
- **Stripe Client** (`external/stripe/stripe.client.ts`): Stripe API wrapper with error handling Webhook event log for idempotency.
- Customer creation | Column | Type | Description |
- Subscription lifecycle management |--------|------|-------------|
- Payment intent creation for donations | id | UUID | Primary key |
- Webhook event verification | subscription_id | UUID | FK to subscriptions |
- Payment method updates | 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: ### tier_vehicle_selections
- `STRIPE_SECRET_KEY` - Stripe API secret key Tracks which vehicles user selected to keep during tier downgrade.
- `STRIPE_WEBHOOK_SECRET` - Stripe webhook signing secret | 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 ## Subscription Tiers
Defined in `user_profiles.subscription_tier` enum: | Tier | Price | Vehicle Limit | Features |
- `free` - Limited features (default) |------|-------|---------------|----------|
- `pro` - Enhanced features | Free | $0 | 2 | Basic tracking, standard reports |
- `enterprise` - Full features | 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 When payment fails:
- `yearly` - Annual billing 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 When user downgrades to a tier with fewer vehicle allowance:
- `past_due` - Payment failed, in grace period 1. Check if current vehicle count > target tier limit
- `canceled` - Subscription canceled 2. If yes, show VehicleSelectionDialog
- `unpaid` - Payment failed, grace period expired 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 STRIPE_SECRET_KEY=sk_live_...
- Get current subscription for user STRIPE_WEBHOOK_SECRET=whsec_...
- Create new subscription (Stripe customer + free tier record) STRIPE_PRO_MONTHLY_PRICE_ID=price_...
- Upgrade subscription (create Stripe subscription with payment method) STRIPE_PRO_YEARLY_PRICE_ID=price_...
- Cancel subscription (schedule for end of period) STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...
- Reactivate subscription (remove pending cancellation) STRIPE_ENTERPRISE_YEARLY_PRICE_ID=price_...
- 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
``` ```
## Architecture Notes ## Files
- All database columns use snake_case | File | Purpose |
- All TypeScript properties use camelCase |------|---------|
- Repository `mapRow()` methods handle case conversion | migrations/001_subscriptions_tables.sql | Database schema |
- Prepared statements used for all queries (no string concatenation) | domain/subscriptions.types.ts | TypeScript interfaces |
- Comprehensive error logging with structured logger | domain/subscriptions.service.ts | Business logic |
- Webhook idempotency via `stripe_event_id` unique constraint | 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 |

View File

@@ -4,20 +4,21 @@ Frontend UI for subscription management with Stripe integration.
## Overview ## Overview
Provides subscription tier management, payment method updates, and billing history viewing. Provides subscription tier management, payment method updates, billing history, donations, and vehicle selection during downgrade flow.
## Components ## Components
### TierCard ### TierCard
Displays subscription plan with: Subscription plan display with:
- Plan name and pricing (monthly/yearly) - Plan name and pricing (monthly/yearly toggle)
- Feature list - Feature list
- Current plan indicator - Current plan indicator
- Upgrade/downgrade button - Upgrade/downgrade button
### PaymentMethodForm ### PaymentMethodForm
Stripe Elements integration for: Stripe Elements integration for:
- Credit card input with validation - Credit card input with CardElement
- Real-time validation
- Payment method creation - Payment method creation
- Error handling - Error handling
@@ -27,85 +28,130 @@ Invoice list with:
- PDF download links - PDF download links
- MUI Table component - MUI Table component
### VehicleSelectionDialog
Vehicle selection during downgrade:
- Checkbox list for each vehicle
- Counter showing selected vs allowed
- Warning about tier-gated vehicles
- Validation preventing over-selection
### DowngradeFlow
Orchestrates downgrade process:
- Checks vehicle count vs target tier limit
- Shows VehicleSelectionDialog if needed
- Submits vehicle selections to backend
### DonationSection / DonationSectionMobile
One-time donation form:
- Free-form amount input (no presets)
- $0.50 minimum (Stripe limit)
- Stripe CardElement for payment
- Donation history table
- Success feedback
## Pages ## Pages
### SubscriptionPage (Desktop) ### SubscriptionPage (Desktop)
- Current plan card with status MUI-based layout:
- Three-column tier cards layout - Current plan card with status badges
- Payment method section - Three-column tier comparison
- Monthly/yearly toggle
- Payment method modal
- Billing history table - Billing history table
- Material-UI components - Donation section
### SubscriptionMobileScreen (Mobile) ### SubscriptionMobileScreen (Mobile)
Tailwind-based layout:
- GlassCard styling
- Stacked card layout - Stacked card layout
- Touch-friendly buttons (44px min) - Touch-friendly buttons (44px min)
- Tailwind styling - Modal payment forms
- GlassCard components
## API Integration ## API Integration
All endpoints are in `/subscriptions`: ### Subscriptions
- GET `/subscriptions` - Current subscription | Endpoint | Method | Hook |
- POST `/subscriptions/checkout` - Upgrade subscription |----------|--------|------|
- POST `/subscriptions/cancel` - Cancel subscription | /api/subscriptions | GET | useSubscription() |
- POST `/subscriptions/reactivate` - Reactivate subscription | /api/subscriptions/checkout | POST | useCheckout() |
- PUT `/subscriptions/payment-method` - Update payment method | /api/subscriptions/cancel | POST | useCancelSubscription() |
- GET `/subscriptions/invoices` - Invoice history | /api/subscriptions/reactivate | POST | useReactivateSubscription() |
| /api/subscriptions/downgrade | POST | useDowngrade() |
| /api/subscriptions/payment-method | PUT | useUpdatePaymentMethod() |
| /api/subscriptions/invoices | GET | useInvoices() |
### Donations
| Endpoint | Method | Hook |
|----------|--------|------|
| /api/donations | POST | useCreateDonation() |
| /api/donations | GET | useDonations() |
## Hooks ## Hooks
- `useSubscription()` - Fetch current subscription | Hook | Purpose |
- `useCheckout()` - Upgrade subscription |------|---------|
- `useCancelSubscription()` - Cancel subscription | useSubscription() | Fetch current subscription |
- `useReactivateSubscription()` - Reactivate subscription | useCheckout() | Upgrade subscription |
- `useInvoices()` - Fetch invoice history | useCancelSubscription() | Cancel subscription |
| useReactivateSubscription() | Reactivate subscription |
| useDowngrade() | Downgrade with vehicle selection |
| useInvoices() | Fetch billing history |
| useCreateDonation() | Create donation payment |
| useDonations() | Fetch donation history |
## Environment Setup ## Environment Variables
Required environment variable:
```bash ```bash
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_... VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
``` ```
## Subscription Tiers ## Subscription Tiers
### Free ### Free ($0)
- 2 vehicles - 2 vehicles
- Basic tracking - Basic tracking
- Standard reports - Standard reports
- Price: $0
### Pro ### Pro ($1.99/month or $19.99/year)
- Up to 5 vehicles - Up to 5 vehicles
- VIN decoding - VIN decoding
- OCR functionality - OCR functionality
- API access - API access
- Price: $1.99/month or $19.99/year
### Enterprise ### Enterprise ($4.99/month or $49.99/year)
- Unlimited vehicles - Unlimited vehicles
- All Pro features - All Pro features
- Priority support - Priority support
- Price: $4.99/month or $49.99/year
## Mobile Navigation ## Routing
Add subscription screen to settings navigation: - Desktop: `/garage/settings/subscription`
```typescript - Mobile: `navigateToScreen('Subscription')`
navigateToScreen('Subscription')
```
## Desktop Routing ## Files
Route: `/garage/settings/subscription` | File | Purpose |
|------|---------|
| types/subscription.types.ts | TypeScript interfaces |
| api/subscription.api.ts | API client calls |
| hooks/useSubscription.ts | React Query hooks |
| constants/plans.ts | Tier configuration |
| components/TierCard.tsx | Plan display card |
| components/PaymentMethodForm.tsx | Stripe Elements form |
| components/BillingHistory.tsx | Invoice table |
| components/VehicleSelectionDialog.tsx | Vehicle selection modal |
| components/DowngradeFlow.tsx | Downgrade orchestrator |
| components/DonationSection.tsx | Desktop donation UI |
| components/DonationSectionMobile.tsx | Mobile donation UI |
| pages/SubscriptionPage.tsx | Desktop page |
| mobile/SubscriptionMobileScreen.tsx | Mobile screen |
## Testing ## Testing
Test subscription flow:
1. View current plan 1. View current plan
2. Toggle monthly/yearly billing 2. Toggle monthly/yearly billing
3. Select upgrade tier 3. Upgrade: Select tier, enter payment, complete checkout
4. Enter payment method 4. Downgrade: Select vehicles to keep if over limit
5. Complete checkout 5. Cancel/reactivate subscription
6. Verify subscription update 6. Make a donation
7. View billing history 7. View billing and donation history