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.
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.
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.
- Basic
- 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.
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.
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.
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.
So, here we need to enter the following details. You can enter the details like the below inputs.
- Card Number: 4242 4242 4242 4242
- Expiration Date: 10/21 or whatever you like from the future of today’s date
- CVC: whatever you like
- 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.
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.
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.

Krunal Lathiya is a seasoned Computer Science expert with over eight years in the tech industry. He boasts deep knowledge in Data Science and Machine Learning. Versed in Python, JavaScript, PHP, R, and Golang. Skilled in frameworks like Angular and React and platforms such as Node.js. His expertise spans both front-end and back-end development. His proficiency in the Python language stands as a testament to his versatility and commitment to the craft.
Hello…
can you help me with this error
“No such plan: Basic; one exists with a name of Basic, but its ID is plan_EIxwRA8.
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.
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.
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)
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?
Want to store credit card information and retrieve when need again? is it possible? if yes how to do this
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
I think I had the same problem, I fixed it by putting the whole js code inside:
window.onload = function () {
// Your code here
}
you should add yield(‘scripts’) in your parent blade below yield(‘section’)
thanks @Boultox Says it did work after putting load function
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?
Error
“No such plan: Basic; one exists with a name of Basic, but its ID is plan_EIxwRA8.
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”
I get the following error –
Stripe \ Error \ InvalidRequest
This customer has no attached payment source
how u resolved it,. pls tell me
Hi,
I have an error message: “No such payment_method: tok_*******************”. I don’t understand how and why…
were you able to resolve this ?
I am also getting the same error, please help !
You get the “No such payment_method: tok_” error message because you are using Laravel Cashier version 10.
To fix the problem downgrade to version 9 using
composer require laravel/cashier:9.3.5
It works, Thanks for help
You are my God. thank you
Seriously, use “tok_VISA” for testing purposes.
hello,there
I have same problem. did you solved this issue.
no such payment method..
if you have solved this problem , I’d like you to notice to me asap.
thanks
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!
were you able to resolve this ?
I am also getting the same error, please help !
I too, am getting this error.
I just don’t understand the need to constantly be changing things that work fine.
Use “tok_VISA” for testing purposes.
I’m also getting this error. I can’t downgrade to Cashier 9.3.5 because it breaks some dependencies. I’m not sure what the post above means by “Use “tok_VISA” for testing purposes.” . Can you help at all with this please
Does this work with Laravel 6 and Cashier 10.5?
sir can you help us for integrating GPay and Apple pay in current scenarios
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.
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.
give me this error `No such PaymentMethod: tok_1H8fUOLqhYHXv1ZliqUr2X1B`
awesome ! Thanks for sharing. I appreciate.
title should be cashier stripe integration on laravel
Call to undefined method App\Models\User::subscribedToPlan()