laravel/cashier
Laravel Cashier (Stripe) adds a fluent, expressive API for subscription billing in Laravel. Manage subscriptions, coupons, plan swaps, quantities, cancellation grace periods, and invoice PDF generation—without writing boilerplate billing code.
Installation:
composer require laravel/cashier stripe/stripe-php
Publish the migration and config:
php artisan vendor:publish --provider="Laravel\Cashier\CashierServiceProvider"
php artisan migrate
Configure Stripe:
Add your Stripe secret key to .env:
STRIPE_KEY=your_stripe_secret_key
STRIPE_ENDPOINT=https://api.stripe.com
First Use Case: Attach a Stripe customer to a User model:
use Laravel\Cashier\Billable;
class User extends Authenticatable implements Billable
{
use Billable;
}
Create a Subscription:
$user->newSubscription('main', 'price_123')->create($paymentMethodId);
webhooks table and route POST /stripe/webhook to StripeWebhookController.Stripe::fake() for unit tests.Create/Update:
// Create a subscription
$user->newSubscription('main', 'price_123')->create($paymentMethodId);
// Switch plans
$user->subscription('main')->swap('price_456');
// Cancel (with grace period)
$user->subscription('main')->cancel();
Pause/Resume:
$user->subscription('main')->pause();
$user->subscription('main')->resume();
Quantity Adjustments:
$user->subscription('main')->quantity(5); // For metered billing
Generate Invoice PDF:
$invoice = $user->invoices()->latest()->first();
return response()->streamDownload(function () use ($invoice) {
echo $invoice->download();
}, 'invoice.pdf');
Manual Payment:
$user->invoice()->pay($paymentMethodId);
Apply Coupon:
$user->newSubscription('main', 'price_123')->withCoupon('SUMMER20')->create($paymentMethodId);
Trial Period:
$user->newSubscription('main', 'price_123')->trialDays(7)->create($paymentMethodId);
Stripe Checkout:
$session = $user->createCheckoutSession([
'success_url' => route('checkout.success'),
'cancel_url' => route('checkout.cancel'),
'line_items' => [
[
'price' => 'price_123',
'quantity' => 1,
],
],
]);
Embedded Checkout:
$session = $user->createCheckoutSession([
'mode' => 'subscription',
'ui_mode' => 'embedded',
'client_reference_id' => $user->id,
]);
use Laravel\Cashier\Http\Controllers\StripeWebhookController;
Route::post('/stripe/webhook', [StripeWebhookController::class, 'handle']);
invoice.paid, customer.subscription.deleted, invoice.payment_failed.handleWebhook in StripeWebhookController.Track subscription changes:
class UserObserver
{
public function saved(User $user)
{
if ($user->wasRecentlyCreated && $user->subscribed('main')) {
// Send welcome email
}
}
}
public function handle(Request $request, Closure $next)
{
if ($request->user()->subscribed('main')) {
return $next($request);
}
abort(403, 'Subscription required');
}
Use Stripe Products/Prices API to fetch dynamic prices:
$price = \Stripe\Price::retrieve('price_123');
$user->newSubscription('main', $price)->create($paymentMethodId);
public function test_subscription_creation()
{
Stripe::fake();
$user = User::factory()->create();
$user->newSubscription('main', 'price_123')->create('pm_123');
Stripe::assertSubscriptionCreated();
}
Configure Stripe for multiple currencies and use:
$user->newSubscription('main', 'price_123')->create($paymentMethodId, [
'billing_cycle_anchor' => now(),
'proration_behavior' => 'none',
]);
Stripe::webhook() to verify signatures and handle retries gracefully.proration_behavior:
$user->subscription('main')->swap('price_456', [
'proration_behavior' => 'none', // or 'create_prorations'
]);
$user->subscription('main')->invoices()->create();
try {
$user->updateDefaultPaymentMethod('pm_new');
} catch (\Exception $e) {
// Handle error (e.g., payment method invalid)
}
tax_behavior and ensure Stripe tax settings are configured:
$user->newSubscription('main', 'price_123')->create($paymentMethodId, [
'tax_behavior' => 'exclusive', // or 'inclusive'
]);
Stripe::fake() and mock specific scenarios:
Stripe::fake([
'customer_creation' => 'fail', // Simulate failure
]);
Enable Stripe debug mode:
\Stripe\Stripe::setApiKey(config('cashier.key'));
\Stripe\Stripe::setApiVersion('2023-10-16');
\Stripe\Stripe::setLogLevel(\Stripe\Logger::DEBUG);
Use Stripe CLI to test webhooks locally:
stripe listen --forward-to localhost:8000/stripe/webhook
InvalidRequestError: Validate all required fields (e.g., payment_method, price).AuthenticationError: Ensure STRIPE_KEY is correct and not expired.ResourceMissing: Handle cases where subscriptions/invoices are deleted externally.If Stripe and your database are out of sync:
php artisan cashier:sync
Extend StripeWebhookController:
class CustomStripeWebhookController extends StripeWebhookController
{
protected function handleWebhookEvent($event)
{
if ($event->type === 'customer.subscription.deleted') {
// Custom logic (e.g., send cancellation email)
}
parent::handleWebhookEvent($event);
}
}
Override invoice PDF generation:
// app/Providers/AppServiceProvider.php
public function boot()
{
Invoice::macro('download', function () {
$pdf = PDF::loadView('custom.invoice', ['invoice' => $this]);
return $pdf->output();
});
}
Add methods to your User model:
public function isOnFree
How can I help you explore Laravel packages today?