Laravel Stripe payment gateway has built a reputation of being facile-to-use for both developers and store owners. Because of great compatibility with Laravel, Stripe has become the go-to payment gateway for Laravel eCommerce stores.
The ordinant dictation for Saas predicated platforms is incrementing day by day and nowadays building a subscription-predicated system is macrocosmic. So to make things facile on the backend like Laravel, we do require some packages that can avail us build scalable, securable, and reliable web applications that deal with payment systems.
Laravel Stripe Payment Example
With a host of features and simple Stripe payment gateway integration in Laravel, many development agencies have come to opt for Stripe as the default payment gateway option for all eCommerce stores they develop for their clients.
Laravel Cashier makes it very simple to integrate Payment Gateway like Stripe. So let us see how we can integrate stripe in Laravel on Subscription based system.
We will use the Cashier package to integrate Stripe Payment Gateway in Laravel.
Another important reason for the popularity of Stripe is that it accepts all globally accepted credit and debit cards, thus facilitating payment collections from all over the world.
In short, Stripe provides a complete solution for online payment processing requirements for Laravel powered eCommerce stores.
Step 1: Install and configure Laravel 7
Type the following command.
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 migrations
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 table to hold all of our customer’s subscriptions.
So, first, 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();
});
}
Now, 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
Now, 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 to your account, you will find a Test dashboard.
Now, 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 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 and right now, there are no products available. So we need to create two products. Here products mean plans. So we will create two plans.
- Basic
- Professional
Create and assign the values the same as we have assigned the values on the Plans table in our Laravel application. 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
// 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 and inside that folder, create one file called index.blade.php file. 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
Now, register one user on 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 need to 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 pass 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, now we can 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 official Stripe Documentation. It is securely collect 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 also add 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
Here, we have used the Card element and Javascript to display the form.
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 generally used in a sandbox account to check the application.
Right now nothing will happen because we need to define the form action to store the data inside the database tables. So let us define 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
Now, add a POST route inside the show.blade.php file when we post the data to the server.
// show.blade.php
<form action="{{ route('subscription.create') }}" method="post" id="payment-form">
Inside that controller, we need to define one function 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 are creating the subscription for registered User and charge him.
First, 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.
So, here we have used the Billable trait’s subscribedToPlan() method and pass the first parameter plan and the second parameter main.
We are creating a new Subscription, if the payment made successfully then, it would 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
So, if all of the configurations are right, then 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’s details are updated.
You can check the Stripe Dashboard as well. As we have subscribed to the 50$ Professional Plan.
Step 9: Security Tweaks
Now, we need to keep in mind one thing that if the user is already subscribed to one plan, then we need the user to prevent to choose 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. Finally, the Laravel 7 Stripe Payment Example article is over. There are still so many things that we can do with the project. But for basic understanding, this is enough. Thanks.