Skip to main content

Stripe Payment Integration - Phase 1 Backend Implementation Summary

Overview

This document summarizes the complete backend implementation of Stripe payment integration for the CivStart GovFit platform, including GovFit subscriptions and credit pack purchases with monthly credit rollover.

Implementation Date: December 2, 2024 Status: ✅ Phase 1 Backend Complete


Implementation Checklist

✅ 1. Database Schema Updates

Location: packages/database/prisma/schema.prisma

StartupContact Model - New Fields:

  • stripeCustomerId (String?, unique) - Stripe customer ID
  • govfitSubscriptionId (String?, unique) - GovFit subscription ID
  • govfitSubscriptionStatus (String?) - Subscription status (active/past_due/canceled/unpaid)
  • govfitCurrentPeriodEnd (DateTime?) - Current billing period end date
  • subscriptionGracePeriodEnd (DateTime?) - Grace period end date (1 day after expiry)
  • rolloverCreditsUsed (Int, default 0) - Track rolled over credits

New Models Created:

CreditPackSubscription:

  • Tracks monthly credit pack subscriptions
  • Fields: id, startupId, stripeSubscriptionId, stripePriceId, credits, status, currentPeriodEnd, lastCreditAllocation, rolloverLimit, metadata
  • Indexes on: startupId, stripeSubscriptionId, status, currentPeriodEnd

StripeEvent:

  • Idempotent webhook event tracking
  • Fields: id, stripeEventId, type, customerId, subscriptionId, processed, processingError, rawEvent, createdAt, processedAt
  • Prevents duplicate event processing

Migration: 20251201184508_add_stripe_payment_integration


✅ 2. Stripe SDK Integration

Package Installed: stripe@latest Installation Method: pnpm add stripe --filter backend


✅ 3. Environment Configuration

Location: .env.template

Variables Added (All Environments):

STRIPE_SECRET_KEY=sk_test_...  # sk_live_... for production
STRIPE_PUBLISHABLE_KEY=pk_test_... # pk_live_... for production
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

✅ 4. Module Structure

Location: apps/backend/src/stripe/

Files Created:

stripe/
├── dto/
│ ├── index.ts
│ ├── create-checkout-session.dto.ts
│ ├── cancel-subscription.dto.ts
│ └── subscription.dto.ts
├── interfaces/
│ └── stripe-metadata.interface.ts
├── stripe.service.ts
├── stripe.controller.ts
├── stripe-webhook.controller.ts
└── stripe.module.ts

✅ 5. Core Stripe Service

Location: apps/backend/src/stripe/stripe.service.ts

Key Methods Implemented:

Customer Management:

  • createOrGetCustomer(email, startupId, startupName) - Create/retrieve Stripe customer

Checkout:

  • createCheckoutSession(startupId, priceId, successUrl, cancelUrl) - Generate checkout URL
  • parsePriceId(priceId, startupId, startupEmail) - Extract product metadata

Webhook Handlers:

  • handleCheckoutComplete(session) - Process successful checkout
    • GovFit: Creates subscription, adds 1 credit/month
    • One-time pack: Adds credits immediately
    • Monthly pack: Creates CreditPackSubscription, sets rollover limits
  • handleInvoicePaid(invoice) - Monthly renewal with rollover logic
  • handleSubscriptionDeleted(subscription) - Cleanup on cancellation
  • handlePaymentFailed(invoice) - Update status to past_due

Subscription Management:

  • getActiveSubscriptions(startupId) - List all active subscriptions
  • cancelSubscription(subscriptionId, startupId) - Cancel subscription
  • getBillingHistory(startupId, limit) - Retrieve past invoices
  • getUpcomingInvoice(startupId) - Get next billing cycle info

Security:

  • verifyWebhookSignature(payload, signature) - Webhook signature verification

✅ 6. Webhook Controller

Location: apps/backend/src/stripe/stripe-webhook.controller.ts

Endpoint:

POST /stripe/webhook - Receives all Stripe webhook events

Events Handled:

  1. checkout.session.completed - Process new purchases
  2. invoice.paid - Handle monthly renewals with rollover
  3. invoice.payment_failed - Mark subscriptions as past_due
  4. customer.subscription.deleted - Handle cancellations
  5. customer.subscription.updated - Update subscription status

Features:

  • Idempotent event processing (prevents duplicates)
  • Event logging in StripeEvent table
  • Error tracking with processingError field
  • Raw event storage for debugging

✅ 7. REST API Controller

Location: apps/backend/src/stripe/stripe.controller.ts

Endpoints (All Protected by ClerkAuthGuard):

POST /stripe/create-checkout-session

  • Body: { priceId, successUrl, cancelUrl }
  • Returns: { checkoutUrl, sessionId }

GET /stripe/subscriptions

  • Returns: { govfit, creditPacks[], totalMonthlyCredits }

POST /stripe/cancel-subscription

  • Body: { subscriptionId }
  • Returns: { success, message, canceledAt }

GET /stripe/billing-history?limit=10

  • Returns: { invoices[], hasMore }

GET /stripe/upcoming-invoice

  • Returns: { amount, currency, periodStart, periodEnd, items[] }

GET /stripe/health

  • Returns: { status, timestamp }

✅ 8. Subscription Service

Location: apps/backend/src/services/subscription.service.ts

Cron Jobs:

Daily Grace Period Check (@Cron(EVERY_DAY_AT_MIDNIGHT))

  • Checks all expired subscriptions
  • Locks access if grace period (1 day) expired
  • Sends 7-day expiry warnings

Key Methods:

  • checkExpiredSubscriptions() - Daily cron job
  • lockExpiredSubscription(startupId) - Lock access for expired subs
  • sendExpiryWarnings() - Send 7-day warnings
  • isSubscriptionActive(startupId) - Check if subscription is active
  • getSubscriptionStatus(startupId) - Get detailed subscription status
  • triggerSubscriptionCheck() - Manual trigger for admin

✅ 9. Subscription Access Guard

Location: apps/backend/src/auth/subscription.guard.ts

Usage:

@UseGuards(ClerkAuthGuard, ActiveSubscriptionGuard)

Protection Logic:

  1. Check for active GovFit subscription
  2. Allow access during 1-day grace period
  3. Allow unlimited tier (legacy/admin-granted)
  4. Block access if expired past grace period

Error Response:

{
"statusCode": 403,
"message": "Your GovFit subscription has been canceled...",
"error": "Subscription Required",
"requiresSubscription": true,
"billingUrl": "/settings/billing"
}

✅ 10. Credit Rollover Logic

Rollover Limits per Pack:

1-credit pack  → max 4 unused credits
4-credit pack → max 10 unused credits
12-credit pack → max 30 unused credits
28-credit pack → max 50 unused credits

Monthly Reset Algorithm:

unused = creditsRemaining;
rolloverCredits = min(unused, rolloverLimit);
newCreditsTotal = monthlyAllocation + rolloverCredits;

Implementation:

Located in StripeService.handleCreditPackRenewal():

  1. Calculate unused credits
  2. Apply rollover limit
  3. Reset credits: creditsTotal = allocation + rollover
  4. Log transaction with rollover details
  5. Emit WebSocket update

✅ 11. WebSocket Events

Location: Integrated via WebSocketService

Subscription Events:

  • subscription:updated - Status change
  • subscription:expiring - 7-day warning
  • subscription:grace_period - Entered grace period
  • subscription:locked - Access locked
  • credits:reset - Monthly reset with rollover

Credit Update Payload:

{
startupId: string,
creditsRemaining: number,
creditsUsed: number,
creditsTotal: number,
monthlyAllocation: number,
creditTier: string,
lastUpdate: string,
reason: string
}

✅ 12. Module Registration

Location: apps/backend/src/app.module.ts

imports: [
// ... other modules
StripeModule,
];

Product Configuration

GovFit Platform Access

  • Price: $12,000/year (billed monthly: $1,000/month)
  • Lookup Key: govfit_platform_access
  • Credits: 1 credit/month (resets monthly)
  • Metadata: {credits: 1, frequency: 'monthly', productType: 'govfit'}

Credit Packs

1-Credit Pack

  • One-time: $650 | Lookup: creditpack_1_onetime
  • Monthly: $618/month | Lookup: creditpack_1_monthly
  • Rollover: Max 4 unused credits

4-Credit Pack

  • One-time: $2,000 | Lookup: creditpack_4_onetime
  • Monthly: $1,800/month | Lookup: creditpack_4_monthly
  • Rollover: Max 10 unused credits

12-Credit Pack

  • One-time: $5,000 | Lookup: creditpack_12_onetime
  • Monthly: $4,250/month | Lookup: creditpack_12_monthly
  • Rollover: Max 30 unused credits

28-Credit Pack

  • One-time: $10,000 | Lookup: creditpack_28_onetime
  • Monthly: $8,000/month | Lookup: creditpack_28_monthly
  • Rollover: Max 50 unused credits

Webhook Setup Instructions

1. Create Webhook in Stripe Dashboard

Navigate to: Developers → Webhooks → Add endpoint

2. Set Endpoint URL

  • Development: https://api-dev.civstart.ventures/stripe/webhook
  • Staging: https://api-staging.civstart.ventures/stripe/webhook
  • Production: https://api.civstart.ventures/stripe/webhook

3. Select Events

  • checkout.session.completed
  • invoice.paid
  • invoice.payment_failed
  • customer.subscription.deleted
  • customer.subscription.updated

4. Copy Webhook Secret

Save the whsec_... key to your environment variables.


Testing Checklist

Unit Tests (TODO - Phase 1.5)

  • StripeService customer creation
  • Checkout session generation
  • Webhook event processing
  • Rollover credit calculations
  • Subscription status checks

Integration Tests (TODO - Phase 1.5)

  • End-to-end checkout flow
  • Webhook signature verification
  • Idempotent event processing
  • Grace period expiry
  • Credit rollover on renewal

Manual Testing

  • Test GovFit subscription purchase
  • Test one-time credit pack purchase
  • Test monthly credit pack subscription
  • Test subscription cancellation
  • Test grace period access
  • Test credit rollover on renewal
  • Verify webhook events in Stripe dashboard
  • Test billing history retrieval

Next Steps (Phase 2 - Frontend)

Frontend Implementation Tasks:

  1. Billing Page UI (/settings/billing)

    • Subscription status display
    • Credit pack purchase interface
    • Active subscriptions list
    • Billing history table
    • Cancel subscription modal
  2. Stripe Checkout Integration

    • Checkout button component
    • Success/cancel redirect handling
    • Loading states
  3. Subscription Widgets

    • Credit balance display
    • Expiry warning banners
    • Grace period notifications
    • Upgrade prompts
  4. WebSocket Integration

    • Real-time credit updates
    • Subscription status changes
    • Expiry warnings

Security Considerations

Implemented:

  • ✅ Webhook signature verification
  • ✅ Clerk authentication on all API endpoints
  • ✅ Startup ownership verification
  • ✅ Idempotent event processing
  • ✅ Raw event logging for audit trails

Production Checklist:

  • Use LIVE Stripe API keys (sklive, pklive)
  • Enable Stripe Radar for fraud detection
  • Set up payment method verification
  • Configure webhook retry logic
  • Enable 3D Secure authentication
  • Set up invoice email notifications
  • Configure tax collection (if applicable)

Monitoring & Observability

Logs to Monitor:

  • Webhook event processing (StripeWebhookController)
  • Failed payment attempts
  • Subscription cancellations
  • Grace period expirations
  • Credit rollover operations

Metrics to Track:

  • Webhook processing latency
  • Failed webhook events (check StripeEvent.processingError)
  • Subscription churn rate
  • Average credit usage per startup
  • Revenue by product type

API Documentation

Base URL:

  • Development: https://api-dev.civstart.ventures
  • Staging: https://api-staging.civstart.ventures
  • Production: https://api.civstart.ventures

Authentication:

All endpoints require Clerk JWT token in Authorization header:

Authorization: Bearer <clerk_jwt_token>

Endpoints:

See section 7 above for full endpoint documentation.


Troubleshooting

Common Issues:

1. Webhook signature verification failed

  • Verify STRIPE_WEBHOOK_SECRET is correctly set
  • Check that raw body is being passed to verification
  • Ensure webhook endpoint URL matches Stripe dashboard

2. Customer not found

  • Check stripeCustomerId is stored in database
  • Verify customer exists in Stripe dashboard
  • Customer may have been deleted manually

3. Credits not rolling over

  • Check lastCreditAllocation timestamp
  • Verify rolloverLimit is set correctly
  • Check invoice.paid webhook is being received

4. Subscription not canceling

  • Verify subscriptionId belongs to the startup
  • Check Stripe subscription status in dashboard
  • Look for errors in StripeEvent table

Database Queries

Check Active Subscriptions:

SELECT
id,
startup_name,
govfit_subscription_status,
govfit_current_period_end
FROM startup_contacts
WHERE govfit_subscription_id IS NOT NULL;

Check Credit Pack Subscriptions:

SELECT
sc.startup_name,
cps.credits,
cps.status,
cps.rollover_limit,
cps.current_period_end
FROM credit_pack_subscriptions cps
JOIN startup_contacts sc ON cps.startup_id = sc.id
WHERE cps.status = 'active';

Check Failed Webhook Events:

SELECT
type,
processing_error,
created_at
FROM stripe_events
WHERE processed = false
OR processing_error IS NOT NULL;

Contact & Support

Implementation Team:

  • Backend Lead: [Your Name]
  • Database: Prisma + PostgreSQL
  • Framework: NestJS 11.x
  • Payment Provider: Stripe

Documentation:


Changelog

December 2, 2024 - Phase 1 Complete

  • ✅ Database schema with Stripe integration
  • ✅ Stripe service with full CRUD operations
  • ✅ Webhook handlers for all payment events
  • ✅ REST API endpoints for subscription management
  • ✅ Subscription access guard
  • ✅ Grace period cron jobs
  • ✅ Credit rollover logic
  • ✅ WebSocket real-time updates
  • ✅ Module registration and integration

End of Implementation Summary