Laravel

Laravel SaaS Billing Checklist — Stripe Cashier & Razorpay Webhooks

Arun Tyagi
August 09, 2026
5 views
Laravel SaaS Billing Checklist — Stripe Cashier & Razorpay Webhooks

Last updated: August 2026 · Checklist from production SaaS launches

Building SaaS with Laravel fails less often on controllers and more often on money paths. Stripe looks finished in the dashboard. Razorpay looks finished after one successful payment. Then a customer disputes a charge, a webhook retries twice, and your subscriptions table lies.

I'm Arun Tyagi. This is the billing checklist I walk before a Laravel SaaS goes live — whether you use Laravel Cashier (Stripe) or Razorpay for India-first products.

Pick the provider for the market, not the tutorial

  • Stripe Cashier — international cards, cleaner subscription primitives, solid Laravel docs
  • Razorpay — India cards/UPI/netbanking, GST-friendly flows, more custom glue

Don't run both on day one “just in case.” Dual billing doubles every edge case. If you need both markets later, design a thin BillingGateway interface early — implement one now.

Before you write the first webhook

  1. Decide the commercial unit: seat, organisation plan, or metered usage
  2. Store provider_customer_id and provider_subscription_id on your organisation — not only on the user
  3. Map plan codes in config, not hardcoded Blade
  4. Agree what “past_due” means for product access (grace days? read-only?)

Tenancy and billing couple tightly. If organisations are fuzzy, fix that first — see multi-tenant SaaS on Laravel.

Webhook rules that save weekends

Idempotency. Process the same event twice without creating two invoices or flipping status randomly. Store processed event IDs.

Signature verify first. Reject unsigned payloads. No “temporary logging without verify” in production.

Queues. Webhook controllers should acknowledge fast and hand work to a job. Provider timeouts create duplicate deliveries.

Clock skew. Don't trust only local now() for “subscription ended” — reconcile with provider status on a nightly job.

Checkout success
   → create/update local subscription (pending)
Webhook invoice.paid / payment.captured
   → mark active (source of truth)
Webhook payment_failed / invoice.payment_failed
   → past_due + email + grace policy
Cancel at period end
   → keep access until period_end, then revoke

Product access matrix (write this down)

StatusApp accessAdmin note
trialingFull Phase-1 featuresShow days left
activeFull
past_dueGrace N days, then read-onlySupport can extend once
canceledExport + read until period endNo new seats

If this table isn't written, every developer invents a different rule in a different middleware.

Testing that actually matters

  • Successful first payment
  • Failed card on day 2 of trial
  • Replay the same webhook payload
  • Upgrade plan mid-cycle (proration behaviour documented)
  • Cancel and confirm access end date
  • Organisation with two admins — only billing role can change plan

I keep these as automated feature tests where possible. Manual Stripe CLI clicks alone aren't enough for multi-tenant apps.

Common Cashier / Razorpay mistakes I fix

  • Listening only to checkout.session.completed and ignoring failed renewals
  • Putting plan name in the UI while the database still says the old price ID
  • Deleting local rows when a subscription cancels — you lose audit history
  • Sending marketing emails from the webhook worker (slow + flaky)
  • Allowing any organisation member to change billing without a billing role

None of these show up in a happy-path demo. They show up when finance asks for last month's invoices.

Admin tools you need on week one

Support will ask: “Is this company paid?” If the answer requires SSH, you aren't launched. Filament (or a small custom panel) should show plan, status, last invoice, and a link to the provider dashboard.

How this fits a full Laravel SaaS build

Billing is one slice. Cost and timeline for the whole MVP: Laravel SaaS MVP cost India 2026. Architecture and Phase-1 scope: Build SaaS with Laravel 13. If you want someone to implement this with you: Laravel SaaS Development Services India.

FAQ

Cashier or raw Stripe SDK? Cashier for standard subscriptions. Raw SDK when you have weird metering or marketplace splits.

Can billing wait until after launch? Soft launch with invites can. Public pricing pages without working dunning train customers to mistrust you.

India GST invoices? Plan accounting early if you sell to Indian companies — don't bolt PDF invoices on after first enterprise deal.

Next step: Book a free discovery call or WhatsApp +91 8791775933 with your provider preference (Stripe/Razorpay) and whether seats are per-user or per-organisation.

Frequently Asked Questions

Should I use Stripe Cashier or Razorpay for Laravel SaaS?

Use Stripe Cashier for international cards and standard subscriptions. Use Razorpay for India-first UPI/cards. Avoid running both on day one.

Why do SaaS webhooks break production?

Missing idempotency, slow controllers without queues, and trusting local clocks instead of provider status cause duplicate invoices and wrong access states.

What billing status means for app access?

Write a matrix: trialing/active full access; past_due with grace then read-only; canceled access until period end with no new seats.

Related services

Hire for the topics covered in this article:

Hire me · View pricing · Service locations