Introduction
Laravel Cashier Stripe provides an expressive, fluent interface to Stripe's subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing. In addition to basic subscription management, Cashier can handle coupons, swapping subscription, subscription "quantities", cancellation grace periods, and even generate invoice PDFs.
Upgrading Cashier
When upgrading to a new version of Cashier, it's important that you carefully review the upgrade guide.
Warning!
To prevent breaking changes, Cashier uses a fixed Stripe API version. Cashier 15 utilizes Stripe API version2023-10-16
. The Stripe API version will be updated on minor releases in order to make use of new Stripe features and improvements.
Installation
First, install the Cashier package for Stripe using the Composer package manager:
composer require laravel/cashier
After installing the package, publish Cashier's
migrations using the vendor:publish
Artisan
command:
php artisan vendor:publish --tag="cashier-migrations"
Then, migrate your database:
php artisan migrate
Cashier's migrations will add several columns to your
users
table. They will also create a new
subscriptions
table to hold all of your
customer's subscriptions and a
subscription_items
table for subscriptions
with multiple prices.
If you wish, you can also publish Cashier's configuration
file using the vendor:publish
Artisan
command:
php artisan vendor:publish --tag="cashier-config"
Lastly, to ensure Cashier properly handles all Stripe events, remember to configure Cashier's webhook handling.
Warning!
Stripe recommends that any column used for storing Stripe identifiers should be case-sensitive. Therefore, you should ensure the column collation for thestripe_id
column is set toutf8_bin
when using MySQL. More information regarding this can be found in the Stripe documentation.
Configuration
Billable Model
Before using Cashier, add the Billable
trait
to your billable model definition. Typically, this will
be the App\Models\User
model. This trait
provides various methods to allow you to perform common
billing tasks, such as creating subscriptions, applying
coupons, and updating payment method information:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
Cashier assumes your billable model will be the
App\Models\User
class that ships with
Laravel. If you wish to change this you may specify a
different model via the useCustomerModel
method. This method should typically be called in the
boot
method of your
AppServiceProvider
class:
use App\Models\Cashier\User;
use Laravel\Cashier\Cashier;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Cashier::useCustomerModel(User::class);
}
Warning!
If you're using a model other than Laravel's suppliedApp\Models\User
model, you'll need to publish and alter the Cashier migrations provided to match your alternative model's table name.
API Keys
Next, you should configure your Stripe API keys in your
application's .env
file. You can retrieve
your Stripe API keys from the Stripe control panel:
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
Warning!
You should ensure that theSTRIPE_WEBHOOK_SECRET
environment variable is defined in your application's.env
file, as this variable is used to ensure that incoming webhooks are actually from Stripe.
Currency Configuration
The default Cashier currency is United States Dollars
(USD). You can change the default currency by setting
the CASHIER_CURRENCY
environment variable
within your application's .env
file:
CASHIER_CURRENCY=eur
In addition to configuring Cashier's currency, you may
also specify a locale to be used when formatting money
values for display on invoices. Internally, Cashier
utilizes PHP's
NumberFormatter
class to set the
currency locale:
CASHIER_CURRENCY_LOCALE=nl_BE
Warning!
In order to use locales other thanen
, ensure theext-intl
PHP extension is installed and configured on your server.
Tax Configuration
Thanks to Stripe
Tax, it's possible to automatically calculate
taxes for all invoices generated by Stripe. You can
enable automatic tax calculation by invoking the
calculateTaxes
method in the
boot
method of your application's
App\Providers\AppServiceProvider
class:
use Laravel\Cashier\Cashier;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Cashier::calculateTaxes();
}
Once tax calculation has been enabled, any new subscriptions and any one-off invoices that are generated will receive automatic tax calculation.
For this feature to work properly, your customer's billing details, such as the customer's name, address, and tax ID, need to be synced to Stripe. You may use the customer data synchronization and Tax ID methods offered by Cashier to accomplish this.
Warning!
No tax is calculated for single charges or single charge checkouts.
Logging
Cashier allows you to specify the log channel to be used
when logging fatal Stripe errors. You may specify the
log channel by defining the CASHIER_LOGGER
environment variable within your application's
.env
file:
CASHIER_LOGGER=stack
Exceptions that are generated by API calls to Stripe will be logged through your application's default log channel.
Using Custom Models
You are free to extend the models used internally by Cashier by defining your own model and extending the corresponding Cashier model:
use Laravel\Cashier\Subscription as CashierSubscription;
class Subscription extends CashierSubscription
{
// ...
}
After defining your model, you may instruct Cashier to
use your custom model via the
Laravel\Cashier\Cashier
class. Typically,
you should inform Cashier about your custom models in
the boot
method of your application's
App\Providers\AppServiceProvider
class:
use App\Models\Cashier\Subscription;
use App\Models\Cashier\SubscriptionItem;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useSubscriptionItemModel(SubscriptionItem::class);
}
Quickstart
Selling Products
Note:
Before utilizing Stripe Checkout, you should define Products with fixed prices in your Stripe dashboard. In addition, you should configure Cashier's webhook handling.
Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Stripe Checkout, you can easily build modern, robust payment integrations.
To charge customers for non-recurring, single-charge products, we'll utilize Cashier to direct customers to Stripe Checkout, where they will provide their payment details and confirm their purchase. Once the payment has been made via Checkout, the customer will be redirected to a success URL of your choosing within your application:
use Illuminate\Http\Request;
Route::get('/checkout', function (Request $request) {
$stripePriceId = 'price_deluxe_album';
$quantity = 1;
return $request->user()->checkout([$stripePriceId => $quantity], [
'success_url' => route('checkout-success'),
'cancel_url' => route('checkout-cancel'),
]);
})->name('checkout');
Route::view('checkout.success')->name('checkout-success');
Route::view('checkout.cancel')->name('checkout-cancel');
As you can see in the example above, we will utilize
Cashier's provided checkout
method to
redirect the customer to Stripe Checkout for a given
"price identifier". When using Stripe,
"prices" refer to defined
prices for specific products.
If necessary, the checkout
method will
automatically create a customer in Stripe and connect
that Stripe customer record to the corresponding user in
your application's database. After completing the
checkout session, the customer will be redirected to a
dedicated success or cancellation page where you can
display an informational message to the customer.
Providing Meta Data to Stripe Checkout
When selling products, it's common to keep track of
completed orders and purchased products via
Cart
and Order
models defined
by your own application. When redirecting customers to
Stripe Checkout to complete a purchase, you may need to
provide an existing order identifier so that you can
associate the completed purchase with the corresponding
order when the customer is redirected back to your
application.
To accomplish this, you may provide an array of
metadata
to the checkout
method. Let's imagine that a pending Order
is created within our application when a user begins the
checkout process. Remember, the Cart
and
Order
models in this example are
illustrative and not provided by Cashier. You are free
to implement these concepts based on the needs of your
own application:
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
$order = Order::create([
'cart_id' => $cart->id,
'price_ids' => $cart->price_ids,
'status' => 'incomplete',
]);
return $request->user()->checkout($order->price_ids, [
'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout-cancel'),
'metadata' => ['order_id' => $order->id],
]);
})->name('checkout');
As you can see in the example above, when a user begins
the checkout process, we will provide all of the cart /
order's associated Stripe price identifiers to the
checkout
method. Of course, your
application is responsible for associating these items
with the "shopping cart" or order as a
customer adds them. We also provide the order's ID to
the Stripe Checkout session via the
metadata
array. Finally, we have added the
CHECKOUT_SESSION_ID
template variable to
the Checkout success route. When Stripe redirects
customers back to your application, this template
variable will automatically be populated with the
Checkout session ID.
Next, let's build the Checkout success route. This is the route that users will be redirected to after their purchase has been completed via Stripe Checkout. Within this route, we can retrieve the Stripe Checkout session ID and the associated Stripe Checkout instance in order to access our provided meta data and update our customer's order accordingly:
use App\Models\Order;
use Illuminate\Http\Request;
use Laravel\Cashier\Cashier;
Route::get('/checkout/success', function (Request $request) {
$sessionId = $request->get('session_id');
if ($sessionId === null) {
return;
}
$session = Cashier::stripe()->checkout->sessions->retrieve($sessionId);
if ($session->payment_status !== 'paid') {
return;
}
$orderId = $session['metadata']['order_id'] ?? null;
$order = Order::findOrFail($orderId);
$order->update(['status' => 'completed']);
return view('checkout-success', ['order' => $order]);
})->name('checkout-success');
Please refer to Stripe's documentation for more information on the data contained by the Checkout session object.
Selling Subscriptions
Note:
Before utilizing Stripe Checkout, you should define Products with fixed prices in your Stripe dashboard. In addition, you should configure Cashier's webhook handling.
Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Stripe Checkout, you can easily build modern, robust payment integrations.
To learn how to sell subscriptions using Cashier and
Stripe Checkout, let's consider the simple scenario of a
subscription service with a basic monthly
(price_basic_monthly
) and yearly
(price_basic_yearly
) plan. These two prices
could be grouped under a "Basic" product
(pro_basic
) in our Stripe dashboard. In
addition, our subscription service might offer an Expert
plan as pro_expert
.
First, let's discover how a customer can subscribe to our services. Of course, you can imagine the customer might click a "subscribe" button for the Basic plan on our application's pricing page. This button or link should direct the user to a Laravel route which creates the Stripe Checkout session for their chosen plan:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_basic_monthly')
->trialDays(5)
->allowPromotionCodes()
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
As you can see in the example above, we will redirect the
customer to a Stripe Checkout session which will allow
them to subscribe to our Basic plan. After a successful
checkout or cancellation, the customer will be
redirected back to the URL we provided to the
checkout
method. To know when their
subscription has actually started (since some payment
methods require a few seconds to process), we'll also
need to configure
Cashier's webhook handling.
Now that customers can start subscriptions, we need to
restrict certain portions of our application so that
only subscribed users can access them. Of course, we can
always determine a user's current subscription status
via the subscribed
method provided by
Cashier's Billable
trait:
@if ($user->subscribed())
<p>You are subscribed.</p>
@endif
We can even easily determine if a user is subscribed to specific product or price:
@if ($user->subscribedToProduct('pro_basic'))
<p>You are subscribed to our Basic product.</p>
@endif
@if ($user->subscribedToPrice('price_basic_monthly'))
<p>You are subscribed to our monthly Basic plan.</p>
@endif
Building a Subscribed Middleware
For convenience, you may wish to create a middleware which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Subscribed
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// Redirect user to billing page and ask them to subscribe...
return redirect('/billing');
}
return $next($request);
}
}
Once the middleware has been defined, you may assign it to a route:
use App\Http\Middleware\Subscribed;
Route::get('/dashboard', function () {
// ...
})->middleware([Subscribed::class]);
Allowing Customers to Manage Their Billing Plan
Of course, customers may want to change their subscription plan to another product or "tier". The easiest way to allow this is by directing customers to Stripe's Customer Billing Portal, which provides a hosted user interface that allows customers to download invoices, update their payment method, and change subscription plans.
First, define a link or button within your application that directs users to a Laravel route which we will utilize to initiate a Billing Portal session:
<a href="{{ route('billing') }}">
Billing
</a>
Next, let's define the route that initiates a Stripe
Customer Billing Portal session and redirects the user
to the Portal. The redirectToBillingPortal
method accepts the URL that users should be returned to
when exiting the Portal:
use Illuminate\Http\Request;
Route::get('/billing', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('dashboard'));
})->middleware(['auth'])->name('billing');
Note:
As long as you have configured Cashier's webhook handling, Cashier will automatically keep your application's Cashier-related database tables in sync by inspecting the incoming webhooks from Stripe. So, for example, when a user cancels their subscription via Stripe's Customer Billing Portal, Cashier will receive the corresponding webhook and mark the subscription as "cancelled" in your application's database.
Customers
Retrieving Customers
You can retrieve a customer by their Stripe ID using the
Cashier::findBillable
method. This method
will return an instance of the billable model:
use Laravel\Cashier\Cashier;
$user = Cashier::findBillable($stripeId);
Creating Customers
Occasionally, you may wish to create a Stripe customer
without beginning a subscription. You may accomplish
this using the createAsStripeCustomer
method:
$stripeCustomer = $user->createAsStripeCustomer();
Once the customer has been created in Stripe, you may
begin a subscription at a later date. You may provide an
optional $options
array to pass in any
additional customer
creation parameters that are supported by the Stripe
API:
$stripeCustomer = $user->createAsStripeCustomer($options);
You may use the asStripeCustomer
method if
you want to return the Stripe customer object for a
billable model:
$stripeCustomer = $user->asStripeCustomer();
The createOrGetStripeCustomer
method may be
used if you would like to retrieve the Stripe customer
object for a given billable model but are not sure
whether the billable model is already a customer within
Stripe. This method will create a new customer in Stripe
if one does not already exist:
$stripeCustomer = $user->createOrGetStripeCustomer();
Updating Customers
Occasionally, you may wish to update the Stripe customer
directly with additional information. You may accomplish
this using the updateStripeCustomer
method.
This method accepts an array of customer
update options supported by the Stripe API:
$stripeCustomer = $user->updateStripeCustomer($options);
Balances
Stripe allows you to credit or debit a customer's
"balance". Later, this balance will be
credited or debited on new invoices. To check the
customer's total balance you may use the
balance
method that is available on your
billable model. The balance
method will
return a formatted string representation of the balance
in the customer's currency:
$balance = $user->balance();
To credit a customer's balance, you may provide a value
to the creditBalance
method. If you wish,
you may also provide a description:
$user->creditBalance(500, 'Premium customer top-up.');
Providing a value to the debitBalance
method
will debit the customer's balance:
$user->debitBalance(300, 'Bad usage penalty.');
The applyBalance
method will create new
customer balance transactions for the customer. You may
retrieve these transaction records using the
balanceTransactions
method, which may be
useful in order to provide a log of credits and debits
for the customer to review:
// Retrieve all transactions...
$transactions = $user->balanceTransactions();
foreach ($transactions as $transaction) {
// Transaction amount...
$amount = $transaction->amount(); // $2.31
// Retrieve the related invoice when available...
$invoice = $transaction->invoice();
}
Tax IDs
Cashier offers an easy way to manage a customer's tax
IDs. For example, the taxIds
method may be
used to retrieve all of the tax
IDs that are assigned to a customer as a
collection:
$taxIds = $user->taxIds();
You can also retrieve a specific tax ID for a customer by its identifier:
$taxId = $user->findTaxId('txi_belgium');
You may create a new Tax ID by providing a valid type
and value to the createTaxId
method:
$taxId = $user->createTaxId('eu_vat', 'BE0123456789');
The createTaxId
method will immediately add
the VAT ID to the customer's account. Verification
of VAT IDs is also done by Stripe; however, this
is an asynchronous process. You can be notified of
verification updates by subscribing to the
customer.tax_id.updated
webhook event and
inspecting the
VAT IDs verification
parameter. For
more information on handling webhooks, please consult
the documentation on
defining webhook handlers.
You may delete a tax ID using the
deleteTaxId
method:
$user->deleteTaxId('txi_belgium');
Syncing Customer Data With Stripe
Typically, when your application's users update their name, email address, or other information that is also stored by Stripe, you should inform Stripe of the updates. By doing so, Stripe's copy of the information will be in sync with your application's.
To automate this, you may define an event listener on
your billable model that reacts to the model's
updated
event. Then, within your event
listener, you may invoke the
syncStripeCustomerDetails
method on the
model:
use App\Models\User;
use function Illuminate\Events\queueable;
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::updated(queueable(function (User $customer) {
if ($customer->hasStripeId()) {
$customer->syncStripeCustomerDetails();
}
}));
}
Now, every time your customer model is updated, its information will be synced with Stripe. For convenience, Cashier will automatically sync your customer's information with Stripe on the initial creation of the customer.
You may customize the columns used for syncing customer
information to Stripe by overriding a variety of methods
provided by Cashier. For example, you may override the
stripeName
method to customize the
attribute that should be considered the customer's
"name" when Cashier syncs customer information
to Stripe:
/**
* Get the customer name that should be synced to Stripe.
*/
public function stripeName(): string|null
{
return $this->company_name;
}
Similarly, you may override the stripeEmail
,
stripePhone
, stripeAddress
,
and stripePreferredLocales
methods. These
methods will sync information to their corresponding
customer parameters when updating
the Stripe customer object. If you wish to take
total control over the customer information sync
process, you may override the
syncStripeCustomerDetails
method.
Billing Portal
Stripe offers an
easy way to set up a billing portal so that your
customer can manage their subscription, payment methods,
and view their billing history. You can redirect your
users to the billing portal by invoking the
redirectToBillingPortal
method on the
billable model from a controller or route:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal();
});
By default, when the user is finished managing their
subscription, they will be able to return to the
home
route of your application via a link
within the Stripe billing portal. You may provide a
custom URL that the user should return to by passing the
URL as an argument to the
redirectToBillingPortal
method:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('billing'));
});
If you would like to generate the URL to the billing
portal without generating an HTTP redirect response, you
may invoke the billingPortalUrl
method:
$url = $request->user()->billingPortalUrl(route('billing'));
Payment Methods
Storing Payment Methods
In order to create subscriptions or perform "one-off" charges with Stripe, you will need to store a payment method and retrieve its identifier from Stripe. The approach used to accomplish this differs based on whether you plan to use the payment method for subscriptions or single charges, so we will examine both below.
Payment Methods for Subscriptions
When storing a customer's credit card information for
future use by a subscription, the Stripe "Setup
Intents" API must be used to securely gather the
customer's payment method details. A "Setup
Intent" indicates to Stripe the intention to charge
a customer's payment method. Cashier's
Billable
trait includes the
createSetupIntent
method to easily create a
new Setup Intent. You should invoke this method from the
route or controller that will render the form which
gathers your customer's payment method details:
return view('update-payment-method', [
'intent' => $user->createSetupIntent()
]);
After you have created the Setup Intent and passed it to the view, you should attach its secret to the element that will gather the payment method. For example, consider this "update payment method" form:
<input id="card-holder-name" type="text">
<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ $intent->client_secret }}">
Update Payment Method
</button>
Next, the Stripe.js library may be used to attach a Stripe Element to the form and securely gather the customer's payment details:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
Next, the card can be verified and a secure "payment
method identifier" can be retrieved from Stripe
using Stripe's
confirmCardSetup
method:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (e) => {
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: cardHolderName.value }
}
}
);
if (error) {
// Display "error.message" to the user...
} else {
// The card has been verified successfully...
}
});
After the card has been verified by Stripe, you may pass
the resulting setupIntent.payment_method
identifier to your Laravel application, where it can be
attached to the customer. The payment method can either
be added as a new
payment method or used to
update the default payment method. You can also
immediately use the payment method identifier to create a new
subscription.
Note:
If you would like more information about Setup Intents and gathering customer payment details please review this overview provided by Stripe.
Payment Methods for Single Charges
Of course, when making a single charge against a customer's payment method, we will only need to use a payment method identifier once. Due to Stripe limitations, you may not use the stored default payment method of a customer for single charges. You must allow the customer to enter their payment method details using the Stripe.js library. For example, consider the following form:
<input id="card-holder-name" type="text">
<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>
<button id="card-button">
Process Payment
</button>
After defining such a form, the Stripe.js library may be used to attach a Stripe Element to the form and securely gather the customer's payment details:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
Next, the card can be verified and a secure "payment
method identifier" can be retrieved from Stripe
using Stripe's
createPaymentMethod
method:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
cardButton.addEventListener('click', async (e) => {
const { paymentMethod, error } = await stripe.createPaymentMethod(
'card', cardElement, {
billing_details: { name: cardHolderName.value }
}
);
if (error) {
// Display "error.message" to the user...
} else {
// The card has been verified successfully...
}
});
If the card is verified successfully, you may pass the
paymentMethod.id
to your Laravel
application and process a single charge.
Retrieving Payment Methods
The paymentMethods
method on the billable
model instance returns a collection of
Laravel\Cashier\PaymentMethod
instances:
$paymentMethods = $user->paymentMethods();
By default, this method will return payment methods of
every type. To retrieve payment methods of a specific
type, you may pass the type
as an argument
to the method:
$paymentMethods = $user->paymentMethods('sepa_debit');
To retrieve the customer's default payment method, the
defaultPaymentMethod
method may be
used:
$paymentMethod = $user->defaultPaymentMethod();
You can retrieve a specific payment method that is
attached to the billable model using the
findPaymentMethod
method:
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
Payment Method Presence
To determine if a billable model has a default payment
method attached to their account, invoke the
hasDefaultPaymentMethod
method:
if ($user->hasDefaultPaymentMethod()) {
// ...
}
You may use the hasPaymentMethod
method to
determine if a billable model has at least one payment
method attached to their account:
if ($user->hasPaymentMethod()) {
// ...
}
This method will determine if the billable model has any
payment method at all. To determine if a payment method
of a specific type exists for the model, you may pass
the type
as an argument to the method:
if ($user->hasPaymentMethod('sepa_debit')) {
// ...
}
Updating the Default Payment Method
The updateDefaultPaymentMethod
method may be
used to update a customer's default payment method
information. This method accepts a Stripe payment method
identifier and will assign the new payment method as the
default billing payment method:
$user->updateDefaultPaymentMethod($paymentMethod);
To sync your default payment method information with the
customer's default payment method information in Stripe,
you may use the
updateDefaultPaymentMethodFromStripe
method:
$user->updateDefaultPaymentMethodFromStripe();
Warning!
The default payment method on a customer can only be used for invoicing and creating new subscriptions. Due to limitations imposed by Stripe, it may not be used for single charges.
Adding Payment Methods
To add a new payment method, you may call the
addPaymentMethod
method on the billable
model, passing the payment method identifier:
$user->addPaymentMethod($paymentMethod);
Note:
To learn how to retrieve payment method identifiers please review the payment method storage documentation.
Deleting Payment Methods
To delete a payment method, you may call the
delete
method on the
Laravel\Cashier\PaymentMethod
instance you
wish to delete:
$paymentMethod->delete();
The deletePaymentMethod
method will delete a
specific payment method from the billable model:
$user->deletePaymentMethod('pm_visa');
The deletePaymentMethods
method will delete
all of the payment method information for the billable
model:
$user->deletePaymentMethods();
By default, this method will delete payment methods of
every type. To delete payment methods of a specific type
you can pass the type
as an argument to the
method:
$user->deletePaymentMethods('sepa_debit');
Warning!
If a user has an active subscription, your application should not allow them to delete their default payment method.
Subscriptions
Subscriptions provide a way to set up recurring payments for your customers. Stripe subscriptions managed by Cashier provide support for multiple subscription prices, subscription quantities, trials, and more.
Creating Subscriptions
To create a subscription, first retrieve an instance of
your billable model, which typically will be an instance
of App\Models\User
. Once you have retrieved
the model instance, you may use the
newSubscription
method to create the
model's subscription:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription(
'default', 'price_monthly'
)->create($request->paymentMethodId);
// ...
});
The first argument passed to the
newSubscription
method should be the
internal type of the subscription. If your application
only offers a single subscription, you might call this
default
or primary
. This
subscription type is only for internal application usage
and is not meant to be shown to users. In addition, it
should not contain spaces and it should never be changed
after creating the subscription. The second argument is
the specific price the user is subscribing to. This
value should correspond to the price's identifier in
Stripe.
The create
method, which accepts a Stripe payment
method identifier or Stripe
PaymentMethod
object, will begin the
subscription as well as update your database with the
billable model's Stripe customer ID and other relevant
billing information.
Warning!
Passing a payment method identifier directly to thecreate
subscription method will also automatically add it to the user's stored payment methods.
Collecting Recurring Payments via Invoice Emails
Instead of collecting a customer's recurring payments automatically, you may instruct Stripe to email an invoice to the customer each time their recurring payment is due. Then, the customer may manually pay the invoice once they receive it. The customer does not need to provide a payment method up front when collecting recurring payments via invoices:
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();
The amount of time a customer has to pay their invoice
before their subscription is cancelled is determined by
the days_until_due
option. By default, this
is 30 days; however, you may provide a specific value
for this option if you wish:
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [
'days_until_due' => 30
]);
Quantities
If you would like to set a specific quantity
for the price when creating the subscription, you should
invoke the quantity
method on the
subscription builder before creating the
subscription:
$user->newSubscription('default', 'price_monthly')
->quantity(5)
->create($paymentMethod);
Additional Details
If you would like to specify additional customer
or subscription
options supported by Stripe, you may do so by passing
them as the second and third arguments to the
create
method:
$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [
'email' => $email,
], [
'metadata' => ['note' => 'Some extra information.'],
]);
Coupons
If you would like to apply a coupon when creating the
subscription, you may use the withCoupon
method:
$user->newSubscription('default', 'price_monthly')
->withCoupon('code')
->create($paymentMethod);
Or, if you would like to apply a Stripe
promotion code, you may use the
withPromotionCode
method:
$user->newSubscription('default', 'price_monthly')
->withPromotionCode('promo_code_id')
->create($paymentMethod);
The given promotion code ID should be the Stripe API ID
assigned to the promotion code and not the customer
facing promotion code. If you need to find a promotion
code ID based on a given customer facing promotion code,
you may use the findPromotionCode
method:
// Find a promotion code ID by its customer facing code...
$promotionCode = $user->findPromotionCode('SUMMERSALE');
// Find an active promotion code ID by its customer facing code...
$promotionCode = $user->findActivePromotionCode('SUMMERSALE');
In the example above, the returned
$promotionCode
object is an instance of
Laravel\Cashier\PromotionCode
. This class
decorates an underlying
Stripe\PromotionCode
object. You can
retrieve the coupon related to the promotion code by
invoking the coupon
method:
$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();
The coupon instance allows you to determine the discount amount and whether the coupon represents a fixed discount or percentage based discount:
if ($coupon->isPercentage()) {
return $coupon->percentOff().'%'; // 21.5%
} else {
return $coupon->amountOff(); // $5.99
}
You can also retrieve the discounts that are currently applied to a customer or subscription:
$discount = $billable->discount();
$discount = $subscription->discount();
The returned Laravel\Cashier\Discount
instances decorate an underlying
Stripe\Discount
object instance. You may
retrieve the coupon related to this discount by invoking
the coupon
method:
$coupon = $subscription->discount()->coupon();
If you would like to apply a new coupon or promotion code
to a customer or subscription, you may do so via the
applyCoupon
or
applyPromotionCode
methods:
$billable->applyCoupon('coupon_id');
$billable->applyPromotionCode('promotion_code_id');
$subscription->applyCoupon('coupon_id');
$subscription->applyPromotionCode('promotion_code_id');
Remember, you should use the Stripe API ID assigned to the promotion code and not the customer facing promotion code. Only one coupon or promotion code can be applied to a customer or subscription at a given time.
For more info on this subject, please consult the Stripe documentation regarding coupons and promotion codes.
Adding Subscriptions
If you would like to add a subscription to a customer who
already has a default payment method you may invoke the
add
method on the subscription builder:
use App\Models\User;
$user = User::find(1);
$user->newSubscription('default', 'price_monthly')->add();
Creating Subscriptions From the Stripe Dashboard
You may also create subscriptions from the Stripe
dashboard itself. When doing so, Cashier will sync newly
added subscriptions and assign them a type of
default
. To customize the subscription type
that is assigned to dashboard created subscriptions, define
webhook event handlers.
In addition, you may only create one type of subscription via the Stripe dashboard. If your application offers multiple subscriptions that use different types, only one type of subscription may be added through the Stripe dashboard.
Finally, you should always make sure to only add one
active subscription per type of subscription offered by
your application. If a customer has two
default
subscriptions, only the most
recently added subscription will be used by Cashier even
though both would be synced with your application's
database.
Checking Subscription Status
Once a customer is subscribed to your application, you
may easily check their subscription status using a
variety of convenient methods. First, the
subscribed
method returns true
if the customer has an active subscription, even if the
subscription is currently within its trial period. The
subscribed
method accepts the type of the
subscription as its first argument:
if ($user->subscribed('default')) {
// ...
}
The subscribed
method also makes a great
candidate for a route
middleware, allowing you to filter access to
routes and controllers based on the user's subscription
status:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsSubscribed
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed('default')) {
// This user is not a paying customer...
return redirect('billing');
}
return $next($request);
}
}
If you would like to determine if a user is still within
their trial period, you may use the onTrial
method. This method can be useful for determining if you
should display a warning to the user that they are still
on their trial period:
if ($user->subscription('default')->onTrial()) {
// ...
}
The subscribedToProduct
method may be used
to determine if the user is subscribed to a given
product based on a given Stripe product's identifier. In
Stripe, products are collections of prices. In this
example, we will determine if the user's
default
subscription is actively subscribed
to the application's "premium" product. The
given Stripe product identifier should correspond to one
of your product's identifiers in the Stripe
dashboard:
if ($user->subscribedToProduct('prod_premium', 'default')) {
// ...
}
By passing an array to the
subscribedToProduct
method, you may
determine if the user's default
subscription is actively subscribed to the application's
"basic" or "premium" product:
if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) {
// ...
}
The subscribedToPrice
method may be used to
determine if a customer's subscription corresponds to a
given price ID:
if ($user->subscribedToPrice('price_basic_monthly', 'default')) {
// ...
}
The recurring
method may be used to
determine if the user is currently subscribed and is no
longer within their trial period:
if ($user->subscription('default')->recurring()) {
// ...
}
Warning!
If a user has two subscriptions with the same type, the most recent subscription will always be returned by thesubscription
method. For example, a user might have two subscription records with the type ofdefault
; however, one of the subscriptions may be an old, expired subscription, while the other is the current, active subscription. The most recent subscription will always be returned while older subscriptions are kept in the database for historical review.
Canceled Subscription Status
To determine if the user was once an active subscriber
but has canceled their subscription, you may use the
canceled
method:
if ($user->subscription('default')->canceled()) {
// ...
}
You may also determine if a user has canceled their
subscription but are still on their "grace
period" until the subscription fully expires. For
example, if a user cancels a subscription on March 5th
that was originally scheduled to expire on March 10th,
the user is on their "grace period" until
March 10th. Note that the subscribed
method
still returns true
during this time:
if ($user->subscription('default')->onGracePeriod()) {
// ...
}
To determine if the user has canceled their subscription
and is no longer within their "grace period",
you may use the ended
method:
if ($user->subscription('default')->ended()) {
// ...
}
Incomplete and Past Due Status
If a subscription requires a secondary payment action
after creation the subscription will be marked as
incomplete
. Subscription statuses are
stored in the stripe_status
column of
Cashier's subscriptions
database table.
Similarly, if a secondary payment action is required when
swapping prices the subscription will be marked as
past_due
. When your subscription is in
either of these states it will not be active until the
customer has confirmed their payment. Determining if a
subscription has an incomplete payment may be
accomplished using the hasIncompletePayment
method on the billable model or a subscription
instance:
if ($user->hasIncompletePayment('default')) {
// ...
}
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}
When a subscription has an incomplete payment, you should
direct the user to Cashier's payment confirmation page,
passing the latestPayment
identifier. You
may use the latestPayment
method available
on subscription instance to retrieve this
identifier:
<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
Please confirm your payment.
</a>
If you would like the subscription to still be considered
active when it's in a past_due
or
incomplete
state, you may use the
keepPastDueSubscriptionsActive
and
keepIncompleteSubscriptionsActive
methods
provided by Cashier. Typically, these methods should be
called in the register
method of your
App\Providers\AppServiceProvider
:
use Laravel\Cashier\Cashier;
/**
* Register any application services.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
Cashier::keepIncompleteSubscriptionsActive();
}
Warning!
When a subscription is in anincomplete
state it cannot be changed until the payment is confirmed. Therefore, theswap
andupdateQuantity
methods will throw an exception when the subscription is in anincomplete
state.
Subscription Scopes
Most subscription states are also available as query scopes so that you may easily query your database for subscriptions that are in a given state:
// Get all active subscriptions...
$subscriptions = Subscription::query()->active()->get();
// Get all of the canceled subscriptions for a user...
$subscriptions = $user->subscriptions()->canceled()->get();
A complete list of available scopes is available below:
Subscription::query()->active();
Subscription::query()->canceled();
Subscription::query()->ended();
Subscription::query()->incomplete();
Subscription::query()->notCanceled();
Subscription::query()->notOnGracePeriod();
Subscription::query()->notOnTrial();
Subscription::query()->onGracePeriod();
Subscription::query()->onTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
Changing Prices
After a customer is subscribed to your application, they
may occasionally want to change to a new subscription
price. To swap a customer to a new price, pass the
Stripe price's identifier to the swap
method. When swapping prices, it is assumed that the
user would like to re-activate their subscription if it
was previously canceled. The given price identifier
should correspond to a Stripe price identifier available
in the Stripe dashboard:
use App\Models\User;
$user = App\Models\User::find(1);
$user->subscription('default')->swap('price_yearly');
If the customer is on trial, the trial period will be maintained. Additionally, if a "quantity" exists for the subscription, that quantity will also be maintained.
If you would like to swap prices and cancel any trial
period the customer is currently on, you may invoke the
skipTrial
method:
$user->subscription('default')
->skipTrial()
->swap('price_yearly');
If you would like to swap prices and immediately invoice
the customer instead of waiting for their next billing
cycle, you may use the swapAndInvoice
method:
$user = User::find(1);
$user->subscription('default')->swapAndInvoice('price_yearly');
Prorations
By default, Stripe prorates charges when swapping between
prices. The noProrate
method may be used to
update the subscription's price without prorating the
charges:
$user->subscription('default')->noProrate()->swap('price_yearly');
For more information on subscription proration, consult the Stripe documentation.
Warning!
Executing thenoProrate
method before theswapAndInvoice
method will have no effect on proration. An invoice will always be issued.
Subscription Quantity
Sometimes subscriptions are affected by
"quantity". For example, a project management
application might charge $10 per month per project. You
may use the incrementQuantity
and
decrementQuantity
methods to easily
increment or decrement your subscription quantity:
use App\Models\User;
$user = User::find(1);
$user->subscription('default')->incrementQuantity();
// Add five to the subscription's current quantity...
$user->subscription('default')->incrementQuantity(5);
$user->subscription('default')->decrementQuantity();
// Subtract five from the subscription's current quantity...
$user->subscription('default')->decrementQuantity(5);
Alternatively, you may set a specific quantity using the
updateQuantity
method:
$user->subscription('default')->updateQuantity(10);
The noProrate
method may be used to update
the subscription's quantity without prorating the
charges:
$user->subscription('default')->noProrate()->updateQuantity(10);
For more information on subscription quantities, consult the Stripe documentation.
Quantities for Subscriptions With Multiple Products
If your subscription is a subscription with multiple products, you should pass the ID of the price whose quantity you wish to increment or decrement as the second argument to the increment / decrement methods:
$user->subscription('default')->incrementQuantity(1, 'price_chat');
Subscriptions With Multiple Products
Subscription
with multiple products allow you to assign
multiple billing products to a single subscription. For
example, imagine you are building a customer service
"helpdesk" application that has a base
subscription price of $10 per month but offers a live
chat add-on product for an additional $15 per month.
Information for subscriptions with multiple products is
stored in Cashier's subscription_items
database table.
You may specify multiple products for a given
subscription by passing an array of prices as the second
argument to the newSubscription
method:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', [
'price_monthly',
'price_chat',
])->create($request->paymentMethodId);
// ...
});
In the example above, the customer will have two prices
attached to their default
subscription.
Both prices will be charged on their respective billing
intervals. If necessary, you may use the
quantity
method to indicate a specific
quantity for each price:
$user = User::find(1);
$user->newSubscription('default', ['price_monthly', 'price_chat'])
->quantity(5, 'price_chat')
->create($paymentMethod);
If you would like to add another price to an existing
subscription, you may invoke the subscription's
addPrice
method:
$user = User::find(1);
$user->subscription('default')->addPrice('price_chat');
The example above will add the new price and the customer
will be billed for it on their next billing cycle. If
you would like to bill the customer immediately you may
use the addPriceAndInvoice
method:
$user->subscription('default')->addPriceAndInvoice('price_chat');
If you would like to add a price with a specific
quantity, you can pass the quantity as the second
argument of the addPrice
or
addPriceAndInvoice
methods:
$user = User::find(1);
$user->subscription('default')->addPrice('price_chat', 5);
You may remove prices from subscriptions using the
removePrice
method:
$user->subscription('default')->removePrice('price_chat');
Warning!
You may not remove the last price on a subscription. Instead, you should simply cancel the subscription.
Swapping Prices
You may also change the prices attached to a subscription
with multiple products. For example, imagine a customer
has a price_basic
subscription with a
price_chat
add-on product and you want to
upgrade the customer from the price_basic
to the price_pro
price:
use App\Models\User;
$user = User::find(1);
$user->subscription('default')->swap(['price_pro', 'price_chat']);
When executing the example above, the underlying
subscription item with the price_basic
is
deleted and the one with the price_chat
is
preserved. Additionally, a new subscription item for the
price_pro
is created.
You can also specify subscription item options by passing
an array of key / value pairs to the swap
method. For example, you may need to specify the
subscription price quantities:
$user = User::find(1);
$user->subscription('default')->swap([
'price_pro' => ['quantity' => 5],
'price_chat'
]);
If you want to swap a single price on a subscription, you
may do so using the swap
method on the
subscription item itself. This approach is particularly
useful if you would like to preserve all of the existing
metadata on the subscription's other prices:
$user = User::find(1);
$user->subscription('default')
->findItemOrFail('price_basic')
->swap('price_pro');
Proration
By default, Stripe will prorate charges when adding or
removing prices from a subscription with multiple
products. If you would like to make a price adjustment
without proration, you should chain the
noProrate
method onto your price
operation:
$user->subscription('default')->noProrate()->removePrice('price_chat');
Quantities
If you would like to update quantities on individual subscription prices, you may do so using the existing quantity methods by passing the ID of the price as an additional argument to the method:
$user = User::find(1);
$user->subscription('default')->incrementQuantity(5, 'price_chat');
$user->subscription('default')->decrementQuantity(3, 'price_chat');
$user->subscription('default')->updateQuantity(10, 'price_chat');
Warning!
When a subscription has multiple prices thestripe_price
andquantity
attributes on theSubscription
model will benull
. To access the individual price attributes, you should use theitems
relationship available on theSubscription
model.
Subscription Items
When a subscription has multiple prices, it will have
multiple subscription "items" stored in your
database's subscription_items
table. You
may access these via the items
relationship
on the subscription:
use App\Models\User;
$user = User::find(1);
$subscriptionItem = $user->subscription('default')->items->first();
// Retrieve the Stripe price and quantity for a specific item...
$stripePrice = $subscriptionItem->stripe_price;
$quantity = $subscriptionItem->quantity;
You can also retrieve a specific price using the
findItemOrFail
method:
$user = User::find(1);
$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');
Multiple Subscriptions
Stripe allows your customers to have multiple subscriptions simultaneously. For example, you may run a gym that offers a swimming subscription and a weight-lifting subscription, and each subscription may have different pricing. Of course, customers should be able to subscribe to either or both plans.
When your application creates subscriptions, you may
provide the type of the subscription to the
newSubscription
method. The type may be any
string that represents the type of subscription the user
is initiating:
use Illuminate\Http\Request;
Route::post('/swimming/subscribe', function (Request $request) {
$request->user()->newSubscription('swimming')
->price('price_swimming_monthly')
->create($request->paymentMethodId);
// ...
});
In this example, we initiated a monthly swimming
subscription for the customer. However, they may want to
swap to a yearly subscription at a later time. When
adjusting the customer's subscription, we can simply
swap the price on the swimming
subscription:
$user->subscription('swimming')->swap('price_swimming_yearly');
Of course, you may also cancel the subscription entirely:
$user->subscription('swimming')->cancel();
Metered Billing
Metered billing allows you to charge customers based on their product usage during a billing cycle. For example, you may charge customers based on the number of text messages or emails they send per month.
To start using metered billing, you will first need to
create a new product in your Stripe dashboard with a
metered price. Then, use the meteredPrice
to add the metered price ID to a customer
subscription:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default')
->meteredPrice('price_metered')
->create($request->paymentMethodId);
// ...
});
You may also start a metered subscription via Stripe Checkout:
$checkout = Auth::user()
->newSubscription('default', [])
->meteredPrice('price_metered')
->checkout();
return view('your-checkout-view', [
'checkout' => $checkout,
]);
Reporting Usage
As your customer uses your application, you will report
their usage to Stripe so that they can be billed
accurately. To increment the usage of a metered
subscription, you may use the reportUsage
method:
$user = User::find(1);
$user->subscription('default')->reportUsage();
By default, a "usage quantity" of 1 is added to the billing period. Alternatively, you may pass a specific amount of "usage" to add to the customer's usage for the billing period:
$user = User::find(1);
$user->subscription('default')->reportUsage(15);
If your application offers multiple prices on a single
subscription, you will need to use the
reportUsageFor
method to specify the
metered price you want to report usage for:
$user = User::find(1);
$user->subscription('default')->reportUsageFor('price_metered', 15);
Sometimes, you may need to update usage which you have
previously reported. To accomplish this, you may pass a
timestamp or a DateTimeInterface
instance
as the second parameter to reportUsage
.
When doing so, Stripe will update the usage that was
reported at that given time. You can continue to update
previous usage records as the given date and time is
still within the current billing period:
$user = User::find(1);
$user->subscription('default')->reportUsage(5, $timestamp);
Retrieving Usage Records
To retrieve a customer's past usage, you may use a
subscription instance's usageRecords
method:
$user = User::find(1);
$usageRecords = $user->subscription('default')->usageRecords();
If your application offers multiple prices on a single
subscription, you may use the
usageRecordsFor
method to specify the
metered price that you wish to retrieve usage records
for:
$user = User::find(1);
$usageRecords = $user->subscription('default')->usageRecordsFor('price_metered');
The usageRecords
and
usageRecordsFor
methods return a Collection
instance containing an associative array of usage
records. You may iterate over this array to display a
customer's total usage:
@foreach ($usageRecords as $usageRecord)
- Period Starting: {{ $usageRecord['period']['start'] }}
- Period Ending: {{ $usageRecord['period']['end'] }}
- Total Usage: {{ $usageRecord['total_usage'] }}
@endforeach
For a full reference of all usage data returned and how to use Stripe's cursor based pagination, please consult the official Stripe API documentation.
Subscription Taxes
Warning!
Instead of calculating Tax Rates manually, you can automatically calculate taxes using Stripe Tax
To specify the tax rates a user pays on a subscription,
you should implement the taxRates
method on
your billable model and return an array containing the
Stripe tax rate IDs. You can define these tax rates in
your
Stripe dashboard:
/**
* The tax rates that should apply to the customer's subscriptions.
*
* @return array<int, string>
*/
public function taxRates(): array
{
return ['txr_id'];
}
The taxRates
method enables you to apply a
tax rate on a customer-by-customer basis, which may be
helpful for a user base that spans multiple countries
and tax rates.
If you're offering subscriptions with multiple products,
you may define different tax rates for each price by
implementing a priceTaxRates
method on your
billable model:
/**
* The tax rates that should apply to the customer's subscriptions.
*
* @return array<string, array<int, string>>
*/
public function priceTaxRates(): array
{
return [
'price_monthly' => ['txr_id'],
];
}
Warning!
ThetaxRates
method only applies to subscription charges. If you use Cashier to make "one-off" charges, you will need to manually specify the tax rate at that time.
Syncing Tax Rates
When changing the hard-coded tax rate IDs returned by the
taxRates
method, the tax settings on any
existing subscriptions for the user will remain the
same. If you wish to update the tax value for existing
subscriptions with the new taxRates
values,
you should call the syncTaxRates
method on
the user's subscription instance:
$user->subscription('default')->syncTaxRates();
This will also sync any item tax rates for a subscription
with multiple products. If your application is offering
subscriptions with multiple products, you should ensure
that your billable model implements the
priceTaxRates
method discussed above.
Tax Exemption
Cashier also offers the isNotTaxExempt
,
isTaxExempt
, and
reverseChargeApplies
methods to determine
if the customer is tax exempt. These methods will call
the Stripe API to determine a customer's tax exemption
status:
use App\Models\User;
$user = User::find(1);
$user->isTaxExempt();
$user->isNotTaxExempt();
$user->reverseChargeApplies();
Warning!
These methods are also available on anyLaravel\Cashier\Invoice
object. However, when invoked on anInvoice
object, the methods will determine the exemption status at the time the invoice was created.
Subscription Anchor Date
By default, the billing cycle anchor is the date the
subscription was created or, if a trial period is used,
the date that the trial ends. If you would like to
modify the billing anchor date, you may use the
anchorBillingCycleOn
method:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$anchor = Carbon::parse('first day of next month');
$request->user()->newSubscription('default', 'price_monthly')
->anchorBillingCycleOn($anchor->startOfDay())
->create($request->paymentMethodId);
// ...
});
For more information on managing subscription billing cycles, consult the Stripe billing cycle documentation
Cancelling Subscriptions
To cancel a subscription, call the cancel
method on the user's subscription:
$user->subscription('default')->cancel();
When a subscription is canceled, Cashier will
automatically set the ends_at
column in
your subscriptions
database table. This
column is used to know when the subscribed
method should begin returning false
.
For example, if a customer cancels a subscription on
March 1st, but the subscription was not scheduled to end
until March 5th, the subscribed
method will
continue to return true
until March 5th.
This is done because a user is typically allowed to
continue using an application until the end of their
billing cycle.
You may determine if a user has canceled their
subscription but are still on their "grace
period" using the onGracePeriod
method:
if ($user->subscription('default')->onGracePeriod()) {
// ...
}
If you wish to cancel a subscription immediately, call
the cancelNow
method on the user's
subscription:
$user->subscription('default')->cancelNow();
If you wish to cancel a subscription immediately and
invoice any remaining un-invoiced metered usage or new /
pending proration invoice items, call the
cancelNowAndInvoice
method on the user's
subscription:
$user->subscription('default')->cancelNowAndInvoice();
You may also choose to cancel the subscription at a specific moment in time:
$user->subscription('default')->cancelAt(
now()->addDays(10)
);
Finally, you should always cancel user subscriptions before deleting the associated user model:
$user->subscription('default')->cancelNow();
$user->delete();
Resuming Subscriptions
If a customer has canceled their subscription and you
wish to resume it, you may invoke the
resume
method on the subscription. The
customer must still be within their "grace
period" in order to resume a subscription:
$user->subscription('default')->resume();
If the customer cancels a subscription and then resumes that subscription before the subscription has fully expired the customer will not be billed immediately. Instead, their subscription will be re-activated and they will be billed on the original billing cycle.
Subscription Trials
With Payment Method Up Front
If you would like to offer trial periods to your
customers while still collecting payment method
information up front, you should use the
trialDays
method when creating your
subscriptions:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', 'price_monthly')
->trialDays(10)
->create($request->paymentMethodId);
// ...
});
This method will set the trial period ending date on the
subscription record within the database and instruct
Stripe to not begin billing the customer until after
this date. When using the trialDays
method,
Cashier will overwrite any default trial period
configured for the price in Stripe.
Warning!
If the customer's subscription is not canceled before the trial ending date they will be charged as soon as the trial expires, so you should be sure to notify your users of their trial ending date.
The trialUntil
method allows you to provide
a DateTime
instance that specifies when the
trial period should end:
use Carbon\Carbon;
$user->newSubscription('default', 'price_monthly')
->trialUntil(Carbon::now()->addDays(10))
->create($paymentMethod);
You may determine if a user is within their trial period
using either the onTrial
method of the user
instance or the onTrial
method of the
subscription instance. The two examples below are
equivalent:
if ($user->onTrial('default')) {
// ...
}
if ($user->subscription('default')->onTrial()) {
// ...
}
You may use the endTrial
method to
immediately end a subscription trial:
$user->subscription('default')->endTrial();
To determine if an existing trial has expired, you may
use the hasExpiredTrial
methods:
if ($user->hasExpiredTrial('default')) {
// ...
}
if ($user->subscription('default')->hasExpiredTrial()) {
// ...
}
Defining Trial Days in Stripe / Cashier
You may choose to define how many trial days your price's
receive in the Stripe dashboard or always pass them
explicitly using Cashier. If you choose to define your
price's trial days in Stripe you should be aware that
new subscriptions, including new subscriptions for a
customer that had a subscription in the past, will
always receive a trial period unless you explicitly call
the skipTrial()
method.
Without Payment Method Up Front
If you would like to offer trial periods without
collecting the user's payment method information up
front, you may set the trial_ends_at
column
on the user record to your desired trial ending date.
This is typically done during user registration:
use App\Models\User;
$user = User::create([
// ...
'trial_ends_at' => now()->addDays(10),
]);
Warning!
Be sure to add a date cast for thetrial_ends_at
attribute within your billable model's class definition.
Cashier refers to this type of trial as a "generic
trial", since it is not attached to any existing
subscription. The onTrial
method on the
billable model instance will return true
if
the current date is not past the value of
trial_ends_at
:
if ($user->onTrial()) {
// User is within their trial period...
}
Once you are ready to create an actual subscription for
the user, you may use the newSubscription
method as usual:
$user = User::find(1);
$user->newSubscription('default', 'price_monthly')->create($paymentMethod);
To retrieve the user's trial ending date, you may use the
trialEndsAt
method. This method will return
a Carbon date instance if a user is on a trial or
null
if they aren't. You may also pass an
optional subscription type parameter if you would like
to get the trial ending date for a specific subscription
other than the default one:
if ($user->onTrial()) {
$trialEndsAt = $user->trialEndsAt('main');
}
You may also use the onGenericTrial
method
if you wish to know specifically that the user is within
their "generic" trial period and has not yet
created an actual subscription:
if ($user->onGenericTrial()) {
// User is within their "generic" trial period...
}
Extending Trials
The extendTrial
method allows you to extend
the trial period of a subscription after the
subscription has been created. If the trial has already
expired and the customer is already being billed for the
subscription, you can still offer them an extended
trial. The time spent within the trial period will be
deducted from the customer's next invoice:
use App\Models\User;
$subscription = User::find(1)->subscription('default');
// End the trial 7 days from now...
$subscription->extendTrial(
now()->addDays(7)
);
// Add an additional 5 days to the trial...
$subscription->extendTrial(
$subscription->trial_ends_at->addDays(5)
);
Handling Stripe Webhooks
Note:
You may use the Stripe CLI to help test webhooks during local development.
Stripe can notify your application of a variety of events via webhooks. By default, a route that points to Cashier's webhook controller is automatically registered by the Cashier service provider. This controller will handle all incoming webhook requests.
By default, the Cashier webhook controller will automatically handle cancelling subscriptions that have too many failed charges (as defined by your Stripe settings), customer updates, customer deletions, subscription updates, and payment method changes; however, as we'll soon discover, you can extend this controller to handle any Stripe webhook event you like.
To ensure your application can handle Stripe webhooks, be
sure to configure the webhook URL in the Stripe control
panel. By default, Cashier's webhook controller responds
to the /stripe/webhook
URL path. The full
list of all webhooks you should enable in the Stripe
control panel are:
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.updated
customer.deleted
payment_method.automatically_updated
invoice.payment_action_required
invoice.payment_succeeded
For convenience, Cashier includes a
cashier:webhook
Artisan command. This
command will create a webhook in Stripe that listens to
all of the events required by Cashier:
php artisan cashier:webhook
By default, the created webhook will point to the URL
defined by the APP_URL
environment variable
and the cashier.webhook
route that is
included with Cashier. You may provide the
--url
option when invoking the command if
you would like to use a different URL:
php artisan cashier:webhook --url "https://example.com/stripe/webhook"
The webhook that is created will use the Stripe API
version that your version of Cashier is compatible with.
If you would like to use a different Stripe version, you
may provide the --api-version
option:
php artisan cashier:webhook --api-version="2019-12-03"
After creation, the webhook will be immediately active.
If you wish to create the webhook but have it disabled
until you're ready, you may provide the
--disabled
option when invoking the
command:
php artisan cashier:webhook --disabled
Warning!
Make sure you protect incoming Stripe webhook requests with Cashier's included webhook signature verification middleware.
Webhooks and CSRF Protection
Since Stripe webhooks need to bypass Laravel's CSRF protection, be sure to
list the URI as an exception in your application's
App\Http\Middleware\VerifyCsrfToken
middleware or list the route outside of the
web
middleware group:
protected $except = [
'stripe/*',
];
Defining Webhook Event Handlers
Cashier automatically handles subscription cancellations for failed charges and other common Stripe webhook events. However, if you have additional webhook events you would like to handle, you may do so by listening to the following events that are dispatched by Cashier:
Laravel\Cashier\Events\WebhookReceived
Laravel\Cashier\Events\WebhookHandled
Both events contain the full payload of the Stripe
webhook. For example, if you wish to handle the
invoice.payment_succeeded
webhook, you may
register a listener
that will handle the event:
<?php
namespace App\Listeners;
use Laravel\Cashier\Events\WebhookReceived;
class StripeEventListener
{
/**
* Handle received Stripe webhooks.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['type'] === 'invoice.payment_succeeded') {
// Handle the incoming event...
}
}
}
Once your listener has been defined, you may register it
within your application's
EventServiceProvider
:
<?php
namespace App\Providers;
use App\Listeners\StripeEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Cashier\Events\WebhookReceived;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
WebhookReceived::class => [
StripeEventListener::class,
],
];
}
Verifying Webhook Signatures
To secure your webhooks, you may use Stripe's webhook signatures. For convenience, Cashier automatically includes a middleware which validates that the incoming Stripe webhook request is valid.
To enable webhook verification, ensure that the
STRIPE_WEBHOOK_SECRET
environment variable
is set in your application's .env
file. The
webhook secret
may be retrieved from your
Stripe account dashboard.
Single Charges
Simple Charge
If you would like to make a one-time charge against a
customer, you may use the charge
method on
a billable model instance. You will need to provide a
payment method identifier as the second argument
to the charge
method:
use Illuminate\Http\Request;
Route::post('/purchase', function (Request $request) {
$stripeCharge = $request->user()->charge(
100, $request->paymentMethodId
);
// ...
});
The charge
method accepts an array as its
third argument, allowing you to pass any options you
wish to the underlying Stripe charge creation. More
information regarding the options available to you when
creating charges may be found in the Stripe
documentation:
$user->charge(100, $paymentMethod, [
'custom_option' => $value,
]);
You may also use the charge
method without
an underlying customer or user. To accomplish this,
invoke the charge
method on a new instance
of your application's billable model:
use App\Models\User;
$stripeCharge = (new User)->charge(100, $paymentMethod);
The charge
method will throw an exception if
the charge fails. If the charge is successful, an
instance of Laravel\Cashier\Payment
will be
returned from the method:
try {
$payment = $user->charge(100, $paymentMethod);
} catch (Exception $e) {
// ...
}
Warning!
Thecharge
method accepts the payment amount in the lowest denominator of the currency used by your application. For example, if customers are paying in United States Dollars, amounts should be specified in pennies.
Charge With Invoice
Sometimes you may need to make a one-time charge and
offer a PDF invoice to your customer. The
invoicePrice
method lets you do just that.
For example, let's invoice a customer for five new
shirts:
$user->invoicePrice('price_tshirt', 5);
The invoice will be immediately charged against the
user's default payment method. The
invoicePrice
method also accepts an array
as its third argument. This array contains the billing
options for the invoice item. The fourth argument
accepted by the method is also an array which should
contain the billing options for the invoice itself:
$user->invoicePrice('price_tshirt', 5, [
'discounts' => [
['coupon' => 'SUMMER21SALE']
],
], [
'default_tax_rates' => ['txr_id'],
]);
Similarly to invoicePrice
, you may use the
tabPrice
method to create a one-time charge
for multiple items (up to 250 items per invoice) by
adding them to the customer's "tab" and then
invoicing the customer. For example, we may invoice a
customer for five shirts and two mugs:
$user->tabPrice('price_tshirt', 5);
$user->tabPrice('price_mug', 2);
$user->invoice();
Alternatively, you may use the invoiceFor
method to make a "one-off" charge against the
customer's default payment method:
$user->invoiceFor('One Time Fee', 500);
Although the invoiceFor
method is available
for you to use, it is recommended that you use the
invoicePrice
and tabPrice
methods with pre-defined prices. By doing so, you will
have access to better analytics and data within your
Stripe dashboard regarding your sales on a per-product
basis.
Warning!
Theinvoice
,invoicePrice
, andinvoiceFor
methods will create a Stripe invoice which will retry failed billing attempts. If you do not want invoices to retry failed charges, you will need to close them using the Stripe API after the first failed charge.
Creating Payment Intents
You can create a new Stripe payment intent by invoking
the pay
method on a billable model
instance. Calling this method will create a payment
intent that is wrapped in a
Laravel\Cashier\Payment
instance:
use Illuminate\Http\Request;
Route::post('/pay', function (Request $request) {
$payment = $request->user()->pay(
$request->get('amount')
);
return $payment->client_secret;
});
After creating the payment intent, you can return the client secret to your application's frontend so that the user can complete the payment in their browser. To read more about building entire payment flows using Stripe payment intents, please consult the Stripe documentation.
When using the pay
method, the default
payment methods that are enabled within your Stripe
dashboard will be available to the customer.
Alternatively, if you only want to allow for some
specific payment methods to be used, you may use the
payWith
method:
use Illuminate\Http\Request;
Route::post('/pay', function (Request $request) {
$payment = $request->user()->payWith(
$request->get('amount'), ['card', 'bancontact']
);
return $payment->client_secret;
});
Warning!
Thepay
andpayWith
methods accept the payment amount in the lowest denominator of the currency used by your application. For example, if customers are paying in United States Dollars, amounts should be specified in pennies.
Refunding Charges
If you need to refund a Stripe charge, you may use the
refund
method. This method accepts the
Stripe payment
intent ID as its first argument:
$payment = $user->charge(100, $paymentMethodId);
$user->refund($payment->id);
Invoices
Retrieving Invoices
You may easily retrieve an array of a billable model's
invoices using the invoices
method. The
invoices
method returns a collection of
Laravel\Cashier\Invoice
instances:
$invoices = $user->invoices();
If you would like to include pending invoices in the
results, you may use the
invoicesIncludingPending
method:
$invoices = $user->invoicesIncludingPending();
You may use the findInvoice
method to
retrieve a specific invoice by its ID:
$invoice = $user->findInvoice($invoiceId);
Displaying Invoice Information
When listing the invoices for the customer, you may use the invoice's methods to display the relevant invoice information. For example, you may wish to list every invoice in a table, allowing the user to easily download any of them:
<table>
@foreach ($invoices as $invoice)
<tr>
<td>{{ $invoice->date()->toFormattedDateString() }}</td>
<td>{{ $invoice->total() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
</tr>
@endforeach
</table>
Upcoming Invoices
To retrieve the upcoming invoice for a customer, you may
use the upcomingInvoice
method:
$invoice = $user->upcomingInvoice();
Similarly, if the customer has multiple subscriptions, you can also retrieve the upcoming invoice for a specific subscription:
$invoice = $user->subscription('default')->upcomingInvoice();
Previewing Subscription Invoices
Using the previewInvoice
method, you can
preview an invoice before making price changes. This
will allow you to determine what your customer's invoice
will look like when a given price change is made:
$invoice = $user->subscription('default')->previewInvoice('price_yearly');
You may pass an array of prices to the
previewInvoice
method in order to preview
invoices with multiple new prices:
$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);
Generating Invoice PDFs
Before generating invoice PDFs, you should use Composer to install the Dompdf library, which is the default invoice renderer for Cashier:
composer require dompdf/dompdf
From within a route or controller, you may use the
downloadInvoice
method to generate a PDF
download of a given invoice. This method will
automatically generate the proper HTTP response needed
to download the invoice:
use Illuminate\Http\Request;
Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) {
return $request->user()->downloadInvoice($invoiceId);
});
By default, all data on the invoice is derived from the
customer and invoice data stored in Stripe. The filename
is based on your app.name
config value.
However, you can customize some of this data by
providing an array as the second argument to the
downloadInvoice
method. This array allows
you to customize information such as your company and
product details:
return $request->user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
'street' => 'Main Str. 1',
'location' => '2000 Antwerp, Belgium',
'phone' => ' 32 499 00 00 00',
'email' => 'info@example.com',
'url' => 'https://example.com',
'vendorVat' => 'BE123456789',
]);
The downloadInvoice
method also allows for a
custom filename via its third argument. This filename
will automatically be suffixed with
.pdf
:
return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');
Custom Invoice Renderer
Cashier also makes it possible to use a custom invoice
renderer. By default, Cashier uses the
DompdfInvoiceRenderer
implementation, which
utilizes the dompdf
PHP library to generate Cashier's invoices. However, you
may use any renderer you wish by implementing the
Laravel\Cashier\Contracts\InvoiceRenderer
interface. For example, you may wish to render an
invoice PDF using an API call to a third-party PDF
rendering service:
use Illuminate\Support\Facades\Http;
use Laravel\Cashier\Contracts\InvoiceRenderer;
use Laravel\Cashier\Invoice;
class ApiInvoiceRenderer implements InvoiceRenderer
{
/**
* Render the given invoice and return the raw PDF bytes.
*/
public function render(Invoice $invoice, array $data = [], array $options = []): string
{
$html = $invoice->view($data)->render();
return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body();
}
}
Once you have implemented the invoice renderer contract,
you should update the
cashier.invoices.renderer
configuration
value in your application's
config/cashier.php
configuration file. This
configuration value should be set to the class name of
your custom renderer implementation.
Checkout
Cashier Stripe also provides support for Stripe Checkout. Stripe Checkout takes the pain out of implementing custom pages to accept payments by providing a pre-built, hosted payment page.
The following documentation contains information on how to get started using Stripe Checkout with Cashier. To learn more about Stripe Checkout, you should also consider reviewing Stripe's own documentation on Checkout.
Product Checkouts
You may perform a checkout for an existing product that
has been created within your Stripe dashboard using the
checkout
method on a billable model. The
checkout
method will initiate a new Stripe
Checkout session. By default, you're required to pass a
Stripe Price ID:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout('price_tshirt');
});
If needed, you may also specify a product quantity:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 15]);
});
When a customer visits this route they will be redirected
to Stripe's Checkout page. By default, when a user
successfully completes or cancels a purchase they will
be redirected to your home
route location,
but you may specify custom callback URLs using the
success_url
and cancel_url
options:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
When defining your success_url
checkout
option, you may instruct Stripe to add the checkout
session ID as a query string parameter when invoking
your URL. To do so, add the literal string
{CHECKOUT_SESSION_ID}
to your
success_url
query string. Stripe will
replace this placeholder with the actual checkout
session ID:
use Illuminate\Http\Request;
use Stripe\Checkout\Session;
use Stripe\Customer;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout-cancel'),
]);
});
Route::get('/checkout-success', function (Request $request) {
$checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id'));
return view('checkout.success', ['checkoutSession' => $checkoutSession]);
})->name('checkout-success');
Promotion Codes
By default, Stripe Checkout does not allow user
redeemable promotion codes. Luckily, there's an
easy way to enable these for your Checkout page. To do
so, you may invoke the allowPromotionCodes
method:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()
->allowPromotionCodes()
->checkout('price_tshirt');
});
Single Charge Checkouts
You can also perform a simple charge for an ad-hoc
product that has not been created in your Stripe
dashboard. To do so you may use the
checkoutCharge
method on a billable model
and pass it a chargeable amount, a product name, and an
optional quantity. When a customer visits this route
they will be redirected to Stripe's Checkout page:
use Illuminate\Http\Request;
Route::get('/charge-checkout', function (Request $request) {
return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);
});
Warning!
When using thecheckoutCharge
method, Stripe will always create a new product and price in your Stripe dashboard. Therefore, we recommend that you create the products up front in your Stripe dashboard and use thecheckout
method instead.
Subscription Checkouts
Warning!
Using Stripe Checkout for subscriptions requires you to enable thecustomer.subscription.created
webhook in your Stripe dashboard. This webhook will create the subscription record in your database and store all of the relevant subscription items.
You may also use Stripe Checkout to initiate
subscriptions. After defining your subscription with
Cashier's subscription builder methods, you may call the
checkout
method. When a customer visits
this route they will be redirected to Stripe's Checkout
page:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout();
});
Just as with product checkouts, you may customize the success and cancellation URLs:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
Of course, you can also enable promotion codes for subscription checkouts:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->allowPromotionCodes()
->checkout();
});
Warning!
Unfortunately Stripe Checkout does not support all subscription billing options when starting subscriptions. Using theanchorBillingCycleOn
method on the subscription builder, setting proration behavior, or setting payment behavior will not have any effect during Stripe Checkout sessions. Please consult the Stripe Checkout Session API documentation to review which parameters are available.
Stripe Checkout and Trial Periods
Of course, you can define a trial period when building a subscription that will be completed using Stripe Checkout:
$checkout = Auth::user()->newSubscription('default', 'price_monthly')
->trialDays(3)
->checkout();
However, the trial period must be at least 48 hours, which is the minimum amount of trial time supported by Stripe Checkout.
Subscriptions and Webhooks
Remember, Stripe and Cashier update subscription statuses via webhooks, so there's a possibility a subscription might not yet be active when the customer returns to the application after entering their payment information. To handle this scenario, you may wish to display a message informing the user that their payment or subscription is pending.
Collecting Tax IDs
Checkout also supports collecting a customer's Tax ID. To
enable this on a checkout session, invoke the
collectTaxIds
method when creating the
session:
$checkout = $user->collectTaxIds()->checkout('price_tshirt');
When this method is invoked, a new checkbox will be available to the customer that allows them to indicate if they're purchasing as a company. If so, they will have the opportunity to provide their Tax ID number.
Warning!
If you have already configured automatic tax collection in your application's service provider then this feature will be enabled automatically and there is no need to invoke thecollectTaxIds
method.
Guest Checkouts
Using the Checkout::guest
method, you may
initiate checkout sessions for guests of your
application that do not have an "account":
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
Similarly to when creating checkout sessions for existing
users, you may utilize additional methods available on
the Laravel\Cashier\CheckoutBuilder
instance to customize the guest checkout session:
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()
->withPromotionCode('promo-code')
->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
After a guest checkout has been completed, Stripe can
dispatch a checkout.session.completed
webhook event, so make sure to configure
your Stripe webhook to actually send this event
to your application. Once the webhook has been enabled
within the Stripe dashboard, you may handle the webhook
with Cashier. The object contained in the
webhook payload will be a checkout
object that you may inspect in order to fulfill
your customer's order.
Handling Failed Payments
Sometimes, payments for subscriptions or single charges
can fail. When this happens, Cashier will throw an
Laravel\Cashier\Exceptions\IncompletePayment
exception that informs you that this happened. After
catching this exception, you have two options on how to
proceed.
First, you could redirect your customer to the dedicated
payment confirmation page which is included with
Cashier. This page already has an associated named route
that is registered via Cashier's service provider. So,
you may catch the IncompletePayment
exception and redirect the user to the payment
confirmation page:
use Laravel\Cashier\Exceptions\IncompletePayment;
try {
$subscription = $user->newSubscription('default', 'price_monthly')
->create($paymentMethod);
} catch (IncompletePayment $exception) {
return redirect()->route(
'cashier.payment',
[$exception->payment->id, 'redirect' => route('home')]
);
}
On the payment confirmation page, the customer will be
prompted to enter their credit card information again
and perform any additional actions required by Stripe,
such as "3D Secure" confirmation. After
confirming their payment, the user will be redirected to
the URL provided by the redirect
parameter
specified above. Upon redirection, message
(string) and success
(integer) query string
variables will be added to the URL. The payment page
currently supports the following payment method
types:
- Credit Cards
- Alipay
- Bancontact
- BECS Direct Debit
- EPS
- Giropay
- iDEAL
- SEPA Direct Debit
Alternatively, you could allow Stripe to handle the
payment confirmation for you. In this case, instead of
redirecting to the payment confirmation page, you may setup
Stripe's automatic billing emails in your Stripe
dashboard. However, if an IncompletePayment
exception is caught, you should still inform the user
they will receive an email with further payment
confirmation instructions.
Payment exceptions may be thrown for the following
methods: charge
, invoiceFor
,
and invoice
on models using the
Billable
trait. When interacting with
subscriptions, the create
method on the
SubscriptionBuilder
, and the
incrementAndInvoice
and
swapAndInvoice
methods on the
Subscription
and
SubscriptionItem
models may throw
incomplete payment exceptions.
Determining if an existing subscription has an incomplete
payment may be accomplished using the
hasIncompletePayment
method on the billable
model or a subscription instance:
if ($user->hasIncompletePayment('default')) {
// ...
}
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}
You can derive the specific status of an incomplete
payment by inspecting the payment
property
on the exception instance:
use Laravel\Cashier\Exceptions\IncompletePayment;
try {
$user->charge(1000, 'pm_card_threeDSecure2Required');
} catch (IncompletePayment $exception) {
// Get the payment intent status...
$exception->payment->status;
// Check specific conditions...
if ($exception->payment->requiresPaymentMethod()) {
// ...
} elseif ($exception->payment->requiresConfirmation()) {
// ...
}
}
Confirming Payments
Some payment methods require additional data in order to
confirm payments. For example, SEPA payment methods
require additional "mandate" data during the
payment process. You may provide this data to Cashier
using the withPaymentConfirmationOptions
method:
$subscription->withPaymentConfirmationOptions([
'mandate_data' => '...',
])->swap('price_xxx');
You may consult the Stripe API documentation to review all of the options accepted when confirming payments.
Strong Customer Authentication
If your business or one of your customers is based in Europe you will need to abide by the EU's Strong Customer Authentication (SCA) regulations. These regulations were imposed in September 2019 by the European Union to prevent payment fraud. Luckily, Stripe and Cashier are prepared for building SCA compliant applications.
Warning!
Before getting started, review Stripe's guide on PSD2 and SCA as well as their documentation on the new SCA APIs.
Payments Requiring Additional Confirmation
SCA regulations often require extra verification in order
to confirm and process a payment. When this happens,
Cashier will throw a
Laravel\Cashier\Exceptions\IncompletePayment
exception that informs you that extra verification is
needed. More information on how to handle these
exceptions be found can be found in the documentation on
handling failed
payments.
Payment confirmation screens presented by Stripe or Cashier may be tailored to a specific bank or card issuer's payment flow and can include additional card confirmation, a temporary small charge, separate device authentication, or other forms of verification.
Incomplete and Past Due State
When a payment needs additional confirmation, the
subscription will remain in an incomplete
or past_due
state as indicated by its
stripe_status
database column. Cashier will
automatically activate the customer's subscription as
soon as payment confirmation is complete and your
application is notified by Stripe via webhook of its
completion.
For more information on incomplete
and
past_due
states, please refer to our
additional documentation on these states.
Off-Session Payment Notifications
Since SCA regulations require customers to occasionally
verify their payment details even while their
subscription is active, Cashier can send a notification
to the customer when off-session payment confirmation is
required. For example, this may occur when a
subscription is renewing. Cashier's payment notification
can be enabled by setting the
CASHIER_PAYMENT_NOTIFICATION
environment
variable to a notification class. By default, this
notification is disabled. Of course, Cashier includes a
notification class you may use for this purpose, but you
are free to provide your own notification class if
desired:
CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment
To ensure that off-session payment confirmation
notifications are delivered, verify that Stripe webhooks are
configured for your application and the
invoice.payment_action_required
webhook is
enabled in your Stripe dashboard. In addition, your
Billable
model should also use Laravel's
Illuminate\Notifications\Notifiable
trait.
Warning!
Notifications will be sent even when customers are manually making a payment that requires additional confirmation. Unfortunately, there is no way for Stripe to know that the payment was done manually or "off-session". But, a customer will simply see a "Payment Successful" message if they visit the payment page after already confirming their payment. The customer will not be allowed to accidentally confirm the same payment twice and incur an accidental second charge.
Stripe SDK
Many of Cashier's objects are wrappers around Stripe SDK
objects. If you would like to interact with the Stripe
objects directly, you may conveniently retrieve them
using the asStripe
method:
$stripeSubscription = $subscription->asStripeSubscription();
$stripeSubscription->application_fee_percent = 5;
$stripeSubscription->save();
You may also use the
updateStripeSubscription
method to update a
Stripe subscription directly:
$subscription->updateStripeSubscription(['application_fee_percent' => 5]);
You may invoke the stripe
method on the
Cashier
class if you would like to use the
Stripe\StripeClient
client directly. For
example, you could use this method to access the
StripeClient
instance and retrieve a list
of prices from your Stripe account:
use Laravel\Cashier\Cashier;
$prices = Cashier::stripe()->prices->all();
Testing
When testing an application that uses Cashier, you may mock the actual HTTP requests to the Stripe API; however, this requires you to partially re-implement Cashier's own behavior. Therefore, we recommend allowing your tests to hit the actual Stripe API. While this is slower, it provides more confidence that your application is working as expected and any slow tests may be placed within their own PHPUnit testing group.
When testing, remember that Cashier itself already has a great test suite, so you should only focus on testing the subscription and payment flow of your own application and not every underlying Cashier behavior.
To get started, add the testing version
of your Stripe secret to your phpunit.xml
file:
<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>
Now, whenever you interact with Cashier while testing, it will send actual API requests to your Stripe testing environment. For convenience, you should pre-fill your Stripe testing account with subscriptions / prices that you may use during testing.
Note:
In order to test a variety of billing scenarios, such as credit card denials and failures, you may use the vast range of testing card numbers and tokens provided by Stripe.