Billing Module
Stripe subscriptions, plans, trials, and webhooks
The Billing module manages all subscription and payment functionality via Stripe. Source: packages/server/src/modules/billing/.
REST Endpoints
All endpoints are prefixed with /api/v1/billing.
| Method | Path | Guards | Description |
|---|---|---|---|
| GET | /api/v1/billing/status | ClerkAuthGuard | Billing status for subscription gate |
| POST | /api/v1/billing/trial | ClerkAuthGuard + @Roles('OWNER', 'ADMIN') | Start 14-day Standard trial |
| POST | /api/v1/billing/checkout | ClerkAuthGuard + @Roles('OWNER', 'ADMIN') | Create Stripe Checkout session |
| POST | /api/v1/billing/downgrade | ClerkAuthGuard + @Roles('OWNER', 'ADMIN') | Downgrade to Free plan |
| POST | /api/v1/billing/portal | ClerkAuthGuard + @Roles('OWNER', 'ADMIN') | Create Stripe Customer Portal session |
| GET | /api/v1/billing/subscription | ClerkAuthGuard | Get subscription details |
| GET | /api/v1/billing/checkout/success | None | Stripe success redirect target |
| GET | /api/v1/billing/checkout/cancel | None | Stripe cancel redirect target |
| POST | /api/v1/billing/webhook | None | Stripe webhook (signature verified) |
Billing Service (services/billing.service.ts)
Plan Lifecycle
NO_PLAN (new org)
│
├──── POST /trial ────▶ TRIALING (14 days, no card)
│ │
│ ├── (trial ends) ──▶ FREE
│ └── POST /checkout ─▶ ACTIVE (paid)
│
└──── POST /checkout ─▶ ACTIVE (paid)
│
└── POST /downgrade ─▶ FREENobody starts on FREE directly. NO_PLAN → trial or paid.
getBillingStatus(orgId)
Used by the web app's activation gate. Returns:
{
hasActivePlan: boolean; // ACTIVE or TRIALING
hadPriorSubscription: boolean; // Was ever on a paid plan
plan: Plan;
status: SubscriptionStatus;
}startTrial(orgId)
Creates a 14-day trial subscription. No card required. Sets plan to STANDARD with status TRIALING. Calls StripeService.createTrialSubscription(...) with customer, price, and trial period configuration.
createCheckoutSession(orgId, plan)
Creates a Stripe Checkout session for the specified plan. Returns a url to redirect the user to. On success, Stripe redirects to GET /billing/checkout/success?session_id=....
downgradeToFree(orgId)
- If currently
TRIALING: cancels the trial immediately and sets plan toFREE. - If currently
ACTIVE(paid): setscancelAtPeriodEnd: truein Stripe — user retains access until the billing period ends.
createPortalSession(orgId)
Creates a Stripe Customer Portal session URL. Allows users to manage payment methods, view invoices, and cancel subscriptions themselves.
getSubscriptionDetails(orgId)
Returns the subscription details:
{
plan: Plan;
status: SubscriptionStatus;
seats: number;
purchasedSeats: number | null;
currentPeriodStart: string | null; // ISO date string
currentPeriodEnd: string | null;
cancelAtPeriodEnd: boolean;
trialEnd: string | null;
managedExternally: boolean; // true for ENTERPRISE/MANUAL
hasPaymentMethod: boolean;
}Plans Configuration (plans.config.ts)
| Plan | Daily Token Limit (per user) | Max Members | Description |
|---|---|---|---|
NO_PLAN | 0 (blocked) | 1 | Pre-activation |
FREE | 1,000,000 | 1 | Post-cancellation |
STANDARD | 2,000,000 | 1 | Default paid plan |
PREMIUM | 10,000,000 | Unlimited | High-usage plan |
ENTERPRISE | 10,000,000 | Unlimited | Custom contract |
Stripe Config (stripe.config.ts)
| Config | Description |
|---|---|
| Trial days | 14 |
| Price IDs | Configured per environment (Standard, Premium) |
| Webhook secret | For signature verification |
Stripe Service (services/stripe.service.ts)
Thin wrapper around the Stripe SDK:
| Method | Description |
|---|---|
createCustomer(orgId, email, name) | Create Stripe customer |
createCheckoutSession(customerId, priceId, metadata) | Checkout session |
createPortalSession(customerId, returnUrl) | Customer portal session |
createTrialSubscription(customerId, priceId, trialDays) | Trial subscription |
cancelSubscription(subscriptionId, immediately?) | Cancel subscription |
updateSubscription(subscriptionId, params) | Update subscription fields |
Stripe Webhook Handler (webhooks/stripe-webhook.handler.ts)
Handles Stripe webhook events at POST /api/v1/billing/webhook. Raw body is preserved for Stripe signature verification.
| Event | Action |
|---|---|
checkout.session.completed | Activate subscription, set plan to STANDARD/PREMIUM |
customer.subscription.updated | Sync status changes |
customer.subscription.deleted | Set plan to FREE |
invoice.payment_failed | Set status to PAST_DUE |
invoice.paid | Clear PAST_DUE, extend period |
Billing Repository (repositories/billing.repository.ts)
All Prisma queries:
findSubscription(orgId)→ current subscriptionupsertSubscription(orgId, data)→ create or updatesetPaymentCustomer(orgId, customerId, provider)→ store payment customer IDfindSubscription(orgId)→ current subscription