Skip to main content
Engineering7 min readOctober 15, 2025

Building SaaS Billing with Stripe: Beyond the Basics

Advanced SaaS billing with Stripe — metered billing, proration, plan changes, dunning, tax automation, and the edge cases that trip up growing SaaS products.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

The basic Stripe subscription integration is well-documented: create a customer, attach a payment method, subscribe them to a price. Getting that working takes a day. Everything after that — plan changes, metered billing, proration, dunning, tax compliance — is where SaaS billing becomes genuinely complex.

I have built billing systems for SaaS products at various stages. The basic integration handles the happy path. This article walks through everything else.

Beyond Simple Subscriptions

Most SaaS products outgrow simple fixed-price subscriptions quickly. Users want to upgrade, downgrade, add seats, and pay for what they use. Each of these operations has subtleties.

Plan changes require deciding how to handle the money. When a user upgrades mid-cycle from a $50/month plan to a $100/month plan, do you charge the full $100 immediately, prorate the remaining days, or wait until the next billing cycle? Stripe supports all three approaches through the proration_behavior parameter on subscription updates.

Proration is the most common approach and the one users expect. Stripe calculates the unused time on the old plan as a credit and the remaining time on the new plan as a charge, then applies the difference to the next invoice. This is technically correct but confusing to users who see a partial charge on their statement. Explain proration clearly in your UI and in the invoice emails.

Seat-based pricing adds complexity because the quantity changes over time. When a team adds members, the subscription quantity increases. When members leave, it decreases. Stripe handles quantity changes with proration, but you need to decide the business rules: Can users add seats at any time? Is there a minimum? Can they remove seats mid-cycle, or only at renewal?

Implement seat management through your application layer, not just Stripe. Track which users occupy which seats, enforce limits based on the subscription quantity, and update Stripe when the seat count changes. Your user management system should integrate tightly with seat-based billing.

Metered and Usage-Based Billing

Usage-based billing charges customers for what they consume — API calls, storage, compute minutes, messages sent. Stripe's metered billing records usage throughout the billing period and generates an invoice at the end.

Report usage to Stripe through the Usage Records API. Each usage record includes a timestamp, a quantity, and an action (set or increment). I recommend incremental reporting — send usage events as they occur rather than calculating totals at billing time. This provides better visibility and is more resilient to timing issues.

Architecture the usage tracking as a separate pipeline from your main application. Usage events are high-volume and should not block your primary API. Emit usage events to a queue, process them in batches, and report to Stripe asynchronously. If the usage pipeline fails, buffer events and catch up later — Stripe accepts backdated usage records.

Set up usage alerts and limits. Stripe does not enforce usage limits — it happily bills for unlimited usage. Your application needs to enforce limits for capped plans and send alerts when users approach their allocation. Nobody wants a surprise $10,000 invoice because an API integration ran away.

Provide real-time usage dashboards. Users should be able to see their current usage at any time, not just on the invoice at the end of the month. Cache the current period's usage in your database and update it as usage events are processed. The analytics dashboard patterns for usage data help users understand and manage their consumption.

Dunning and Payment Recovery

Failed payments are inevitable. Credit cards expire, spending limits are reached, and bank accounts have insufficient funds. How you handle failed payments — the dunning process — directly affects your churn rate.

Stripe's Smart Retries automatically retry failed payments with optimized timing. Enable this in your Stripe dashboard. But automated retries are only part of the solution.

Send clear, non-alarmist email notifications when a payment fails. The first email should be informational: "Your payment failed, we'll retry automatically." Include a link to update payment information. Subsequent emails should increase urgency as the grace period progresses.

Define a grace period before access restriction. I typically use a 7-14 day grace period with escalating notifications:

  • Day 0: Payment fails. Send informational email. Full access continues.
  • Day 3: If still failing, send reminder with update payment link.
  • Day 7: Send urgent notification. Consider restricting some features.
  • Day 14: Restrict to read-only access. Send final notice.
  • Day 21: Pause the subscription.

Never delete data when a subscription lapses. Users who eventually update their payment should return to exactly the state they left. Data deletion should only happen after a defined retention period, with advance notice.

Build a payment update flow within your app. Stripe's Customer Portal provides a hosted solution for subscription and payment management. For more control, build a custom flow using Stripe's Setup Intents to collect new payment methods securely.

Tax Compliance

Tax on SaaS subscriptions is complex and varies by jurisdiction. Some states charge sales tax on SaaS, others do not. The EU requires VAT on digital services. Tax rules change frequently.

Stripe Tax automates tax calculation and collection. Enable it on your Stripe account, configure your tax settings, and Stripe calculates the appropriate tax for each transaction based on the customer's location. This handles the vast majority of tax scenarios for SaaS products.

Collect customer location information — at minimum, country and postal code — to enable accurate tax calculation. For B2B customers in the EU, collect the VAT ID for reverse-charge mechanisms that exempt the transaction from VAT.

Tax invoices must include specific information depending on the jurisdiction — your tax ID, the customer's details, tax rates, and amounts. Stripe generates compliant invoices, but verify they meet your obligations in your primary markets.

If Stripe Tax does not cover your needs, services like Avalara and TaxJar provide more comprehensive tax automation. These integrate with Stripe and handle edge cases like nexus determination and tax filing. The Stripe subscription guide covers the foundational billing integration that these tax tools build on.

Keep billing simple for as long as possible. Every pricing dimension, discount type, and billing variation adds code complexity and support burden. Start with straightforward monthly or annual pricing. Add usage-based components when customer feedback demands it. Build complexity incrementally, and test every billing scenario — especially the edge cases around plan changes, upgrades during trials, and payments failing mid-proration — before they reach production.