Overview
Laravel Cashier (Stripe) is Laravel’s official package for Stripe billing. You can use it to create and manage subscriptions, run one-time charges, retrieve invoices, and handle Stripe webhooks through a fluent API.
Installation and configuration
Install Cashier and run its database migrations.
composer require laravel/cashier
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
Add the Billable trait to your billable model.
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
Set Stripe keys in .env.
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
Customer management
Use createOrGetStripeCustomer() when you want to safely fetch or create the Stripe customer record.
$stripeCustomer = $user->createOrGetStripeCustomer();
Use createAsStripeCustomer() when you explicitly want to create a customer first.
$stripeCustomer = $user->createAsStripeCustomer();
Subscriptions
Create a subscription
Create a subscription with newSubscription() and create().
Pass a Payment Method ID (for example, from Stripe.js) as $paymentMethodId.
$user->newSubscription('default', 'price_monthly')
->create($paymentMethodId);
price_monthly is an example. Use the actual Stripe Price ID from your Stripe dashboard.
Check status
Use subscribed() to check whether a user currently has an active subscription.
if ($user->subscribed('default')) {
// Active subscription...
}
Cancel and resume
$user->subscription('default')->cancel();
if ($user->subscription('default')->onGracePeriod()) {
// The user is on the grace period...
}
$user->subscription('default')->resume();
One-time charges
Use charge() for one-time billing. Pass the amount in the lowest currency denomination (for example, 100 means $1.00 in USD). Use a Stripe Payment Method ID as $paymentMethodId.
$payment = $user->charge(100, $paymentMethodId);
If the charge fails, charge() throws an exception.
Payment Element
Stripe’s Payment Element supports multiple payment methods in a single UI component, including cards, Apple Pay, Google Pay, and iDEAL.
Payment Element for Subscriptions
Create a Setup Intent and pass it to your view.
return view('subscribe', [
'intent' => $user->createSetupIntent()
]);
Mount the Payment Element using the Setup Intent’s client_secret.
<div id="payment-element"></div>
<button id="submit">Subscribe</button>
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements({
clientSecret: '{{ $intent->client_secret }}'
});
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
document.getElementById('submit').addEventListener('click', async () => {
const { error } = await stripe.confirmSetup({
elements,
confirmParams: {
return_url: '{{ route("subscription.complete") }}',
},
});
if (error) {
// Display "error.message" to the user...
}
});
</script>
After Stripe redirects to your return_url, use the setup_intent query string parameter to retrieve the payment method and create the subscription.
use Illuminate\Http\Request;
Route::get('/subscription/complete', function (Request $request) {
$setupIntent = $request->user()->findSetupIntent(
$request->setup_intent
);
$paymentMethod = $setupIntent->payment_method;
$request->user()
->newSubscription('default', 'price_xxx')
->create($paymentMethod);
return redirect('/dashboard');
})->name('subscription.complete');
Payment Element for Single Charges
For one-off payments, create a Payment Intent with the pay() method. Store the Payment Intent ID on an order model so you can retrieve it after Stripe redirects back. The example below assumes an Order model with user_id, amount, status, and stripe_payment_intent_id columns.
use App\Models\Order;
use Illuminate\Http\Request;
Route::post('/pay', function (Request $request) {
$amount = 1000;
$payment = $request->user()->pay($amount);
$order = Order::create([
'user_id' => $request->user()->id,
'amount' => $amount,
'status' => 'pending',
'stripe_payment_intent_id' => $payment->id,
]);
return view('checkout', [
'clientSecret' => $payment->client_secret,
'order' => $order,
]);
});
Mount the Payment Element and confirm the payment.
<div id="payment-element"></div>
<button id="submit">Pay Now</button>
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements({
clientSecret: '{{ $clientSecret }}'
});
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
document.getElementById('submit').addEventListener('click', async () => {
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: '{{ route("payment.complete") }}',
},
});
if (error) {
// Display "error.message" to the user...
}
});
</script>
After the redirect, retrieve the order using the payment_intent query parameter. Always verify that the Payment Intent belongs to the authenticated customer and that its status is succeeded before fulfilling the order.
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/payment/complete', function (Request $request) {
$order = Order::where('user_id', $request->user()->id)
->where('stripe_payment_intent_id', $request->payment_intent)
->firstOrFail();
$paymentIntent = $request->user()
->stripe()
->paymentIntents
->retrieve($request->payment_intent);
if ($paymentIntent->customer === $request->user()->stripe_id &&
$paymentIntent->status === 'succeeded') {
$order->update(['status' => 'paid']);
// Fulfill the order...
}
return redirect('/dashboard');
})->name('payment.complete');
Invoices
Retrieve invoices with invoices().
$invoices = $user->invoices();
To generate downloadable PDFs, install dompdf/dompdf and call downloadInvoice(). Pass an invoice ID from the invoices() collection as $invoiceId.
composer require dompdf/dompdf
return $user->downloadInvoice($invoiceId);
Webhook setup
Cashier automatically registers a Stripe webhook route and uses /stripe/webhook by default. Configure this URL in your Stripe dashboard.
You can create the webhook through Artisan.
php artisan cashier:webhook
Exclude stripe/* from CSRF protection.
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(except: [
'stripe/*',
]);
})
Set STRIPE_WEBHOOK_SECRET in .env so Cashier can validate webhook signatures.Last modified on July 4, 2026