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
- Decide the commercial unit: seat, organisation plan, or metered usage
- Store
provider_customer_idandprovider_subscription_idon your organisation — not only on the user - Map plan codes in config, not hardcoded Blade
- 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)
| Status | App access | Admin note |
|---|---|---|
| trialing | Full Phase-1 features | Show days left |
| active | Full | — |
| past_due | Grace N days, then read-only | Support can extend once |
| canceled | Export + read until period end | No 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.completedand 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:
Related Posts
PHP Laravel Framework Deep Dive
Explore the powerful features of Laravel framework and how to leverage them in your projects.
Laravel Developer in Dubai – Arun Tyagi Web Development Solutions
Dubai's technology sector—anchored by free zones like DIFC, Dubai Internet City, and DMCC—generates...
Leading Web Development Freelancer in NCR – Arun Tyagi
Delhi NCR hosts one of India's largest concentrations of freelance web developers, with skill levels...