skip to content

What is Laravel Cashier (Stripe) for, and what does adding the Billable trait to the User model give you?

level: juniorimportance: must knowfreq 38%

answer

  1. a Stripe billing wrapper, not a gateway
  2. published migrations add stripe_id and trial_ends_at
  3. subscriptions and subscription_items tables
  4. subscribed() checks the default type
  5. invoices() calls the Stripe API

basics

~20 s

Laravel Cashier wraps Stripe's billing API for an Eloquent model. The Billable trait adds customer columns, a subscriptions relation and methods such as newSubscription(), subscribed(), checkout(), charge() and invoices(), while Stripe remains the system that actually bills customers.

solid answer

~30 s

Cashier (`laravel/cashier`, version 16 for Stripe) is a first-party layer over Stripe Billing: Stripe stores prices, charges cards and runs renewals, and Cashier mirrors the result into your database and gives you an expressive API. You publish its migrations (`vendor:publish --tag="cashier-migrations"`), which add `stripe_id`, `pm_type`, `pm_last_four` and `trial_ends_at` to `users` and create `subscriptions` and `subscription_items`. Adding `Laravel\Cashier\Billable` to `User` composes traits for customers, payment methods, subscriptions, charges, invoices and taxes, so you get `newSubscription()`, `subscribed()`, `checkout()`, `charge()`, `invoices()` and `downloadInvoice()`. Local rows are a cache of Stripe's state; invoices are read from Stripe on demand.

code

php · 11 lines
php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

go deeper

for a junior

Recall that Cashier is Laravel's wrapper for Stripe billing, that Billable goes on the User model, and that subscribed() is the everyday check for a paying customer.

for a middle

Explain the migrations and tables, the traits Billable composes, what subscribed() treats as valid, and why invoices() is a live Stripe call.

for a senior

Treat the local tables as a webhook-fed cache of Stripe, avoid Stripe calls on hot paths, and protect invoice downloads by customer ownership.

for a principal

Judge whether Cashier's Stripe-shaped model fits the product's pricing, and what it would cost to move billing providers once subscriptions depend on it.

## What Cashier is, and what it is not **Laravel Cashier (Stripe)** is a first-party package, `laravel/cashier`, currently at version 16, that puts an Eloquent-friendly API over **Stripe Billing**. It is not a payment gateway and it does not hold card data: - **Stripe** owns products, prices, customers, payment methods, invoices and the renewal schedule, and it charges the card. - **Cashier** creates and updates those Stripe objects through the Stripe SDK, stores a small local copy of subscription state, and keeps that copy current through webhooks. For a podcast-hosting SaaS with a monthly and a yearly plan, you define both prices in the Stripe dashboard and refer to them by price ID, such as `price_podcast_monthly`, from Laravel. ## Installing it 1. `composer require laravel/cashier`; 2. `php artisan vendor:publish --tag="cashier-migrations"` and `php artisan migrate`, because the migrations are published into your app rather than loaded from the package; 3. set `STRIPE_KEY` and `STRIPE_SECRET`, plus `STRIPE_WEBHOOK_SECRET` for signed webhooks; 4. add the `Billable` trait to the model that pays, which is `App\Models\User` by default. The migrations create this local schema: | Table | Columns that matter | |---|---| | `users` (added columns) | `stripe_id`, `pm_type`, `pm_last_four`, `trial_ends_at` | | `subscriptions` | `type`, `stripe_id`, `stripe_status`, `stripe_price`, `quantity`, `trial_ends_at`, `ends_at` | | `subscription_items` | one row per price on a subscription | ## What the Billable trait composes `Billable` is a bundle of smaller traits, which is why one `use` line gives a model so many methods: - **`ManagesCustomer`**: `createOrGetStripeCustomer()`, `stripeId()`, `redirectToBillingPortal()`, balances and tax IDs; - **`ManagesSubscriptions`**: `newSubscription()`, `subscription('default')`, `subscribed()`, `onTrial()`, `subscribedToPrice()`; - **`ManagesPaymentMethods`**: default and stored payment methods; - **`PerformsCharges`**: `charge()`, `pay()`, `refund()`, and `checkout()` for one-off Checkout sessions; - **`ManagesInvoices`**: `invoices()`, `findInvoice()`, `downloadInvoice()`, `upcomingInvoice()`; - **`HandlesTaxes`** and **`ManagesUsageBilling`** for tax rates and metered prices. ## Checking access `subscribed()` takes a subscription **type**, `'default'` unless you name one, and returns true when that subscription is **valid**: active, on trial, or cancelled but still inside its grace period. It reads the local `subscriptions` rows, so it costs no Stripe call. Gating the podcast upload page is usually `abort_unless($request->user()->subscribed(), 402)` or a middleware that does the same. ## Invoices are read from Stripe Cashier keeps **no invoices table**. `$user->invoices()` calls the Stripe API each time, returning up to 24 paid invoices by default as `Laravel\Cashier\Invoice` objects; `invoicesIncludingPending()` adds open ones. `downloadInvoice($id)` renders a PDF, which needs `dompdf/dompdf` installed for the default renderer, and it refuses an invoice that belongs to another Stripe customer with a **403**, or an unknown ID with a **404**. Because every listing is a remote call, cache or paginate it on busy account pages. ## Interview traps - **"Cashier stores the card."** It stores only `pm_type` and `pm_last_four` for display; Stripe holds the payment method. - **"`subscribed()` means paid this month."** It is also true during a trial and during the grace period after `cancel()`. - **"The local tables are the truth."** They mirror Stripe, and webhooks keep them current; state changed in Stripe, such as a failed renewal, only reaches your tables through a webhook. ## Versions worth knowing Cashier for Stripe is at 16.8 on this pin and supports Laravel 13. Two older changes still shape answers: since Cashier 14, `dompdf/dompdf` is an optional dependency you install yourself for invoice PDFs, and new subscriptions default to Stripe's `default_incomplete` payment behaviour. Cashier also pins the Stripe API version it speaks: its `STRIPE_VERSION` constant reads `Stripe\Util\ApiVersion::CURRENT` from the installed Stripe SDK, so the exact version follows the SDK release, and the `cashier:webhook` command creates endpoints on that version so payload shapes match. A separate package, Cashier Paddle, targets Paddle instead of Stripe; its API is similar in spirit but not identical, so name which one you mean in an interview.

  • In Laravel Cashier, what is the difference between $user->onTrial() and $user->onTrial('default')?
    With no arguments, `onTrial()` first checks the user's own `trial_ends_at`, a generic trial with no Stripe subscription yet, and then the default subscription's trial. Passing any argument, even `'default'`, skips the generic check and only looks at that subscription's `trial_ends_at`.
  • Why does Laravel Cashier publish its migrations instead of loading them from the package?
    Publishing copies them into `database/migrations`, so they become part of your app's history: you can change the billable table or column types before migrating, and the schema does not shift when the package updates. You run `vendor:publish --tag="cashier-migrations"` once, then `migrate`.

saying these in an interview costs you the question

  • Cashier processes card payments itself, so Stripe is optional.
  • Cashier stores full card details in the users table.
  • $user->invoices() reads from a local invoices table.
  • subscribed() returns false while a customer is on a trial.
  • The Billable trait works without running Cashier's migrations.