Who This Guide Is For
This guide is for SaaS founders, product owners, and technical leads who are building or redesigning the billing system for a subscription software product. You have a product that people pay for on a recurring basis — monthly, annually, or based on usage — and you need to implement the infrastructure that charges customers, manages their subscription state, controls access to features, and handles the messy realities of upgrades, downgrades, failed payments, cancellations, and refunds. You understand your product pricing model conceptually and want to know how to implement it without building a fragile system that breaks every time the pricing changes.
Before You Start
- Billing is not a side project. It touches every part of your product — authentication, feature access, onboarding, customer communication, reporting, and customer success. Treating it as a weekend task leads to technical debt that is expensive to unwind.
- Choose your payment processor before designing the system. Stripe, Paddle, and similar platforms each have opinionated APIs that shape how you model subscriptions, handle tax, and manage customer records. Designing your system abstractly and then discovering your processor does not support a particular pattern wastes time.
- Pricing will change. Your current pricing model will not be your pricing model in two years. Design the billing system to accommodate pricing changes without requiring code changes for every adjustment. Hardcoded pricing is a maintenance burden that slows commercial decisions.
Step 1: Define Your Pricing Model Clearly
Before writing any code, document exactly how customers will be charged. This sounds obvious, but most billing bugs stem from ambiguity in the pricing model rather than from implementation errors.
Flat-rate subscriptions charge a fixed amount per billing period. Every customer pays the same price for the same access. This is the simplest model to implement and the easiest for customers to understand. A single plan at forty pounds per month with annual billing at four hundred pounds per year (offering two months free) is a flat-rate model.
Tiered subscriptions offer multiple plans at different price points with different feature sets or usage limits. A Starter plan, a Professional plan, and an Enterprise plan, each with increasing features and increasing price. The implementation complexity comes from defining what differs between tiers and enforcing those differences throughout the product.
Per-seat pricing charges based on the number of users on the account. Ten users at ten pounds each is one hundred pounds per month. The billing system must track seat count, handle mid-cycle additions and removals (prorating), and enforce seat limits. Per-seat models create a natural relationship between customer growth and billing, but the prorating logic is complex.
Usage-based pricing charges based on consumption — API calls, messages sent, storage used, records processed. This model aligns cost with value (customers pay for what they use) but requires metering infrastructure to track usage, a billing engine that calculates charges from usage data, and a pricing schedule that converts usage into money.
Hybrid models combine elements. A base subscription fee plus usage charges above an included allowance. A per-seat price with usage-based add-ons. These are commercially attractive but technically the most complex because the billing logic must handle multiple charge types within a single invoice.
Document the model with specific examples. If a customer on the Professional plan adds three seats on the fifteenth of the month and uses 12,000 API calls, what should their invoice for that month be? If you cannot calculate the answer manually, the billing system cannot calculate it either.
Step 2: Integrate Your Payment Processor
Your payment processor handles the financial mechanics — charging cards, managing payment methods, handling retries for failed payments, and dealing with compliance requirements like PCI DSS and Strong Customer Authentication. You should not build these capabilities yourself.
Stripe is the most common choice for SaaS billing and the one this guide references for specific implementation patterns. The Subscriptions API manages the recurring billing cycle, including proration for mid-cycle changes, automatic retry logic for failed payments, and webhook notifications for billing events.
The integration architecture follows a clear pattern. Your application creates a Stripe Customer when a user signs up. When they subscribe, your application creates a Stripe Subscription linked to a Stripe Price (which defines the amount, currency, and billing interval). Stripe handles the charging. Your application listens for webhooks to learn about the outcomes — successful payments, failed payments, subscription cancellations, and so on.
Store Stripe identifiers in your database. Every customer record should have a stripe_customer_id. Every subscription record should have a stripe_subscription_id and stripe_price_id. These foreign keys are how your application maps its internal state to the processor state. When a webhook arrives, you look up the local record by the Stripe identifier and update accordingly.
Never trust the client-side for billing state. Subscription status must come from your server, which in turn gets it from Stripe via webhooks. The frontend should never determine whether a user has access to a feature based on locally stored data. A determined user could manipulate client-side state. The server checks the subscription record in your database, which is kept in sync with Stripe via webhooks.
Handle the webhook events that matter. At minimum, process: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed, and customer.subscription.trial_will_end. Each event triggers an update to your local subscription record. Payment failures trigger a dunning flow. Subscription deletions revoke access.
Step 3: Build the Entitlement Layer
Entitlements answer the question: what is this user allowed to do? The subscription determines what plan the user is on. The entitlement layer translates that plan into specific feature access, usage limits, and capability flags throughout the product.
Centralise entitlement checks in a single service. Do not scatter subscription checks across controllers and views. Build an EntitlementService (or equivalent) that accepts a user and a capability and returns a boolean. Every feature gate in the product calls this service. When the pricing model changes, you update the entitlement mappings in one place rather than hunting through the codebase for hardcoded plan checks.
Map plans to capabilities, not features to plans. Define a set of capabilities (can_export_csv, can_use_api, max_team_members, monthly_api_limit) and map each plan to its set of capabilities. The product code checks capabilities, not plan names. This decouples your feature code from your pricing model. When you introduce a new plan or restructure existing ones, you update the capability mappings without touching feature code.
Enforce limits at the point of action. If the Starter plan allows five team members, enforce the limit when a user attempts to add a sixth member — not when they view the team page. The enforcement should return a clear, actionable message: your plan allows five team members, upgrade to Professional for unlimited members. This is both a boundary and a conversion opportunity.
Handle grace periods for failed payments. When a payment fails, you should not immediately revoke access. Stripe retries failed payments on a schedule (typically three attempts over a few weeks). During this period, the subscription is in a past_due state. Your entitlement layer should continue granting access during the retry window while displaying a banner prompting the user to update their payment method. Revoke access only after all retries are exhausted and the subscription moves to canceled or unpaid.
Cache entitlements appropriately. Checking the database on every API request to determine whether the user has access is expensive. Cache the entitlement result per user with a short TTL (five to ten minutes) and invalidate the cache when a subscription webhook updates their record. The slight delay between a subscription change and the cache update is acceptable for most products.
Step 4: Handle Subscription Lifecycle Events
Subscriptions are not static. Customers upgrade, downgrade, pause, cancel, resume, and change payment methods. Each event has billing implications and user experience implications.
Upgrades should take effect immediately. When a customer moves from Starter to Professional, they expect immediate access to the new features. The billing change — prorating the remainder of the current period and charging the difference — should happen transparently. Stripe handles proration automatically when you update the subscription price. Your application updates the local subscription record and refreshes entitlements.
Downgrades are more complex. If a customer moves from Professional to Starter but is currently using Professional-only features (they have eight team members but Starter allows five), you need a policy. Options include: applying the downgrade at the end of the current billing period (giving them time to reduce usage), applying immediately with a grace period to comply, or blocking the downgrade until usage is within the new plan limits. Choose one and implement it consistently.
Cancellations should not be instant by default. Best practice is to cancel at the end of the current billing period — the customer has paid for the full period and should retain access until it ends. The subscription record should show a cancels_at date so the user knows when access will end and that they can reactivate before then. This reduces involuntary churn by giving customers time to reconsider.
Failed payment dunning is a workflow, not a single event. When a payment fails: send a notification explaining the issue and asking the user to update their payment method. Continue to allow access. When the second retry fails: send a more urgent notification. When the final retry fails: notify the user that access will be revoked, then revoke it. Each step is triggered by a Stripe webhook event and handled by your application.
Reactivation should be as frictionless as possible. A customer who cancelled and wants to return should be able to resubscribe in a few clicks. Do not make them re-enter all their details. Their Stripe Customer record still exists, their payment method may still be valid, and their account data is still in your database. Reactivation is a new subscription creation against an existing customer, not a new signup.
Step 5: Build the Billing Experience
The billing experience is every interaction a customer has with their subscription — viewing their plan, changing it, managing payment methods, viewing invoices, and understanding what they are paying for.
A self-service billing portal is essential. Customers should be able to view their current plan, see upcoming invoice amounts, update their payment method, download past invoices, and change or cancel their plan without contacting support. Stripe Customer Portal provides a hosted version of this that covers the basics. For more control, build your own billing page that uses the Stripe API to display and manage subscription data.
Invoices should be accessible and clear. Every payment should generate a downloadable invoice that shows what was charged, the billing period, and the line items. For businesses buying your product, these invoices are required for their accounting. Stripe generates invoices automatically for subscription payments. Ensure the invoice includes your company details, the customer details, and complies with the invoicing requirements of your jurisdiction.
Usage visibility matters for usage-based or hybrid models. If a customer is charged based on API calls, they need a way to see their current usage, their included allowance, and their projected bill. A usage dashboard that updates in near real-time prevents bill shock and builds trust. Customers who understand their bill are less likely to churn over pricing.
Pricing page consistency is often overlooked. The pricing page on your marketing site, the upgrade prompts in the product, and the billing portal should all show the same prices, features, and plan names. Inconsistencies erode trust. When prices change, update all surfaces simultaneously.
Common Mistakes
- Building billing logic before choosing a payment processor. Stripe, Paddle, and others have strong opinions about how subscriptions work. Designing your billing model in the abstract and then discovering your processor does not support it wastes weeks.
- Hardcoding plan names in application code. Checking if a user plan equals a specific string throughout the codebase means every pricing change requires a code deployment. Use an entitlement service that maps plans to capabilities.
- Ignoring proration. Mid-cycle plan changes without proration lead to either overcharging or undercharging customers. Both are problems. Use your processor built-in proration logic.
- Not handling webhook failures. If a webhook delivery fails and you do not process it, your local subscription state diverges from Stripe state. Implement idempotent webhook handlers with retry logic and monitor for unprocessed events.
- Forgetting about tax. VAT, sales tax, and GST requirements vary by jurisdiction and customer type. Stripe Tax or a dedicated tax service handles the complexity. Do not implement tax calculation yourself.
What Good Looks Like
A well-implemented billing system charges customers accurately, handles lifecycle events gracefully, and requires zero manual intervention for routine operations. Customers can manage their own subscriptions without contacting support. Entitlements are enforced consistently across the product. Failed payments trigger an automated dunning flow that recovers revenue without human involvement. Pricing changes are configuration changes, not code changes. The billing data feeds into financial reporting and customer health dashboards. The system has been running for months and the last time someone thought about billing was when they changed the pricing — and even that was a fifteen-minute configuration update.
Next Steps
For the technical implementation of Stripe payment infrastructure, see How to Integrate Stripe Payments. For building the client portal where customers manage their billing, How to Plan a Client Portal covers the approach. For the broader product architecture that houses the billing system, How to Define Requirements for a SaaS Product covers the planning phase. To discuss billing architecture for your product, get in touch.