Stripe Payments in Laravel 13: Subscriptions with Cashier Step by Step
Setting up Stripe payments by subscription from scratch used to take weeks of code: customers, prices, invoices, webhooks, and states. With Laravel Cashier in Laravel 13, it takes hours: one trait, one checkout, one webhook. Here is how to take an app to charging in production with Stripe.
What Laravel Cashier Is and Why Use It with Stripe
Laravel Cashier is Laravel's official subscription billing package. It adds a fluent interface over the Stripe API and removes the boilerplate every payment app repeats: creating customers, subscriptions, invoices, coupons, and payment methods. The official Cashier documentation for Laravel 13.x is the reference for this guide.
What It Solves: Subscriptions, Invoices, and Webhooks Out of the Box
Once the package is installed, your user model gains methods to subscribe, cancel, swap plans, list invoices, or download PDFs without writing raw Stripe calls. Cashier also listens for the most common Stripe events and keeps subscription state in sync in your database, including automatic cancellation on failed charges.
What NOT to Do: Storing Cards in Your Own Database
The security argument many forget: with Stripe Checkout, card data is processed on Stripe's servers and never passes through your application. Your server only handles identifiers for customers and payment methods. That simplifies PCI DSS compliance and removes the burden of holding sensitive data. If you are tempted to save card numbers, that is the sign the approach is wrong.
Installation and Setup in Laravel 13
Laravel 13 was released on March 17, 2026 and requires PHP 8.3 at minimum. For this guide it does not matter whether you come from Laravel 12 or older versions: Cashier installs the same way, and if you just upgraded, our Laravel 13 new features article shows what changed in the framework.
composer require laravel/cashier and Migrations
Install the package and publish its migrations with two commands:
composer require laravel/cashier
php artisan vendor:publish --tag=cashier-migrations
php artisan migrateThe migrations add the stripe_id, pm_type, and pm_last_four columns to your users table, plus the subscription tables. Cashier manages the whole schema; there is nothing to create by hand.
Stripe Keys in .env and Test Mode
Copy the keys from the Stripe dashboard into your .env. During development always use the test keys, which start with sk_test and pk_test:
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...With test keys, the whole flow works with test cards such as 4242 4242 4242 4242 and no real charges. That is the mode you should use while developing, and the one you will need for the local testing covered at the end of this guide.
The Billable Trait on the User Model
Add the Billable trait to your user model:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}From that moment on, every user is a Stripe customer on demand: Cashier creates the customer automatically on the first charge, so you never call Stripe's API to manage customers yourself.
Your First Subscription with Stripe Checkout
Checkout is the payment page hosted by Stripe: the user enters the card on Stripe's domain and your app never sees the data. It is the route Stripe itself recommends and the shortest way to start charging.
Creating the Product and Price in the Stripe Dashboard
In the Stripe dashboard, create a product (for example, Pro Plan) and a recurring monthly price. Note the price ID, which looks like price_...; that is the value you will use in code to reference the plan.
Redirecting the User with the checkout() Method
In the controller, create a Checkout session and redirect the user:
return $request->user()->checkout([
'price_pro' => 1,
], [
'success_url' => route('dashboard'),
'cancel_url' => route('pricing'),
]);The first array maps prices to quantities; the second configures where the user returns after paying or cancelling. Replace price_pro with your real price ID.
The Webhook: Knowing When the Subscription Starts
Some payment methods take a few seconds to process, so returning from Checkout does not guarantee the subscription already exists. That is where the webhook comes in: Stripe notifies your server of events and Cashier updates the database. Register the standard route:
Route::post('/stripe/webhook', [StripeWebhookController::class, 'handleWebhook'])
->name('cashier.webhook');Cashier automatically handles the cancellation of subscriptions on failed charges and other common events; for additional events, the package dispatches its own events that you can listen to with listeners.
Managing the Subscription Lifecycle
The subscription does not end with the first month's charge: you need to cancel, resume, and swap plans without touching the Stripe dashboard. Cashier exposes direct methods on the user's active subscription.
Cancel, Resume, and Swap Plans
$user->subscription('default')->cancel();
$user->subscription('default')->resume();
$user->subscription('default')->swap('price_enterprise');Cancelling keeps access until the end of the already-paid period; resuming only works if the period has not ended; swapping changes the plan and bills the difference pro-rated. Those three methods cover most of subscription management.
Grace Periods and States (past_due, unpaid)
When a charge fails, Stripe marks the subscription as past_due and Cashier cancels it after a grace period if the customer does not update the payment method. You can check the state with pastDue() or unpaid() on the subscription and offer a form to update the card before losing the customer.
Payment Methods and Single Charges
Subscriptions do not cover every case: sometimes you need to charge a one-off payment or store several cards per customer.
Why the Default Payment Method Can't Do Single Charges
There is a real Stripe limitation that Cashier inherits: a customer's default payment method can only be used for invoicing and for creating new subscriptions; it cannot be used for single charges. For a one-off charge, the customer must pick a method in a Checkout session or a payment modal.
Adding and Removing Payment Methods
$user->addPaymentMethod($paymentMethodId);
$paymentMethod->delete();addPaymentMethod stores the method as available without charging anything; delete removes it from the customer's account. Both operate on PaymentMethod instances and cover card management without sensitive data passing through your server.
Invoices: Listing, Previewing, and Downloading PDFs
Cashier treats every charge as an invoice with its associated PDF. Listing them is straightforward:
foreach ($user->invoices() as $invoice) {
echo $invoice->date()->toFormattedDateString();
}For the PDF download, use downloadInvoice, which generates the file in memory and returns it as a download response; a custom filename is automatically suffixed with .pdf:
return $user->downloadInvoice($invoice->id, [
'vendor' => 'Your Company',
'product' => 'Pro Subscription',
]);That is the usual flow in Laravel apps with Stripe: an invoice list in the user panel and a download button per invoice, without generating PDFs by hand.
Testing Payments Without Real Money: Stripe CLI and Local Webhooks
Stripe CLI is the missing piece in development: it forwards Stripe webhooks to your local machine. Install the CLI, log in with your account, and run:
stripe listen --forward-to http://localhost:8000/stripe/webhookThe command prints a webhook secret that you add to .env as STRIPE_WEBHOOK_SECRET. From then on, every test payment fires the events in your app as if it were production, and you can test the full flow — subscription, failed charge, cancellation, and resuming — all with test cards.
Conclusion
With Laravel Cashier, charging a subscription in Laravel 13 stops being a project: install the package, add the Billable trait, redirect to Checkout, and listen for the webhook. Everything else — cancellations, plan swaps, invoice PDFs — is already solved by the package. If you also need to translate your billing panel, our Laravel 13 localization guide shows you how. Keep reading the blog for more Laravel 13 tutorials.