Billing and Stripe
Billing is split between API entry points, tRPC procedures, and a dedicated service layer.
This feature is designed so Stripe orchestration, customer ownership checks, and long-lived subscription state can evolve without forcing billing logic into route handlers or UI components.
Need implementation details?
Use this page as the feature overview, then continue with Configure Stripe or Extend Pricing when you want to change billing behavior.
What this feature includes
Multiple entrypoints
Billing is exposed through both route handlers and typed procedures depending on the kind of interaction.
Service-layer orchestration
Checkout creation, customer linking, subscription mapping, and webhook processing live in focused service modules.
Webhook synchronization
Stripe events remain the long-lived source of truth for subscription and payment state changes.
Entry points
The current template exposes billing behavior through:
src/app/api/stripe/checkout/route.tssrc/app/api/stripe/webhook/route.tssrc/server/api/routers/stripe.ts
These entrypoints separate browser-driven actions from provider-driven events.
Service layer
Billing logic is decomposed into focused service modules:
src/server/services/billing/stripe-client.tssrc/server/services/billing/checkout.tssrc/server/services/billing/customer.tssrc/server/services/billing/webhook-sync.ts
This separation keeps Stripe orchestration out of route handlers and makes billing easier to evolve.
Supported flows
The current architecture supports:
- one-time payments
- subscription checkout
- customer creation and ownership checks
- subscription upgrades
- subscription cancellation
- invoice and payment method retrieval
- webhook-based synchronization
Ownership and safety
Protected Stripe operations are tied back to the authenticated user.
Examples:
- customer access is verified before updates
- payment intents are checked against
metadata.userId - subscription flows update local user and subscription records
This reduces the risk of billing state drifting away from actual application ownership.
Pricing and tiers
Tier mapping depends on the configured Stripe price IDs in src/env.js.
Current tier-related environment variables:
STRIPE_PRICE_BASIC_IDSTRIPE_PRICE_PRO_IDSTRIPE_PRICE_ENTERPRISE_IDSTRIPE_PRICE_ONE_TIME_ID
Tier mapping from Stripe price IDs to application tiers is handled in webhook-sync.ts via the webhook route.
Extension guidance
When adding new billing logic:
- keep raw Stripe client setup in
stripe-client.ts - add reusable domain logic to
src/server/services/billing - keep tRPC procedures thin and focused on input/output contracts
- keep webhook event mapping centralized instead of scattering it across routes
Billing boundary
Keep route handlers thin, procedures focused, and Stripe orchestration inside the billing services layer. That gives you safer room to evolve pricing and subscription behavior later.