# Stripe Billing for SaaS: Subscriptions, Usage-Based Pricing, and the Edge Cases That Break Things — Hunchbite

> A practical guide to implementing Stripe billing in a SaaS product — covering subscription models, metered usage, free trials, dunning, proration, and the specific edge cases that break billing implementations and cause revenue leakage.

Source: https://hunchbite.com/guides/stripe-billing-saas

[Home](https://hunchbite.com/)/[Guides](https://hunchbite.com/guides)/[Technology Decisions](https://hunchbite.com/guides/topic/technology-decisions)/Stripe Billing for SaaS: Subscriptions, Usage-Based Pricing, and the Edge Cases That Break Things

[Technology Decisions](https://hunchbite.com/guides/topic/technology-decisions)

# Stripe Billing for SaaS: Subscriptions, Usage-Based Pricing, and the Edge Cases That Break Things

A practical guide to implementing Stripe billing in a SaaS product — covering subscription models, metered usage, free trials, dunning, proration, and the specific edge cases that break billing implementations and cause revenue leakage.

By HunchbiteMarch 30, 202611 min read

StripebillingSaaS

Billing is the one part of your product that directly touches revenue. Get it wrong and you lose money silently — failed payments that never retry, plan changes that prorate incorrectly, trials that don't convert because the confirmation email wasn't sent. Unlike a broken feature that users complain about, broken billing often fails without visible symptoms until you audit your numbers months later.

This guide covers the implementation patterns that work and the specific edge cases that break billing in production. Not a tour of the Stripe documentation — Stripe's docs are excellent. This is what you need to know that the docs don't emphasize.

## The 3 billing models and their Stripe implementation

### 1\. Flat-rate subscription

One price, recurring monthly or annually. The simplest model and still the most common in B2B SaaS.

In Stripe: a `Product` represents what you're selling, a `Price` represents the billing interval and amount, and a `Subscription` ties a `Customer` to a `Price`. The implementation is straightforward: create the subscription when the customer completes signup, handle the `customer.subscription.created` webhook to provision access, and listen for `invoice.payment_failed` to handle payment failures.

The edge cases still exist even here. Trials, plan changes, and failed payments all need explicit handling — which we'll cover below.

### 2\. Per-seat pricing

A recurring subscription where the quantity scales with the number of users. The price is per seat per month; the subscription quantity is the current seat count.

In Stripe: use `Subscription` with a `quantity` parameter. When a customer adds a user, call `stripe.subscriptions.update` to increment the quantity. When they remove a user, decrement it. Stripe prorates the difference automatically (if you configure it to).

The subtlety: decide whether seat additions bill immediately (prorated charge for the remainder of the current billing period) or at the start of the next period. Most SaaS products charge immediately for additions (you're unlocking access now) and bill removals at the end of the period (you've already paid for this month). This needs to be explicit in both your Stripe configuration and your customer-facing terms.

### 3\. Usage-based / metered billing

The customer pays for what they consume — API calls, active users, compute time, storage. The price is unknown until the end of the billing period.

In Stripe: use Stripe Meters. Define the meter (what unit you're counting), report usage events via `stripe.billing.meters.createEvent()` as they happen in your application, and attach the meter to a price in your subscription. Stripe aggregates usage per billing period and generates the invoice automatically.

This is the most complex model to implement correctly. The reporting pipeline needs to be reliable — usage events that fail to reach Stripe mean you don't bill for that usage. A queue-based approach (write usage to your database, a background job sends it to Stripe, with retries on failure) is more reliable than reporting usage inline in the request path.

## The webhook architecture

Webhooks are the foundation of correct billing. Not an integration option — the core mechanism.

Stripe webhooks are HTTP POST requests that Stripe sends to your application when billing events happen. Payment succeeds, subscription cancels, invoice generates, trial ends — these all arrive as webhooks. If your application doesn't handle them, billing state in your database falls out of sync with billing state in Stripe. If you're new to building webhook consumers, our guide on how webhooks work covers the signature verification, idempotency, and retry behavior the patterns below rely on.

The architecture that works in production:

1. **One webhook endpoint.** All Stripe events go to `/api/webhooks/stripe`. Verify the signature on every request. Return `200` immediately.
2. **Queue, don't process inline.** Enqueue the event payload, process it asynchronously. This decouples webhook receipt from processing, handles slow database operations, and survives application restarts mid-processing.
3. **Idempotent handlers.** Stripe retries webhooks that don't receive a `200` response (or receive a `5xx`). Your handlers will receive the same event multiple times. Use the Stripe event ID as an idempotency key — check whether you've already processed this event before doing work.
4. **Dead-letter logging.** Events that fail processing after N retries go to a dead-letter queue or alert channel. Don't silently drop them.

The events you must handle without exception: `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_succeeded`, `invoice.payment_failed`, and `customer.subscription.trial_will_end`.

## The 8 edge cases that break billing implementations

### 1\. Failed payment handling

A payment fails. What happens next?

Stripe has built-in retry logic (Smart Retries — it uses ML to pick retry times with higher success rates). Configure this in your Stripe dashboard. But retries alone aren't enough. You need a dunning flow: a sequence of emails over the retry window telling the customer their payment failed and asking them to update their card.

The hard part is access revocation. Do you immediately revoke access when payment fails, or after the retry window expires? Most SaaS products give a grace period (7–14 days) before revoking access, with escalating communication. This needs to be explicit in your code — the `invoice.payment_failed` webhook starts the grace period clock; the `customer.subscription.deleted` event (after Stripe finally gives up) ends it.

Without handling this explicitly, failed payments either silently accumulate (customer never hears from you, keeps using the product for free) or immediately revoke access on a transient payment failure (aggressive, causes churn).

### 2\. Plan upgrades mid-cycle

A customer on your $49/month plan upgrades to the $99/month plan on day 15 of their billing cycle.

Stripe can handle proration automatically. But you need to decide: charge the prorated difference immediately, or apply it to the next invoice? The Stripe default is immediate proration with an immediate charge. Some SaaS products prefer to credit the unused portion of the current plan and apply it to the next invoice instead.

Set `proration_behavior` explicitly when updating subscriptions. The options are `create_prorations` (immediate charge), `none` (no proration, full new price next cycle), or `always_invoice` (creates a proration invoice immediately).

### 3\. Downgrades

A customer downgrades from $99 to $49\. Unlike upgrades, immediate access reduction is usually wrong — they paid for the full month at the higher tier.

Set `proration_behavior: 'none'` on downgrades and schedule the price change to take effect at the end of the current billing period using `billing_cycle_anchor` or subscription schedule items. The customer keeps their current plan until renewal, then drops to the lower tier.

If you implement this incorrectly, you either credit the customer nothing on the downgrade (charging them for features they no longer have access to) or issue an immediate credit and immediately revoke access (which feels punishing).

### 4\. Trial-to-paid conversion

Free trials are standard SaaS practice. The webhook you must not miss: `customer.subscription.trial_will_end` — Stripe sends this 3 days before the trial ends. Use it to send a conversion email.

When the trial ends, Stripe automatically attempts to charge the payment method on file. If none exists, the subscription goes into `incomplete` status. You need to handle this: prompt for payment before trial end, and handle the state where trial expired without a payment method.

The `invoice.payment_succeeded` event after trial expiry is your signal to move the customer from trial to active status in your database. Do not rely on the subscription status field alone for this — check the invoice.

### 5\. Annual to monthly switches

A customer on annual billing wants to switch to monthly. This is operationally messy.

They've paid for the full year. Do you refund the unused months and switch immediately? Or run out the year and switch at renewal? Most SaaS products do the latter (simpler, less accounting). Be explicit in your UI about which they're getting.

If you do immediate switches, you're looking at partial refunds via `stripe.refunds.create` and a new subscription. The Stripe subscription objects don't cleanly handle mid-year plan interval changes — you often end up canceling and recreating the subscription. Test this flow thoroughly.

### 6\. Seat addition and removal

Per-seat products have this constantly. A team admin adds 3 users and removes 2 in the same week.

Use Stripe's `quantity` update. But track the subscription quantity in your database independently so you can reconcile. If a webhook fails to deliver and your database says 10 seats but Stripe says 8, the next time you try to update quantity you'll send an incorrect value.

Keep your source of truth in Stripe for billing, your database for access control, and a reconciliation job that runs daily to detect divergence.

### 7\. Refunds and credits

Stripe refunds are straightforward via `stripe.refunds.create`. The complication is how you represent them in your product and your accounting.

For partial refunds (discounts, goodwill credits), prefer Stripe Customer Balance or Stripe Coupons over manual refunds. They apply cleanly to future invoices and don't create complex reconciliation problems.

For full refunds on an annual plan where the customer wants out early, decide your policy before you need it. "No refunds after 14 days" or "prorated refunds" — either is fine, but it needs to be in the code before a customer asks.

### 8\. Invoice item reconciliation

Every Stripe invoice has line items. Those line items need to be reflected in your database — not just "customer paid $99" but what they paid for, when, and what period it covers.

This matters for: support ("did this charge cover their January billing period?"), accounting (revenue recognition), and analytics (MRR calculations). Store the Stripe invoice ID, subscription ID, period start, period end, and line items. The `invoice.payment_succeeded` webhook gives you everything you need.

## Stripe vs. Paddle vs. Lago vs. Orb

| Tool               | Use when                                                                                                    | Avoid when                                                       |
| ------------------ | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| **Stripe Billing** | Your primary billing for almost all SaaS                                                                    | You need complex usage models that Stripe Meters can't represent |
| **Paddle**         | You need a merchant of record (Paddle handles tax, compliance, payouts for 200+ countries)                  | You're US-only and comfortable handling your own tax logic       |
| **Lago**           | Open-source billing, complex usage pricing is your core model, you want to self-host billing infrastructure | You don't want to maintain billing infrastructure                |
| **Orb**            | Usage-based is your primary model and you need flexible aggregation logic (sum, max, unique count, custom)  | Flat-rate subscriptions — overkill and more expensive            |

For most SaaS products: Stripe Billing. Paddle if you're selling globally and want tax handled for you. Lago or Orb only if usage-based pricing is genuinely complex (multiple meters, tiers, custom aggregation) and Stripe Meters can't model it.

## Testing billing properly

The most common mistake: testing billing manually through the Stripe dashboard. This doesn't scale and doesn't catch the edge cases.

**Stripe test mode** gives you test card numbers, test webhooks, and test customers. All your CI tests should run in test mode with a test Stripe account. Use the Stripe CLI (`stripe listen --forward-to localhost:3000/api/webhooks/stripe`) to forward webhooks to your local development environment. In production, that same endpoint needs reliable hosting — where you deploy it ([Vercel, AWS, or Railway](https://hunchbite.com/guides/vercel-vs-aws-deployment)) affects webhook delivery reliability.

**Test Clock** is the feature most billing implementations don't use but should. A Test Clock is a simulated time object you attach to a test customer. You can advance it forward — "simulate that 30 days have passed" — and watch subscription renewals, trial expirations, and invoice generation happen in your test environment. This is the only reliable way to test time-dependent billing flows without waiting for them to occur naturally.

Cover these scenarios in your test suite with Test Clocks:

* Successful subscription creation and first payment
* Failed payment and retry
* Trial to paid conversion (with and without payment method on file)
* Plan upgrade mid-cycle
* Plan downgrade (effective next period)
* Subscription cancellation and access revocation

---

For the full picture of what goes into a B2B SaaS MVP beyond billing — including architecture, multi-tenancy, and what to build in which order — see [building a B2B SaaS MVP](https://hunchbite.com/guides/b2b-saas-mvp-development). For multi-tenant architecture specifically, [multi-tenant SaaS architecture](https://hunchbite.com/guides/multi-tenant-saas-architecture) covers the row-level security vs. schema-per-tenant decision.

## Building a SaaS and need billing done right the first time?

Billing is the part most development teams underestimate. Hunchbite builds SaaS products with Stripe Billing implemented correctly — subscription management, usage tracking, dunning flows, and webhook architecture that doesn't leak revenue.

See how we work as a [SaaS development company](https://hunchbite.com/saas-development-company), billing included.

**[→ SaaS Development Company](https://hunchbite.com/saas-development-company)**

Call [+91 90358 61690](tel:+919035861690) · [Book a free call](https://cal.com/hunchbite/discovery) · [Contact form](https://hunchbite.com/contact)

Ask an AI assistant about this guide: [ChatGPT](https://chatgpt.com/?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.&hints=search) · [Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.) · [Perplexity](https://www.perplexity.ai/search?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.)

On this page

* [The 3 billing models and their Stripe implementation](#the-3-billing-models-and-their-stripe-implementation)
* [1\. Flat-rate subscription](#1-flat-rate-subscription)
* [2\. Per-seat pricing](#2-per-seat-pricing)
* [3\. Usage-based / metered billing](#3-usage-based--metered-billing)
* [The webhook architecture](#the-webhook-architecture)
* [The 8 edge cases that break billing implementations](#the-8-edge-cases-that-break-billing-implementations)
* [1\. Failed payment handling](#1-failed-payment-handling)
* [2\. Plan upgrades mid-cycle](#2-plan-upgrades-mid-cycle)
* [3\. Downgrades](#3-downgrades)
* [4\. Trial-to-paid conversion](#4-trial-to-paid-conversion)
* [5\. Annual to monthly switches](#5-annual-to-monthly-switches)
* [6\. Seat addition and removal](#6-seat-addition-and-removal)
* [7\. Refunds and credits](#7-refunds-and-credits)
* [8\. Invoice item reconciliation](#8-invoice-item-reconciliation)
* [Stripe vs. Paddle vs. Lago vs. Orb](#stripe-vs-paddle-vs-lago-vs-orb)
* [Testing billing properly](#testing-billing-properly)
* [Building a SaaS and need billing done right the first time?](#building-a-saas-and-need-billing-done-right-the-first-time)

Ask AI about this guide

* [Open in ChatGPT](https://chatgpt.com/?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.&hints=search)
* [Open in Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.)
* [Open in Perplexity](https://www.perplexity.ai/search?q=Read%20https%3A%2F%2Fhunchbite.com%2Fguides%2Fstripe-billing-saas%20and%20help%20me%20apply%20it%20to%20my%20situation.%20Answer%20my%20questions%20about%20it.)

FAQ

Should I use Stripe Billing or just Stripe Payments with my own subscription logic?

Use Stripe Billing. Building your own subscription logic on top of Stripe Payments sounds simpler until you're three months in and implementing proration, dunning retry schedules, invoice line item reconciliation, and trial-to-paid conversion logic. Stripe Billing handles all of this and has been hardened against the edge cases that will break your custom implementation. The only reason to avoid Stripe Billing is if your pricing model is so custom that Stripe's objects can't represent it — which is rare. For 95% of SaaS pricing models, Stripe Billing is the correct choice.

How do I handle Stripe webhooks reliably in Next.js?

Three requirements: verify the webhook signature using Stripe's SDK (stripe.webhooks.constructEvent with the raw request body and your webhook secret), make every webhook handler idempotent (Stripe can deliver the same event multiple times), and process webhooks asynchronously — acknowledge receipt immediately with a 200 response, then process the event in a background job. In Next.js App Router, create a route handler at app/api/webhooks/stripe/route.ts, use the raw body (not the parsed JSON), verify the signature, enqueue the event, and return 200\. Never do synchronous database writes in the webhook handler if they might fail or time out.

What's the best way to implement usage-based pricing with Stripe?

Use Stripe Meters (the current recommended approach, which replaced legacy metered billing). Define a meter for your billable unit (API calls, active seats, GB stored), report usage events via the Stripe Meters API as they occur in your application, and attach the meter to a price on your subscription. Stripe aggregates usage and automatically bills at the end of the billing period. The key implementation detail: report usage events reliably — use a queue or background job for usage reporting, don't do it inline in the request path, and handle failures with retries. Lost usage events mean lost revenue.

Weighing a technical decision?

## Get a second opinion before you commit.

Stack choices, architecture trade-offs, build-vs-buy — a 30-minute call with senior engineers can save you months. No sales pitch, just a straight answer.

[Book a Free Call](https://cal.com/hunchbite/discovery)[Have Us Build It](https://hunchbite.com/custom-software-development-company)

Trusted by VMAC Industries, TKD Logistics, Astitva Jewellery & more. [See our recent work →](https://hunchbite.com/case-studies)

Fixed-price, no hourly billing · No obligation · We tell you upfront if we're not a fit

Continue Reading

[Technology DecisionsMulti-Tenant SaaS Architecture: Row-Level Security vs. Schema-Per-TenantThe two main approaches to multi-tenancy in SaaS products — row-level security and schema-per-tenant — with an honest comparison of when each makes sense, performance characteristics, migration costs, and the decision framework senior teams actually use.12 min read](https://hunchbite.com/guides/multi-tenant-saas-architecture)

[Technology DecisionsWhen to Get SOC 2 Certified: The Decision Guide for SaaS FoundersA clear-eyed guide for SaaS founders deciding whether — and when — to pursue SOC 2 certification. What it costs, how long it takes, and the question you have to answer honestly before you start.11 min read](https://hunchbite.com/guides/when-to-get-soc2-certified)

[All Guides](https://hunchbite.com/guides)
