Cosmo Docs
Server

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.

MethodPathGuardsDescription
GET/api/v1/billing/statusClerkAuthGuardBilling status for subscription gate
POST/api/v1/billing/trialClerkAuthGuard + @Roles('OWNER', 'ADMIN')Start 14-day Standard trial
POST/api/v1/billing/checkoutClerkAuthGuard + @Roles('OWNER', 'ADMIN')Create Stripe Checkout session
POST/api/v1/billing/downgradeClerkAuthGuard + @Roles('OWNER', 'ADMIN')Downgrade to Free plan
POST/api/v1/billing/portalClerkAuthGuard + @Roles('OWNER', 'ADMIN')Create Stripe Customer Portal session
GET/api/v1/billing/subscriptionClerkAuthGuardGet subscription details
GET/api/v1/billing/checkout/successNoneStripe success redirect target
GET/api/v1/billing/checkout/cancelNoneStripe cancel redirect target
POST/api/v1/billing/webhookNoneStripe 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 ─▶ FREE

Nobody 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 to FREE.
  • If currently ACTIVE (paid): sets cancelAtPeriodEnd: true in 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)

PlanDaily Token Limit (per user)Max MembersDescription
NO_PLAN0 (blocked)1Pre-activation
FREE1,000,0001Post-cancellation
STANDARD2,000,0001Default paid plan
PREMIUM10,000,000UnlimitedHigh-usage plan
ENTERPRISE10,000,000UnlimitedCustom contract

Stripe Config (stripe.config.ts)

ConfigDescription
Trial days14
Price IDsConfigured per environment (Standard, Premium)
Webhook secretFor signature verification

Stripe Service (services/stripe.service.ts)

Thin wrapper around the Stripe SDK:

MethodDescription
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.

EventAction
checkout.session.completedActivate subscription, set plan to STANDARD/PREMIUM
customer.subscription.updatedSync status changes
customer.subscription.deletedSet plan to FREE
invoice.payment_failedSet status to PAST_DUE
invoice.paidClear PAST_DUE, extend period

Billing Repository (repositories/billing.repository.ts)

All Prisma queries:

  • findSubscription(orgId) → current subscription
  • upsertSubscription(orgId, data) → create or update
  • setPaymentCustomer(orgId, customerId, provider) → store payment customer ID
  • findSubscription(orgId) → current subscription