The latest versions of Laravel and Cashier have brought many changes and made it significantly easier to build features in our web applications. In this tutorial, we will build a simple subscription system using Stripe.

Run the following command.

composer require laravel/cashier

This will install migrations for adding required columns to the users table, and also create a subscriptions table. So let us run the migrations.

php artisan migrate

Optionally, you can publish the migration files to your project database directory along with other migrations.

php artisan vendor:publish --tag="cashier-migrations"

We will also require stripe-php package later for retrieving our subsriptions from our stripe account, so let us install it also.

composer require stripe/stripe-php


Add the Billable trait to the User model.

namespace App;...
use Laravel\Cashier\Billable;class User extends Authenticatable {
  use Billable;

Go to Create an account and go to your Dashboard. There are 2 steps to be followed here

First, Here we need to create a Product for each of the subscription plans we want to offer. Set a name and a price for each.

Dashboard -> Products

For this example, I have created 2 plans, Basic and Premium, priced at £ 10.00 and £ 50.00 respectively.

Grab the API keys from Stripe.

Dashboard -> Developers -> API Keys

Copy the Publishable Key and the Secret Key and add them in the .env file. Also, add the currency in the file, same as the one you selected in the Stripe dashboard.

project/.env file

STRIPE_KEY=your publishable key here

Also in the services file,


Add the following array

'stripe' => [
  'model' => App\User::class,
  'key' => env('STRIPE_KEY'),
  'secret' => env('STRIPE_SECRET'),

Now we can start working on the main feature.

Run the command to create a new controller

php artisan make:controller SubscriptionController

Go to the created file


Add the necessary imports on the top.

<?phpnamespace App\Http\Controllers;require_once('../vendor/autoload.php');use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Cashier\Cashier;
use \Stripe\Stripe;class SubscriptionController extends Controller {}

Now write the functions inside the class.

public function __construct() {
   }   public function retrievePlans() {
       $key = \config('services.stripe.secret');
       $stripe = new \Stripe\StripeClient($key);
       $plansraw = $stripe->plans->all();
       $plans = $plansraw->data;
       foreach($plans as $plan) {
           $prod = $stripe->products->retrieve(
           $plan->product = $prod;
       return $plans;
   }   public function showSubscription() {       $plans = $this->retrievePlans();
       $user = Auth::user();
       return view('seller.pages.subscribe', [
           'intent' => $user->createSetupIntent(),
           'plans' => $plans
   }   public function processSubscription(Request $request)
       $user = Auth::user();
       $paymentMethod = $request->input('payment_method');
       $plan = $request->input('plan');       try {
           $user->newSubscription('default', $plan)->create($paymentMethod, [
               'email' => $user->email
       } catch (\Exception $e) {
           return back()->withErrors(['message' => 'Error creating subscription. ' . $e->getMessage()]);
       return redirect('dashboard');

Now we have the controller for returning the list of stripe plans to our subscription view.


Route::get('/subscribe', 'SubscriptionController@showSubscription');
      Route::post('/subscribe', 'SubscriptionController@processSubscription');      // welcome page only for subscribed users
      Route::get('/welcome', 'SubscriptionController@showWelcome')->middleware('subscribed');

The first 2 routes are the ones we are working with. The next, welcome route, has a middleware, we will be discussing that in the last step of this tutorial, so don’t pay attention to it now.


Here, I will show only the relevant parts of the view, because your theme, scaffolding, and view would be entirely different.

Add the styles required for the Stripe element (shown for entering card details), at the top.

    .StripeElement {
        background-color: white;
        padding: 8px 12px;
        border-radius: 4px;
        border: 1px solid transparent;
        box-shadow: 0 1px 3px 0 #e6ebf1;
        -webkit-transition: box-shadow 150ms ease;
        transition: box-shadow 150ms ease;
    .StripeElement--focus {
        box-shadow: 0 1px 3px 0 #cfd7df;
    .StripeElement--invalid {
        border-color: #fa755a;
    .StripeElement--webkit-autofill {
        background-color: #fefde5 !important;

Now create the form. Here I am showing a view with a list of plans as radio selection option. You can choose a better way to display and choose it. Just make sure the value is submitted correctly in the form submission.

<form action="/seller/subscribe" method="POST" id="subscribe-form">
    <div class="form-group">
        <div class="row">
            @foreach($plans as $plan)            <div class="col-md-4">                <div class="subscription-option">
                    <input type="radio" id="plan-silver" name="plan" value='{{$plan->id}}'>
                    <label for="plan-silver">
                        <span class="plan-price">{{$plan->currency}}{{$plan->amount/100}}<small> /{{$plan->interval}}</small></span>
                        <span class="plan-name">{{$plan->product->name}}</span>
                </div>            </div>            @endforeach        </div>
    </div>    <input id="card-holder-name" type="text"><label for="card-holder-name">Card Holder Name</label>    @csrf
    <div class="form-row">
        <label for="card-element">Credit or debit card</label>
        <div id="card-element" class="form-control">        </div>        <!-- Used to display form errors. -->
        <div id="card-errors" role="alert"></div>
    <div class="stripe-errors"></div>    @if (count($errors) > 0)
    <div class="alert alert-danger">
        @foreach ($errors->all() as $error)
        {{ $error }}<br>
    @endif    <div class="form-group text-center">
        <button  id="card-button" data-secret="{{ $intent->client_secret }}" class="btn btn-lg btn-success btn-block">SUBMIT</button>

Immediately below the closing tag of the FORM element, add the following javascript.

<script src=""></script><script>    var stripe = Stripe('{{ env('STRIPE_KEY') }}');
    var elements = stripe.elements();
    var style = {
        base: {
            color: '#32325d',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
    };    var card = elements.create('card', {hidePostalCode: true,
        style: style});    card.mount('#card-element');    card.addEventListener('change', function(event) {
        var displayError = document.getElementById('card-errors');
        if (event.error) {
            displayError.textContent = event.error.message;
        } else {
            displayError.textContent = '';
    });    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: card,
                    billing_details: { name: cardHolderName.value }
            );        if (error) {
            var errorElement = document.getElementById('card-errors');
            errorElement.textContent = error.message;        } else {            paymentMethodHandler(setupIntent.payment_method);
    });    function paymentMethodHandler(payment_method) {
        var form = document.getElementById('subscribe-form');
        var hiddenInput = document.createElement('input');
        hiddenInput.setAttribute('type', 'hidden');
        hiddenInput.setAttribute('name', 'payment_method');
        hiddenInput.setAttribute('value', payment_method);
        form.appendChild(hiddenInput);        form.submit();

Awesome! Now you can navigate to the /seller/subscribe route and use test credentials of Stripe payment and test it out! It should work if you didn’t mess up.

Now you might want to redirect the user to subscribe page directly if they are not already subscribed and to a dashboard of they are already subscribed. Or you may want to show a button and information in the profile/dashboard according to subscription status. You can use the following snippets for checking.

Pay attention to the name of subscription ‘default’ when performing such actions. All the methods return true/false.

Auth::user()->subscribed('default');  //Check if user is subscribed
Auth::user()->subscription('main')->onGracePeriod(); //cancelled but current subscription has not ende
Auth::user()->onPlan('bronze'); //check which plan.
Auth::user()->subscription('default')->cancelled(); //user earlier had a sibscription but cancelled (and no longer on grace period)


We defined a route above for a ‘welcome’ page only for subscribed users, with a middleware named Subscribed. Let us work on that now.

Some pages you may want to display only if the user is subscribed, otherwise, it should not display at all; the route should not work for the non-subscribed users. We can achieve this using a middleware, in 4 simple steps.

First, create the middleware.

php artisan make:middleware Subscribed

Second, to the newly created file and replace the code.


namespace App\Http\Middleware;
use Closure;class Subscribed {  public function handle($request, Closure $next) {
    if ($request->user() and ! $request->user()->subscribed('default'))
      return redirect('subscribe');    return $next($request);

Third, go the Kernel file and add the middleware.


Add the middleware to the routeMiddleware array.

protected $routeMiddleware = [
'subscribed' => \App\Http\Middleware\Subscribed::class,

Fourth, just add the middleware to the routes you want to protect


Route::get('/welcome', 'Seller\SubscriptionController@showWelcome')->middleware('subscribed');

Or, if you wish to use this for several routes, wrap them in a group like this:

Route::group(['middleware' => ['role:seller']], function () {
  Route::get('/welcome', 'Seller\SubscriptionController@showWelcome');

If this helped you, share, clap, and leave a comment!