diff --git a/backend/src/features/subscriptions/CLAUDE.md b/backend/src/features/subscriptions/CLAUDE.md new file mode 100644 index 0000000..6a77922 --- /dev/null +++ b/backend/src/features/subscriptions/CLAUDE.md @@ -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 diff --git a/backend/src/features/subscriptions/README.md b/backend/src/features/subscriptions/README.md index 2a09f61..2e4e000 100644 --- a/backend/src/features/subscriptions/README.md +++ b/backend/src/features/subscriptions/README.md @@ -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 | diff --git a/frontend/src/features/subscription/README.md b/frontend/src/features/subscription/README.md index 473ef5e..74f8188 100644 --- a/frontend/src/features/subscription/README.md +++ b/frontend/src/features/subscription/README.md @@ -4,20 +4,21 @@ Frontend UI for subscription management with Stripe integration. ## 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 ### TierCard -Displays subscription plan with: -- Plan name and pricing (monthly/yearly) +Subscription plan display with: +- Plan name and pricing (monthly/yearly toggle) - Feature list - Current plan indicator - Upgrade/downgrade button ### PaymentMethodForm Stripe Elements integration for: -- Credit card input with validation +- Credit card input with CardElement +- Real-time validation - Payment method creation - Error handling @@ -27,85 +28,130 @@ Invoice list with: - PDF download links - 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 ### SubscriptionPage (Desktop) -- Current plan card with status -- Three-column tier cards layout -- Payment method section +MUI-based layout: +- Current plan card with status badges +- Three-column tier comparison +- Monthly/yearly toggle +- Payment method modal - Billing history table -- Material-UI components +- Donation section ### SubscriptionMobileScreen (Mobile) +Tailwind-based layout: +- GlassCard styling - Stacked card layout - Touch-friendly buttons (44px min) -- Tailwind styling -- GlassCard components +- Modal payment forms ## API Integration -All endpoints are in `/subscriptions`: -- GET `/subscriptions` - Current subscription -- POST `/subscriptions/checkout` - Upgrade subscription -- POST `/subscriptions/cancel` - Cancel subscription -- POST `/subscriptions/reactivate` - Reactivate subscription -- PUT `/subscriptions/payment-method` - Update payment method -- GET `/subscriptions/invoices` - Invoice history +### Subscriptions +| Endpoint | Method | Hook | +|----------|--------|------| +| /api/subscriptions | GET | useSubscription() | +| /api/subscriptions/checkout | POST | useCheckout() | +| /api/subscriptions/cancel | POST | useCancelSubscription() | +| /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 -- `useSubscription()` - Fetch current subscription -- `useCheckout()` - Upgrade subscription -- `useCancelSubscription()` - Cancel subscription -- `useReactivateSubscription()` - Reactivate subscription -- `useInvoices()` - Fetch invoice history +| Hook | Purpose | +|------|---------| +| useSubscription() | Fetch current subscription | +| useCheckout() | Upgrade subscription | +| 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 VITE_STRIPE_PUBLISHABLE_KEY=pk_test_... ``` ## Subscription Tiers -### Free +### Free ($0) - 2 vehicles - Basic tracking - Standard reports -- Price: $0 -### Pro +### Pro ($1.99/month or $19.99/year) - Up to 5 vehicles - VIN decoding - OCR functionality - API access -- Price: $1.99/month or $19.99/year -### Enterprise +### Enterprise ($4.99/month or $49.99/year) - Unlimited vehicles - All Pro features - Priority support -- Price: $4.99/month or $49.99/year -## Mobile Navigation +## Routing -Add subscription screen to settings navigation: -```typescript -navigateToScreen('Subscription') -``` +- Desktop: `/garage/settings/subscription` +- Mobile: `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 -Test subscription flow: 1. View current plan 2. Toggle monthly/yearly billing -3. Select upgrade tier -4. Enter payment method -5. Complete checkout -6. Verify subscription update -7. View billing history +3. Upgrade: Select tier, enter payment, complete checkout +4. Downgrade: Select vehicles to keep if over limit +5. Cancel/reactivate subscription +6. Make a donation +7. View billing and donation history