DEV Community

Mohamed Sadok
Mohamed Sadok

Posted on

How We Built a Production-Ready SaaS Billing Engine (And How You Can Ship Yours Today)


The complete blueprint for handling subscriptions, customer billing portals, and edge-case webhooks without losing your sanity.*


Every developer building a SaaS eventually runs into the same wall: Monetization Architecture.

Building the core features of an application is exciting. Writing billing logic, managing recurring cycles, handling failed credit card charges, generating PDFs, and wiring up webhook endpoints is usually where momentum slows down.

In this article, we'll walk through the architecture of a production-ready SaaS billing engine and examine the critical patterns needed to accept recurring payments reliably.


1. The Billing State Problem

Handling payments is not just a single API call to charges.create(). A production billing system must manage state transitions across multiple scenarios:

  • What happens when a renewal payment fails at 3:00 AM?
  • How do you handle plan upgrades with prorated charges mid-cycle?
  • What happens if a user cancels their subscription before the cycle ends?

The Core Subscription State Machine

A resilient subscription model requires tracking distinct statuses:

  • trialing
  • active
  • past_due
  • canceled
  • incomplete
[ Checkout ] ──► [ Active ] ──► (Cycle Renewal)
                      │                │
                      ▼                ▼
                 [ Canceled ]    [ Past Due ] ──► [ Inactive ]
Enter fullscreen mode Exit fullscreen mode

2. Webhooks: The Single Source of Truth

Never rely solely on client-side redirect URLs (e.g., /checkout/success) to grant access to paid tiers. Network disconnects, closed browser tabs, or client-side crashes can prevent access from being granted.

Recommended Webhook Pipeline

  1. Verify Signature: Ensure the payload originated from your payment gateway.
  2. Idempotency Check: Store incoming event_id values to prevent processing duplicate events.
  3. Queue Processing: Immediately return a 200 OK response to the payment provider and dispatch the actual processing task to a background worker.
// Example: Verifying and dispatching webhooks asynchronously
public function handleWebhook(Request $request)
{
    $event = $this->verifySignature($request);

    if ($this->hasBeenProcessed($event->id)) {
        return response()->json(['status' => 'already_processed'], 200);
    }

    ProcessBillingEventJob::dispatch($event);

    return response()->json(['status' => 'queued'], 200);
}
Enter fullscreen mode Exit fullscreen mode

3. Customer Self-Service: The Billing Portal

Modern users expect to manage their own billing:

  • Updating expired credit cards
  • Downloading past VAT / Tax invoices
  • Upgrading or downgrading plans

Using hosted billing portals (like Stripe Customer Portal) eliminates the need to build custom PCI-compliant credit card updating forms on your frontend.


4. Get the Starter Kit

Rather than spending 40+ hours wiring up database schemas, webhook listeners, environment configs, and test suites, you can grab the complete SaaS Billing Engine Starter Kit.

Includes:

  • Complete source code with local DDEV / Docker support
  • Production-tested webhook handlers
  • Seeders, migrations, and plan definitions
  • Detailed documentation

https://boukataya.gumroad.com/l/shouoa

Top comments (0)