Accept Payments - Stripe Integration with User Tiers #55
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Integrate Stripe payment processing to enable subscription-based user tiers (Free/Pro/Enterprise) and one-time donations. This feature creates a new
subscriptionsfeature capsule and a "Subscription" page in the settings area.User Tiers
Feature Gating by Tier
Technical Requirements
Stripe Integration
Webhook Events to Handle
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failedcustomer.subscription.trial_will_end(not used but good to handle)Subscription Lifecycle
Payment Failure Notifications (Email)
Downgrade Flow
When downgrading from a higher tier violates vehicle limits:
Donation Feature
UI Requirements
New "Subscription" Page (Settings Area)
Upgrade Prompts
Database Schema (New Tables)
subscriptionsid(UUID, PK)user_id(UUID, FK to user_profiles)stripe_customer_id(VARCHAR)stripe_subscription_id(VARCHAR, nullable)tier(ENUM: 'free', 'pro', 'enterprise')billing_cycle(ENUM: 'monthly', 'yearly', nullable)status(ENUM: 'active', 'past_due', 'canceled', 'unpaid')current_period_start(TIMESTAMP)current_period_end(TIMESTAMP)grace_period_end(TIMESTAMP, nullable)created_at,updated_atsubscription_eventsid(UUID, PK)subscription_id(UUID, FK)event_type(VARCHAR)stripe_event_id(VARCHAR)payload(JSONB)created_atdonationsid(UUID, PK)user_id(UUID, FK)stripe_payment_intent_id(VARCHAR)amount_cents(INTEGER)currency(VARCHAR, default 'usd')status(ENUM: 'pending', 'succeeded', 'failed')created_atdisabled_vehicles(or adddisabled_atcolumn to vehicles)API Endpoints
Subscription Management
GET /api/subscriptions- Get current subscription statusPOST /api/subscriptions/checkout- Create Stripe checkout sessionPOST /api/subscriptions/cancel- Schedule cancellation at period endPOST /api/subscriptions/reactivate- Cancel pending cancellationPUT /api/subscriptions/payment-method- Update payment methodGET /api/subscriptions/invoices- Get billing historyWebhooks
POST /api/webhooks/stripe- Stripe webhook endpoint (public, signature verified)Donations
POST /api/donations- Create donation payment intentGET /api/donations- Get user's donation historyVehicle Management (Updates)
POST /api/vehicles/:id/disable- Disable vehicle (for downgrade)POST /api/vehicles/:id/enable- Re-enable vehicle (if tier allows)Acceptance Criteria
Tier System
Stripe Integration
subscription_eventsSubscription Lifecycle
Payment Failure Handling
Donation Feature
UI/UX
Security
Environment Variables (New)
Dependencies
stripenpm package (backend)@stripe/stripe-jsnpm package (frontend)@stripe/react-stripe-jsnpm package (frontend)Out of Scope
References
Plan: Stripe Integration with User Tiers
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Overview
Implement Stripe payment processing for subscription-based user tiers (Free/Pro/Enterprise) and one-time donations. This creates a new
subscriptionsfeature capsule following the established feature pattern. The approach is layered backend-first to enable testable increments.Key Architectural Decisions:
subscriptionstable (FK to user_profiles.id) - Stripe billing data isolated from user identityPlanning Context
Decision Log
Rejected Alternatives
Constraints & Assumptions
Known Risks
Invisible Knowledge
Architecture
Data Flow
Upgrade Flow:
Payment Failure Flow:
Downgrade Flow:
Why This Structure
Milestones
Milestone 1: Database Schema + Stripe Client
Files:
backend/src/features/subscriptions/migrations/001_subscriptions_tables.sqlbackend/src/features/subscriptions/external/stripe/stripe.client.tsbackend/src/features/subscriptions/external/stripe/stripe.types.tsbackend/src/features/subscriptions/data/subscriptions.repository.tsbackend/src/features/subscriptions/domain/subscriptions.types.tsbackend/src/features/subscriptions/index.tsRequirements:
Acceptance Criteria:
Tests:
backend/src/features/subscriptions/tests/subscriptions.repository.test.tsMilestone 2: Service Layer + Webhook Endpoint
Files:
backend/src/features/subscriptions/domain/subscriptions.service.tsbackend/src/features/subscriptions/api/webhooks.routes.tsbackend/src/features/subscriptions/api/webhooks.controller.tsDependencies: M1
Requirements:
Acceptance Criteria:
Tests:
backend/src/features/subscriptions/tests/webhooks.test.tsMilestone 3: API Endpoints + Grace Period Job
Files:
backend/src/features/subscriptions/api/subscriptions.routes.tsbackend/src/features/subscriptions/api/subscriptions.controller.tsbackend/src/features/subscriptions/jobs/grace-period.job.tsbackend/src/features/notifications/migrations/006_payment_email_templates.sqlbackend/src/core/scheduler/index.ts(modify)Dependencies: M2
Requirements:
Acceptance Criteria:
Tests:
backend/src/features/subscriptions/tests/subscriptions.routes.test.tsMilestone 4: Frontend Subscription Page
Files:
frontend/src/features/settings/subscription/SubscriptionPage.tsxfrontend/src/features/settings/subscription/MobileSubscriptionScreen.tsxfrontend/src/features/settings/subscription/hooks/useSubscription.tsfrontend/src/features/settings/subscription/api/subscription.api.tsfrontend/src/features/settings/subscription/components/TierCard.tsxfrontend/src/features/settings/subscription/components/PaymentMethodForm.tsxfrontend/src/features/settings/subscription/components/BillingHistory.tsxDependencies: M3
Requirements:
Acceptance Criteria:
Tests:
Milestone 5: Vehicle Selection + Downgrade Flow
Files:
frontend/src/features/vehicles/components/VehicleSelectionDialog.tsxfrontend/src/features/settings/subscription/components/DowngradeFlow.tsxbackend/src/features/subscriptions/domain/subscriptions.service.ts(modify)backend/src/features/vehicles/domain/vehicles.service.ts(modify)Dependencies: M4
Requirements:
Acceptance Criteria:
Tests:
backend/src/features/subscriptions/tests/downgrade.test.tsMilestone 6: Donations Feature
Files:
backend/src/features/subscriptions/domain/donations.service.tsbackend/src/features/subscriptions/api/donations.routes.tsbackend/src/features/subscriptions/api/donations.controller.tsfrontend/src/features/settings/subscription/components/DonationSection.tsxDependencies: M1 (can run parallel to M4-M5)
Requirements:
Acceptance Criteria:
Tests:
backend/src/features/subscriptions/tests/donations.test.tsMilestone 7: Documentation
Files:
backend/src/features/subscriptions/README.mdbackend/src/features/subscriptions/CLAUDE.mdfrontend/src/features/settings/subscription/README.mdDependencies: M1-M6
Requirements:
Acceptance Criteria:
Milestone Dependencies
Verdict: AWAITING_REVIEW | Next: QR plan-completeness review
Plan Review: Complete
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Review Summary
QR plan-completeness Findings (SHOULD_FIX)
QR plan-code Findings (Implementation Details)
{ rawBody: true }for signature verificationQR plan-docs Findings (Structure)
frontend/src/features/settings/subscription/CLAUDE.mdOverall Verdict
PLAN APPROVED - Ready for execution with the noted documentation improvements.
The architectural decisions are sound:
The 7 milestones are independently testable with clear acceptance criteria.
Verdict: APPROVED | Next: Create branch
issue-55-stripe-integrationand begin M1 executionMilestone: M1 Complete - Database Schema + Stripe Client
Phase: Execution | Agent: Feature Agent | Status: PASS
Files Created
migrations/001_subscriptions_tables.sqldomain/subscriptions.types.tsexternal/stripe/stripe.client.tsexternal/stripe/stripe.types.tsdata/subscriptions.repository.tsindex.tsREADME.mdSchema Overview
Quality Checks
Commit
88b820bfeat: add subscriptions feature capsule - M1 database schema and Stripe client (refs #55)Verdict: PASS | Next: M2 - Service Layer + Webhook Endpoint
Milestone: M2 Complete - Service Layer + Webhook Endpoint
Phase: Execution | Agent: Feature Agent | Status: PASS
Files Created
domain/subscriptions.service.tsapi/webhooks.controller.tsapi/webhooks.routes.tsService Methods
getSubscription(userId)createSubscription(userId, email)upgradeSubscription(userId, tier, cycle, paymentMethodId)cancelSubscription(userId)reactivateSubscription(userId)handleWebhookEvent(payload, signature)Webhook Events Handled
customer.subscription.created- Update subscription with Stripe subscription IDcustomer.subscription.updated- Sync status, tier, period datescustomer.subscription.deleted- Mark canceled, downgrade to freeinvoice.payment_succeeded- Clear grace periodinvoice.payment_failed- Set 30-day grace periodKey Features
Quality Checks
Commit
7a0c09bfeat: add subscriptions service layer and webhook endpoint - M2 (refs #55)Verdict: PASS | Next: M3 - API Endpoints + Grace Period Job
Milestone: M3 Complete - API Endpoints + Grace Period Job
Phase: Execution | Agent: Feature Agent | Status: PASS
Files Created/Modified
api/subscriptions.controller.tsapi/subscriptions.routes.tsjobs/grace-period.job.tsnotifications/.../006_payment_email_templates.sqlcore/scheduler/index.tsexternal/stripe/stripe.client.tsdomain/subscriptions.service.tsAPI Endpoints
/api/subscriptions/api/subscriptions/checkout/api/subscriptions/cancel/api/subscriptions/reactivate/api/subscriptions/payment-method/api/subscriptions/invoicesGrace Period Job
Email Templates Added
payment_failed_immediate- Sent on first payment failurepayment_failed_7day- Sent 7 days before grace endspayment_failed_1day- Sent 1 day before grace endsQuality Checks
Commit
e7461a4feat: add subscription API endpoints and grace period job - M3 (refs #55)Verdict: PASS | Next: M4 - Frontend Subscription Page
Milestone: M4 Complete - Frontend Subscription Page
Phase: Execution | Agent: Frontend Agent | Status: PASS
Files Created
types/subscription.types.tsapi/subscription.api.tshooks/useSubscription.tscomponents/TierCard.tsxcomponents/PaymentMethodForm.tsxcomponents/BillingHistory.tsxpages/SubscriptionPage.tsxmobile/SubscriptionMobileScreen.tsxconstants/plans.tsDesktop Layout
Mobile Layout
Dependencies Added
Routes Added (App.tsx)
/garage/settings/subscriptionSubscriptionscreenQuality Checks
Commit
94d1c67feat: add frontend subscription page - M4 (refs #55)Verdict: PASS | Next: M5 - Vehicle Selection + Downgrade Flow
Milestone: M5 Complete - Vehicle Selection + Downgrade Flow
Phase: Execution | Agent: Feature Agent | Status: PASS
Files Created
VehicleSelectionDialog.tsxDowngradeFlow.tsxFiles Modified
vehicles.types.tsVehicleWithTierStatusinterfacevehicles.service.tsgetUserVehiclesWithTierStatus()for tier-gated filteringsubscriptions.service.tsdowngradeSubscription()methodsubscriptions.controller.tsdowngrade()endpoint handlersubscriptions.routes.ts/subscriptions/downgraderoutesubscription.api.tsdowngrade()API calluseSubscription.tsuseDowngrade()hookSubscriptionPage.tsxDowngrade Flow
tier_vehicle_selectionsTier Limits
Quality Checks
Commit
6c1a100feat: add vehicle selection and downgrade flow - M5 (refs #55)Verdict: PASS | Next: M6 - Donations Feature
Milestone: M6 Complete - Donations Feature
Phase: Execution | Agent: Feature Agent | Status: PASS
Files Created
donations.service.tsdonations.controller.tsdonations.routes.tsDonationSection.tsxDonationSectionMobile.tsxFiles Modified
app.tssubscriptions.service.tsstripe.client.tsstripe.types.tssubscription.api.tsuseSubscription.tsSubscriptionPage.tsxSubscriptionMobileScreen.tsxAPI Endpoints
/api/donations/api/donationsFeatures
Quality Checks
Commit
56da99dfeat: add donations feature with one-time payments - M6 (refs #55)Verdict: PASS | Next: M7 - Documentation
Milestone: M7 Complete - Documentation
Phase: Execution | Agent: Technical Writer | Status: PASS
Files Updated
backend/.../subscriptions/README.mdbackend/.../subscriptions/CLAUDE.mdfrontend/.../subscription/README.mdDocumentation Contents
Quality Checks
Commit
1cf4b78docs: update subscription feature documentation - M7 (refs #55)All Milestones Complete
Verdict: ALL MILESTONES PASS | Next: Quality Agent review and PR
Fix: Stripe Secrets Deployment Configuration
Phase: Execution | Agent: Developer | Status: PASS
Problem Identified
Stripe secrets were not being picked up during deployment due to missing configuration in multiple locations:
VITE_STRIPE_PUBLISHABLE_KEYwas not passed to Dockerfile during buildinject-secrets.shdid not include Stripe secretsFiles Modified
frontend/DockerfileARGandENVforVITE_STRIPE_PUBLISHABLE_KEYdocker-compose.ymlVITE_STRIPE_PUBLISHABLE_KEYbuild arg, fixed:roflags on Stripe secret mountsscripts/inject-secrets.shSTRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRETto secret injection.gitea/workflows/staging.yamlVITE_STRIPE_PUBLISHABLE_KEYbuild arg and Stripe secrets to inject step.gitea/workflows/production.yamlGitea Configuration Required
Before deployment will work, the following must be configured in Gitea:
Secrets (Settings > Secrets):
STRIPE_SECRET_KEY- Stripe API secret key (sk_live_...)STRIPE_WEBHOOK_SECRET- Stripe webhook signing secret (whsec_...)Variables (Settings > Variables):
VITE_STRIPE_PUBLISHABLE_KEY- Stripe publishable key (pk_live_...)Commit
254bed1fix: add Stripe secrets to CI/CD and build configuration (refs #55)Verdict: PASS | Next: Configure Gitea secrets/variables, then trigger staging deployment
Subscription Section Added to Settings Page
Added the missing Subscription section to both desktop and mobile Settings screens.
Changes
Desktop Settings Page (
frontend/src/pages/SettingsPage.tsx):/garage/settings/subscriptionMobile Settings Screen (
frontend/src/features/settings/mobile/MobileSettingsScreen.tsx):Commits
feat: add Subscription section to Settings page (refs #55)fix: correct subscription description when data unavailable (refs #55)feat: add Subscription section to mobile Settings (refs #55)Verified On Staging
Plan: Enhanced Billing Details Collection
Phase: Planning | Agent: Claude | Status: IN_PROGRESS
Problem Statement
Current
CardElementimplementation only collects card number, expiration, and CVC. Missing: Name, Address, City, State, Zip, Country.Solution: PaymentElement + AddressElement (Billing Mode)
This is the most user-friendly approach:
Implementation Plan
Milestone 1: Update PaymentMethodForm.tsx (Desktop)
CardElementwithPaymentElementAddressElementwithmode: 'billing'PaymentElementto skip billing address (collected by AddressElement)stripe.confirmSetup()or pass billing detailsMilestone 2: Update Mobile Payment Form
Milestone 3: Update DonationSection (Desktop)
CardElementwithPaymentElement+AddressElementconfirmCardPaymentto include billing detailsMilestone 4: Update DonationSectionMobile
Milestone 5: Quality Validation
Fields Collected
Technical Notes
@stripe/react-stripe-jspackage supports both elementsVerdict: APPROVED | Next: Execute Milestone 1
Milestone: Enhanced Billing Details Collection - Complete
Phase: Execution | Agent: Claude | Status: PASS
Changes Made
Milestone 1: PaymentMethodForm.tsx (Desktop Subscriptions)
CardElementwithPaymentElement+AddressElementmode: 'setup'Elements configuration for payment method creationMilestone 2: SubscriptionMobileScreen.tsx (Mobile Subscriptions)
paymentElementsOptionsPaymentMethodFormcomponentMilestone 3: DonationSection.tsx (Desktop Donations)
AddressElementfor billing address collectionconfirmCardPaymentto include full billing_detailsCardElementfor card input (works best with PaymentIntent flow)Milestone 4: DonationSectionMobile.tsx (Mobile Donations)
AddressElementwith mobile-friendly stylingFields Now Collected
Quality Validation
Files Modified
frontend/src/features/subscription/components/PaymentMethodForm.tsxfrontend/src/features/subscription/pages/SubscriptionPage.tsxfrontend/src/features/subscription/mobile/SubscriptionMobileScreen.tsxfrontend/src/features/subscription/components/DonationSection.tsxfrontend/src/features/subscription/components/DonationSectionMobile.tsxVerdict: PASS | Next: Commit and test in browser