Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

PHPadvancedbackendAI generated

Multi-tenant SaaS Billing System with Laravel

Build the billing system of a SaaS product in Laravel, where many customer companies (tenants) share one application but never see each other's data. Each tenant subscribes to a plan through Stripe (in test mode, so no real money moves), pays a monthly fee plus usage (for example per API call or per document processed), and the system keeps everything in sync through Stripe's webhooks, processed reliably in queued jobs. A dashboard shows each tenant their plan, current usage, invoices and payment problems. You'll learn multi-tenancy with automatic query scoping, subscriptions with Laravel Cashier, usage-based billing, handling webhooks safely, queues and retries, and testing billing flows without calling Stripe.

Estimate
~16.5h
Steps
7
Completed by
0
Proposed by
codeseed.app

Laravel · Stripe API · Laravel Queues

Project roadmap

  1. 01

    Isolate tenant data

    ~3h

    Create a Laravel app with authentication where users belong to a tenant. Add a tenant_id to every tenant-owned table and a global scope (in a BelongsToTenant trait) that filters every query by the current tenant and fills tenant_id on create. Resolve the current tenant from the logged-in user in a middleware. Done when: tests show a user can never read or change another tenant's records, even by guessing ids.

  2. 02

    Set up subscriptions with Cashier

    ~2.5h

    Install Laravel Cashier for Stripe and make the Tenant model billable. Create products and prices in Stripe's test mode (a flat monthly base price and a metered usage price), and build pages to choose a plan and subscribe using Stripe Checkout, plus change plan and cancel. Done when: a tenant can subscribe with a Stripe test card, and the subscription appears in Stripe and in the database.

  3. 03

    Handle Stripe webhooks

    ~2.5h

    Use Cashier's webhook controller (with the signing secret verified) and add listeners for invoice.paid, invoice.payment_failed, customer.subscription.updated and customer.subscription.deleted, updating the tenant's status. Record processed event ids so repeated deliveries do nothing, and do the work in queued jobs. Test locally with the Stripe CLI (stripe listen and stripe trigger). Done when: triggered events update tenants correctly, and replayed events change nothing.

  4. 04

    Bill for usage

    ~2.5h

    Record usage events in the database as they happen (tenant, amount and time, with an idempotency key). A scheduled job sends usage to Stripe in batches (as meter events for metered billing), marks what was sent, and retries failures with backoff, so usage is never lost or counted twice. Done when: usage recorded in the app appears in the tenant's upcoming Stripe invoice exactly once.

  5. 05

    Handle payment problems

    ~1.5h

    When a payment fails, mark the tenant as past due, show a banner asking them to update their card (with a link to the Stripe customer portal), and email them. After a grace period without payment, limit the tenant to read-only access. Restore full access when an invoice is paid. Done when: a failed test payment triggers the banner and grace period, and payment restores access.

  6. 06

    Build the tenant dashboard

    ~2h

    Build a billing page for tenant admins showing the current plan and status, usage so far this period with an estimated cost, past invoices with download links (from Stripe), and buttons to change plan or manage payment methods. Only tenant admins can see it. Done when: the dashboard shows accurate billing information for the logged-in tenant.

  7. 07

    Test it

    ~2.5h

    Write Pest or PHPUnit tests: tenant isolation for every model, webhook handling with signed test payloads (including duplicates), the usage sync job with Stripe calls faked (for example with Http::fake or a mocked Stripe client), grace period rules, and dashboard access. Done when: php artisan test passes without calling Stripe.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~16.5h · 7 steps

Tech stack

LaravelStripe APILaravel Queues