The Complete SaaS Cookbook, Part 3: Billing, Subscriptions & Payment Integration
Home/News/Article
SaaSJanuary 8, 2026

The Complete SaaS Cookbook, Part 3: Billing, Subscriptions & Payment Integration

The complete guide to implementing subscription billing, usage-based pricing, free trials, upgrades, downgrades, and everything in between.

E

Engineering Team

Author

15 min read
BillingIntegration

Why Billing Is the Hardest Part

Billing seems simple until you start building it. Then you discover proration, failed payment retries, tax calculation, invoice generation, plan changes mid-cycle, usage-based add-ons, and the dozen edge cases that make experienced developers lose sleep.

The golden rule: never build billing from scratch. Use Stripe, Paddle, or LemonSqueezy as your billing engine and focus your engineering effort on the integration, not the payment processing.

Choosing Your Payment Provider

Each provider has distinct strengths:

  • Stripe: Most flexible. Best for custom billing logic, usage-based pricing, and complex enterprise scenarios. You handle tax calculation (use Stripe Tax) and invoicing. Highest developer effort but maximum control
  • Paddle: Merchant of Record — they handle VAT, sales tax, and invoicing globally. Lower development effort, but less flexibility for custom billing scenarios. Ideal for bootstrapped SaaS selling to global customers
  • LemonSqueezy: Similar MoR model to Paddle, developer-friendly, good for indie SaaS. Less mature for enterprise billing scenarios

The Subscription Data Model

Your database needs these core entities:

Plans
  ├─ id, name, stripe_price_id
  ├─ features (JSON: what's included)
  └─ limits (JSON: usage caps)

Subscriptions
  ├─ id, tenant_id, plan_id
  ├─ stripe_subscription_id
  ├─ status (active, past_due, canceled, trialing)
  ├─ current_period_start, current_period_end
  └─ cancel_at_period_end

Usage Records (if usage-based)
  ├─ id, tenant_id, metric, quantity
  └─ recorded_at

Critical: Your local database is a cache of Stripe's data, not the source of truth. Always sync from Stripe webhooks, never from API responses alone.

Webhook Architecture

Webhooks are the backbone of SaaS billing. Stripe sends events for every significant change, and your application must handle them reliably:

// Essential webhooks to handle
checkout.session.completed    → Provision access
customer.subscription.updated → Sync plan changes
customer.subscription.deleted → Revoke access
invoice.payment_failed        → Notify user, grace period
invoice.paid                  → Update payment status

Webhook processing rules:

  • Idempotent handlers: Stripe may send the same event multiple times. Your handlers must produce the same result regardless of how many times they run
  • Verify signatures: Always validate webhook signatures. Never trust unverified webhook payloads
  • Process asynchronously: Return 200 immediately, process the event in a background job. Stripe will retry if your endpoint times out
  • Store raw events: Log every webhook payload. When billing bugs occur (and they will), these logs are invaluable for debugging

Free Trials That Convert

The typical SaaS trial flow:

  1. No credit card trial (14 days): Lower friction to start. User explores the product freely
  2. Trial ending notification (day 11): Email with clear value summary and upgrade prompt
  3. Trial expired: Account enters read-only mode. Data preserved. Clear upgrade path
  4. Grace period (7 days): User can still upgrade and retain all data. After grace period, data archived

Track trial engagement metrics: features used, sessions per day, team members invited. Users who invite teammates during trial convert at 3-5x the rate of solo users.

Handling Plan Changes

Plan changes mid-billing-cycle are where most billing implementations break:

  • Upgrades: Prorate immediately. Charge the difference for the remaining period and switch to the new plan
  • Downgrades: Apply at period end. User keeps current plan features until the billing cycle completes, then switches to the lower plan
  • Cancellations: Always cancel at period end, never immediately. Users who "cancel" often change their minds before the period ends

Stripe handles proration automatically if configured correctly. Don't build your own proration logic.

Feature Gating

Your application needs a clean way to check whether a tenant has access to specific features:

// Clean feature gating
if (await tenant.hasFeature('advanced-analytics')) {
  // Show advanced analytics
} else {
  // Show upgrade prompt
}

// Usage limit checking
const usage = await tenant.getUsage('api-calls');
if (usage >= tenant.plan.limits.apiCalls) {
  // Return 429 with upgrade prompt
}

Store feature flags and limits on the Plan model, not hardcoded in application logic. This makes it trivial to create new plans or adjust existing ones without code changes.

In Part 4, we'll cover the growth infrastructure: analytics, onboarding flows, and retention mechanics that turn trial users into long-term customers.

Share this article

Newsletter

Enjoyed this article?

Subscribe to get our latest insights on enterprise tech and digital transformation.