Laravel Cashier has released today with support for SCA in Stripe, and removing support for Braintree.

This is a huge update, and many many thanks to every single person who contributed to the codebase or documentation for this update.

A big shout out has to go to Dries Vints though, for what has been nothing less than almost a total re-write of the package and it's documentation.

Fundamental changes to flow

Previous versions of Cashier stepped in AFTER you'd generated a payment source token on the client side - either by using Stripe Elements or the now defunkt popup Checkout.

The old workflow was something like this:

  1. Generate a payment source token on the client side - by having the user enter their card details.
  2. Pass this token to Cashier to begin a subscription, or update the payment method.
  3. Profit $$$

The new way is a little more complicated, and has to take into account:

  • 3D secure Version 2 (where customers may be asked by the bank to confirm their purchase using 2-factor authentication methods)
  • Payment confirmations, where they have to confirm their payment by entering their details again.
  • Off-session payment confirmations, where the bank decides a recurring payment may need confirmation, and the user is not directly accessing your website at the time.

Because off-session confirmations and confirmations can add extra time to the process, you can no longer directly verify that a payment has been completed during the space of one server request as you have been able to do with Cashier in the past.

So the new flow looks something like this when someone is purchasing a subscription:

  1. Generate an "Intent" (in this case, a SetupIntent) which allows stripe to track the status of a payment or payment method.

  2. Collect card details on the client side, while also passing javascript the ID of the "Intent" object to your Javascript.

  3. You can either manually attach this payment method to a customer, or pass it to the subscription create method where it will it attach it for you.

  4. The payment will either complete, require 3D Secure confirmation, require a payment confirmation, or fail for standard card-processing reasons.

  5. (If 3D Secure is required) the customer follows this process, stripe elements will create a popup window for this, and it will either confirm the payment, fail, or also require a confirmation.

  6. (If a confirmation is required) you can choose to either tell the user that a confirmation is required, and let Stripe send them an email, or you can redirect them to the confirmation route that ships with cashier yourself. Either way, the subscription will still not be active.

  7. If the payment is successful, and all confirmations and extra steps that were required are completed successfully, stripe will send a webhook to your application which will confirm the subscription.

Looks daunting doesn't it? But not that much has changed except that:

  1. There is an extra entity to create at first, the SetupIntent, this is done by calling $user->createSetupIntent(), so all the hard work is done for you!

  2. You need to pass the publishable key of this intent to the Strip Elements library, and use stripe.handleCardPayment from the JS SDK rather than Stripe.createToken that you may previously have been using.

  3. Webhooks are now the main way in which a subscription will be confirmed, so you have to be able to test these locally.

Off-session notifications.

Because subscriptions make recurring charges in the background, sometimes the payment provider or bank may want extra confirmation that it's okay to charge this amount again.

When this happens, Cashier will send a webhook () to your app, which will trigger an email to the customer that they require additional confirmation.

To get your app to handle this, add the handler to your .env file:


You can also, I believe, get Stripe to handle the email and confirmation payment via billing emails.

If this confirmation is not provided, it will act like the payment has failed and move on to the dunning process you've specified in your Stripe settings.

Webhooks, and testing them locally.

Webhooks can be a pain, Stripe has some great tooling to let you see the payloads it sends to your application and inspect them, but it also brings up one big problem - testing webhooks locally.

The official docs mention the fact that you can use the valet share command to test webhooks, this will give you an Ngrok URL that securely tunnels traffic into your local copy of your app, meaning you can not only receive webhooks locally, but also inspect their content using Ngrok's tools.

Another tool i use for ease of use (as it gives you a more fixed URL) is Ultrahook - it's also handy if you don't happen to be using Laravel Valet to run your sites locally.