Laravel Stripe Payment Gateway Integration Example

The Stripe payment gateway has built an easy-to-use reputation for developers and store owners. In addition, because of its excellent compatibility with Laravel, Stripe has become the go-to payment gateway for Laravel eCommerce stores.

The demand for Saas-based platforms is increasing daily; nowadays, building a subscription-based system is universal. So to make things easy on the backend, like Laravel, we need some packages that can help us build scalable, securable, and reliable web applications that deal with payment systems. 

Here are the steps to integrate the stripe payment gateway:

Step 1: Install and configure Laravel.

Type the following command.

laravel new stripesub

Go to the project folder and open the project in an editor. I am using VSCode.

cd stripesub && code .

Install the js dependencies using the following command.

npm install

Install the Cashier package for Stripe dependencies.

composer require laravel/cashier

Also, create an authentication scaffold.

php artisan make:auth

Step 2: Create an essential database migration

Before using Cashier, we’ll also need to prepare the database.

We need to add several columns to your users’ table and create new subscriptions and plans tables to hold all of our customer’s subscriptions.

Now, go to the create_users_table.php and add the following schema.

$table->string('stripe_id')->nullable()->collation('utf8mb4_bin');
$table->string('card_brand')->nullable();
$table->string('card_last_four', 4)->nullable();
$table->timestamp('trial_ends_at')->nullable();

Your schema now looks like this.

public function up()
{
     Schema::create('users', function (Blueprint $table) {
         $table->increments('id');
         $table->string('name');
         $table->string('email')->unique();
         $table->timestamp('email_verified_at')->nullable();
         $table->string('password');
         $table->string('stripe_id')->nullable()->collation('utf8mb4_bin');
         $table->string('card_brand')->nullable();
         $table->string('card_last_four', 4)->nullable();
         $table->timestamp('trial_ends_at')->nullable();
         $table->rememberToken();
         $table->timestamps();
     });
}

Create two more migrations files.

php artisan make:migration create_subscriptions_table
php artisan make:migration create_plans_table

Now, write the schema on both of the files.

First, add the following schema inside the create_subscriptions_table.php file.

public function up()
    {
        Schema::create('subscriptions', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id');
            $table->string('name');
            $table->string('stripe_id')->collation('utf8mb4_bin');
            $table->string('stripe_plan');
            $table->integer('quantity');
            $table->timestamp('trial_ends_at')->nullable();
            $table->timestamp('ends_at')->nullable();
            $table->timestamps();
        });
    }

Write the following schema inside the create_plans_table.php file.

public function up()
    {
        Schema::create('plans', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('slug')->unique();
            $table->string('stripe_plan');
            $table->float('cost');
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

Save the file and go to the terminal and migrate tables.

php artisan migrate

Add two plans manually on the plans table like this.

Laravel Stripe Payment Gateway Integration Tutorial With Example

Step 3: Get and Set Stripe API Keys

First, you need to create an account on Stripe. You can do it here. Then after login into your account, you will find a Test dashboard.

Click on the Developers link on the left sidebar. In the submenu, you can find the API keys item. Click on that item, and you will be redirected to this page.

Stripe API keys

You can find the Publishable Key, Stripe Key, and Secret Key here.

You need to add these keys inside the .env file in our project.

// .env

STRIPE_KEY=your key here
STRIPE_SECRET=your secret here

Finally, you should configure your Stripe key in your services.php configuration file. You can retrieve your Stripe API keys from the Stripe control panel.

// services.php

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

Also, you need to add the Billable Trait inside the User.php model.

// User.php

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

Step 4: Create Plans on Stripe Dashboard

You can create a plan through Laravel, but that will take some time, and our motto is to accept the payment through Stripe. So we will create the plans manually on Stripe Dashboard.

First, go to the Billing >> Products link; no products are available. So we need to create two products. Here products mean plans. So we will create two plans.

  1. Basic
  2. Professional

Create and assign the same values on the Plans table in our Laravel application. Thus, after creating two plans, your products look like this.

Laravel Subscription Based System Using Stripe Payment

Step 5: Display Plans on Frontend

First, define the routes for our application inside the routes >> web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
});

We have taken the auth middleware to protect the routes related to payment and home.

Now, create the Plan.php model and PlanController.php file.

php artisan make:model Plan
php artisan make:controller PlanController

Define the index method inside the PlanController.

// PlanController.php

use App\Plan;

public function index()
{
        $plans = Plan::all();
        return view('plans.index', compact('plans'));
}

Now, inside the resources >> views folder, create one folder called plans; inside that folder, create one file called index.blade.php. Write the following code.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">Plans</div>
                <div class="card-body">
                    <ul class="list-group">
                        @foreach($plans as $plan)
                        <li class="list-group-item clearfix">
                            <div class="pull-left">
                                <h5>{{ $plan->name }}</h5>
                                <h5>${{ number_format($plan->cost, 2) }} monthly</h5>
                                <h5>{{ $plan->description }}</h5>
                                <a href="" class="btn btn-outline-dark pull-right">Choose</a>
                            </div>
                        </li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Register one User on the Laravel application and go to this URL: http://stripesub.test/plans.

You will find the plans like this.

Laravel Saas Tutorial

Step 6: Show the plan

So, when the User chooses the plan, we must redirect the User to a particular plan page.

Define one more route inside the routes >> web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
    Route::get('/plan/{plan}', 'PlanController@show')->name('plans.show');
});

Now, by default, RouteModelBinding works with the ID of the model. But we will not pass the ID to show the particular plan. Instead, we will give the slug. So we need to define it inside the Plan.php model.

<?php

// Plan.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Plan extends Model
{
    protected $fillable = [
        'name',
        'slug',
        'stripe_plan',
        'cost',
        'description'
    ];

    public function getRouteKeyName()
    {
        return 'slug';
    }
}

Here, we have defined the function called getRouteKeyName. 

So based on this function, we can now fetch the record based on the slug and not based on the ID. That is why we have taken the slug field as a unique field in the database.

Now, define the show() function inside the PlanController.php file.

// PlanController.php

public function show(Plan $plan, Request $request)
{
     return view('plans.show', compact('plan'));
}

The next step is to create a view file called show.blade.php inside the resources >> views >> plans folder. Add the following code inside the show.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">{{ $plan->name }}</div>
                <div class="card-body">
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Now, we will display the Payment Form on this page.

Step 7: Display the Payment Form

For this example, I am using Card Element. You can find it on the official Stripe Documentation.

It securely collects sensitive card details using Elements, our pre-built UI components.

Stripe Payment Form Example

Now, we need to include the External JS files in our project. For that, we need to modify the resources >> views >> layouts >> app.blade.php file.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                            </li>
                            <li class="nav-item">
                                @if (Route::has('register'))
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                @endif
                            </li>
                        @else
                          <li class="nav-item">      
                            <a class="nav-link" href="{{ route('plans.index') }}">{{ __('Plans') }}</a>
                          </li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>
    @yield('scripts')
</body>
</html>

Here, we have defined one navigation link called plans and added one section for scripts. So, now we can add the Javascript per pagewise using the @yield directive.

The next step is to write the Card Element inside the show.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="">
                <p>You will be charged ${{ number_format($plan->cost, 2) }} for {{ $plan->name }} Plan</p>
            </div>
            <div class="card">
                <form action="#" method="post" id="payment-form">
                    @csrf                    
                    <div class="form-group">
                        <div class="card-header">
                            <label for="card-element">
                                Enter your credit card information
                            </label>
                        </div>
                        <div class="card-body">
                            <div id="card-element">
                            <!-- A Stripe Element will be inserted here. -->
                            </div>
                            <!-- Used to display form errors. -->
                            <div id="card-errors" role="alert"></div>
                            <input type="hidden" name="plan" value="{{ $plan->id }}" />
                        </div>
                    </div>
                    <div class="card-footer">
                        <button class="btn btn-dark" type="submit">Pay</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection
@section('scripts')
<script src="https://js.stripe.com/v3/"></script>
<script>
    // Create a Stripe client.
var stripe = Stripe('{{ env("STRIPE_KEY") }}');

// Create an instance of Elements.
var elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
  base: {
    color: '#32325d',
    lineHeight: '18px',
    fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
    fontSmoothing: 'antialiased',
    fontSize: '16px',
    '::placeholder': {
      color: '#aab7c4'
    }
  },
  invalid: {
    color: '#fa755a',
    iconColor: '#fa755a'
  }
};

// Create an instance of the card Element.
var card = elements.create('card', {style: style});

// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');

// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
  event.preventDefault();

  stripe.createToken(card).then(function(result) {
    if (result.error) {
      // Inform the user if there was an error.
      var errorElement = document.getElementById('card-errors');
      errorElement.textContent = result.error.message;
    } else {
      // Send the token to your server.
      stripeTokenHandler(result.token);
    }
  });
});

// Submit the form with the token ID.
function stripeTokenHandler(token) {
  // Insert the token ID into the form so it gets submitted to the server
  var form = document.getElementById('payment-form');
  var hiddenInput = document.createElement('input');
  hiddenInput.setAttribute('type', 'hidden');
  hiddenInput.setAttribute('name', 'stripeToken');
  hiddenInput.setAttribute('value', token.id);
  form.appendChild(hiddenInput);

  // Submit the form
  form.submit();
}
</script>
@endsection

We have used the Card element and Javascript to display the form here.

Now, add the link to this page inside the index.blade.php file.

// index.blade.php

<a href="{{ route('plans.show', $plan->slug) }}" class="btn btn-outline-dark pull-right">Choose</a>

So, it will be redirected to the page like this.

Stripe Payment Gateway Integration in Laravel

So, here we need to enter the following details. You can enter the details like the below inputs.

  1. Card Number: 4242 4242 4242 4242
  2. Expiration Date: 10/21 or whatever you like from the future of today’s date
  3. CVC: whatever you like
  4. Zipcode: whatever you like

These are dummy details, but these details are generally used in a sandbox account to check the application.

Nothing will happen because we must define the form action to store the database tables. So let us explain the post route.

Step 8: Save the subscriptions and accept payment

Define the final route inside the web.php file.

// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    Route::get('/plans', 'PlanController@index')->name('plans.index');
    Route::get('/plan/{plan}', 'PlanController@show')->name('plans.show');
    Route::post('/subscription', 'SubscriptionController@create')->name('subscription.create');
});

We have defined the POST route for subscriptions. Now, create SubscriptionController using the following command.

php artisan make:controller SubscriptionController

Add a POST route inside the show.blade.php file when posting the data to the server.

// show.blade.php

<form action="{{ route('subscription.create') }}" method="post" id="payment-form">

We need to define one function inside that controller called create().

<?php

// SubscriptionController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Plan;

class SubscriptionController extends Controller
{
    public function create(Request $request, Plan $plan)
    {
        $plan = Plan::findOrFail($request->get('plan'));
        
        $request->user()
            ->newSubscription('main', $plan->stripe_plan)
            ->create($request->stripeToken);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
    }
}

Here, we create the subscription for the registered User and charge him.

We have to fetch the plan according to the id. Then we need to pass that plan to the newSubscription() function and create a subscription.

We have used the Billable trait’s newSubscription() method and passed the first as the main and the second parameter new plan.

We are creating a new Subscription, and if the payment is made successfully, it will redirect to the HomePage with a  success message.

Write the following code inside the home.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            @if(session()->get('success'))
                <div class="alert alert-success">
                    {{ session()->get('success') }}
                </div>
            @endif
            <div class="card">
                <div class="card-header">Dashboard</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    Welcome to the Dashboard
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

If all configurations are right, you should go to any of the plans and try to subscribe to the plan. If you are redirecting to the homepage, then you are almost done.

You can see that one database entry inside the subscriptions table is there.

Laravel Saas Example

Your User’s table also has been updated as you can see, some field details are updated.

You can also check the Stripe Dashboard, as we have subscribed to the 50$ Professional Plan.

Stripe Payment Example

Step 9: Security Tweaks

We must remember that if the User is already subscribed to one plan, we need the User to prevent choosing that plan. So, we need to add one condition on the choose button.

So, add the condition inside the index.blade.php file.

@if(!auth()->user()->subscribedToPlan($plan->stripe_plan, 'main'))
    <a href="{{ route('plans.show', $plan->slug) }}" class="btn btn-outline-dark pull-right">Choose</a>
@endif

Also, we need to add the condition inside the PlanController.php file’s show() function.

// PlanController.php

public function show(Plan $plan, Request $request)
{
        if($request->user()->subscribedToPlan($plan->stripe_plan, 'main')) {
            return redirect()->route('home')->with('success', 'You have already subscribed the plan');
        }
        return view('plans.show', compact('plan'));
 }

Also, we need to do the same thing inside the SubscriptionController’s create() method.

// SubscriptionController.php

public function create(Request $request, Plan $plan)
{
        if($request->user()->subscribedToPlan($plan->stripe_plan, 'main')) {
            return redirect()->route('home')->with('success', 'You have already subscribed the plan');
        }
        $plan = Plan::findOrFail($request->get('plan'));
        
        $request->user()
            ->newSubscription('main', $plan->stripe_plan)
            ->create($request->stripeToken);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
}

Save the file, and now you are good to go. I put this Laravel Stripe Payment Gateway Integration Example’s code on Github.

That’s it.

GitHub Code

35 thoughts on “Laravel Stripe Payment Gateway Integration Example”

    • I was getting the same error, but realized the “newSubscription” is looking for two argument. 1st is the Plan/Subscriptions Name and the 2nd is the plan’s identifier. In this case the Plan/Subscriptions ID.
      https://laravel.com/docs/5.7/billing#subscriptions

      In the subscription controller I did the following. (Note: i’m use 4 different subscription plans).
      public function create(Request $request, Plan $plan)
      {
      $plan = Plan::findOrFail($request->get(‘plan’));
      $stripeToken = $request->stripeToken;
      $user = $request->user();
      $stripeplan = $request->stripe_plan;
      $planid = $request->plan;
      $user->newSubscription($stripeplan, $planid)->create($stripeToken);

      return redirect()->route(‘home’)->with(‘success’, ‘Your plan subscribed successfully’);
      ———————–
      Then in the HTML Form I added the following because I gave my Plan/Subscriptions ID’s custom numbers/identifiers like this; 001, 002, 003 & 004.

      So in the show.blade forms, I have the following hidden fields. (Note the 00 in the plan value.)
      id }}” />
      stripe_plan }}” />

      Hope that helps.

      Reply
  1. I was getting the same error, but realized the “newSubscription” is looking for two argument. 1st is the Plan/Subscriptions Name and the 2nd is the plan’s identifier. In this case the Plan/Subscriptions ID.
    https://laravel.com/docs/5.7/billing#subscriptions

    In the subscription controller I did the following. (Note: i’m use 4 different subscription plans).
    public function create(Request $request, Plan $plan)
    {
    $plan = Plan::findOrFail($request->get(‘plan’));
    $stripeToken = $request->stripeToken;
    $user = $request->user();
    $stripeplan = $request->stripe_plan;
    $planid = $request->plan;
    $user->newSubscription($stripeplan, $planid)->create($stripeToken);

    return redirect()->route(‘home’)->with(‘success’, ‘Your plan subscribed successfully’);
    ———————–
    Then in the HTML Form I added the following because I gave my Plan/Subscriptions ID’s custom numbers/identifiers like this; 001, 002, 003 & 004.

    So in the show.blade forms, I have the following hidden fields. (Note the 00 in the plan value.)
    id }}” />
    stripe_plan }}” />

    Hope that helps.

    Reply
  2. SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘stripe_id’ in ‘field list’ (SQL: update `users` set `updated_at` = 2019-02-21 12:49:21, `stripe_id` = cus_EZPoxOQaIzmrcy where `id` = 11)

    Reply
  3. Hi! First thanks for the nice tutorial! After a quick read, one little question. What prevents the user from changing the hidden input field, in the form, in order to subscribe to another plan, maybe paying less?

    Reply
  4. Want to store credit card information and retrieve when need again? is it possible? if yes how to do this

    Reply
  5. Anyone else having issues with the $stripeToken not being sent/set by the JS? Either the form element is not getting created on the submit of the entire form.adEventListener is not working as for me, even the preventDefault() isn’t working and the subscriptionController is creating a user. Error I’m getting is ‘The customer must have an active payment source attached.’, If I add in something like ‘tok_visa’ as a stripeToken into the form myself it works.
    Jay

    Reply
  6. I get this error when submitting the credit card info.

    Invalid API Key provided: {{***(“******_***”)}}

    The correct test API keys are in my .env file and in Stripe. Any idea where to start?

    Reply
    • most likely you wrote down in the field stripe_plan: Basic. When you create a product in a stripe, it creates a plan, replace it with the one you have in the database. should look like this “plan_Ex0ObKPjfDSFfsd”

      Reply
  7. A few weeks ago, I followed this tutorial, and everything worked perfectly. I just recently went through it again however, and now, I am getting the following error:

    “No such payment_method: tok_FdrmXJZb6LycSI”

    I see that Laravel, Cashier and Stripe have all recently been updated, and so I am guessing that this error is a result of one of those changes. Is there any chance you could update the article, or provide some guidance on what extra steps need to be taken to get the checkout process working again?

    Thanks so much!

    Reply
  8. Hey,

    Nice tutorial!

    Which MySQL tools you are using for database handle?

    And do you know about how to integrate git with MySQL for backup database like version control code?

    Thank you.

    Reply
  9. Hey,

    Nice tutorial!

    Which MySQL tools you are using for MySQL database handle?

    And do you know about how to integrate git with MySQL for backup database like version control code?

    Thank you.

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.