Tuesday, 4 December, 2018 UTC


Summary

In this tutorial, we will see Laravel Cashier Braintree Payment Gateway Tutorial With Example We will build a Subscription based Platform in which the user can choose a plan, and according to that, he will be charged. We use Braintree for this demo, and we will discuss the stripe in the next tutorial. So, we will build a simple payment gateway in which a user can charge according to their selected plan. It is a straightforward system to work with, and for that, we need to create a developer account at Braintree. So, if you have not created yet, then please go to this link.
Laravel Cashier Braintree Payment Gateway
Integrate payment gateway in any web application used to be a very tough task. But nowadays, there are lots of SDKs available to work with that is why it is effortless to integrate any payment gateway to any web application. Now, you can find the Official Laravel Documentation to integrate Braintree here.
Braintree Caveats
For many operations, the Stripe and Braintree implementations of the Cashier function is the same. Both services provide the subscription billing with the credit cards, but Braintree also supports the payments via PayPal. We will not use Paypal for this example. However, Braintree also lacks some features that are supported by the Stripe. You should keep the following things in mind when deciding to use Stripe or Braintree:
  • Braintree supports PayPal while Stripe does not.
  • Braintree does not support the increment and decrement methods on subscriptions. This is a Braintree limitation, not a Cashier limitation.
  • Braintree does not support percentage based discounts. This is a Braintree limitation, not a Cashier limitation.
Let us start this small project by installing the Laravel Framework. I am using Laravel 5.7 for this example.
Step 1: Install and configure Laravel
Install Laravel using the following command.
laravel new subscription
Now, go inside the project folder and configure the database inside the .env file.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=subscription
DB_USERNAME=root
DB_PASSWORD=root
Also, install the npm dependencies using the following command.
npm install
Compile the CSS and JS files.
npm run dev
Also, create the standard authentication using the following command.
php artisan make:auth
Step 2: Install and configure Braintree package
Next step is to install the Laravel Cashier package. So let us install that.
composer require "laravel/cashier-braintree":"~2.0"
Next step is to register the Laravel\Cashier\CashierServiceProvider  service provider in your config/app.php configuration file.
...
Laravel\Cashier\CashierServiceProvider::class,
...
Now, we need to create and modify the database migrations.
First, edit the create_users_table.php file and add the following fields inside the schema.
Schema::table('users', function ($table) {
    $table->string('braintree_id')->nullable();
    $table->string('paypal_email')->nullable();
    $table->string('card_brand')->nullable();
    $table->string('card_last_four')->nullable();
    $table->timestamp('trial_ends_at')->nullable();
});
So, our final create_users_table.php file looks like below.
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    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('braintree_id')->nullable();
            $table->string('paypal_email')->nullable();
            $table->string('card_brand')->nullable();
            $table->string('card_last_four')->nullable();
            $table->timestamp('trial_ends_at')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}
Also, we need to create two more migrations file. To create the files, hit the following commands.
php artisan make:migration create_plans_table
php artisan make:migration create_subscriptions_table
Now, write the following schemas inside it.
<?php

// create_subscriptions_table.php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubsriptionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('subsriptions', function (Blueprint $table) {
          $table->increments('id');
          $table->integer('user_id');
          $table->string('name');
          $table->string('braintree_id');
          $table->string('braintree_plan');
          $table->integer('quantity');
          $table->timestamp('trial_ends_at')->nullable();
          $table->timestamp('ends_at')->nullable();
          $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('subsriptions');
    }
}
And the following is plans table schema.
<?php

// create_plans_table.php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePlansTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('plans', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('slug')->unique();
            $table->string('braintree_plan');
            $table->float('cost');
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('plans');
    }
}
Now go to the terminal and create the tables using the following command.
php artisan migrate
It will create the tables.
 
Now, add the Billable Trait inside the User.php model.
// User.php

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}
Step 3: Grab and add API keys for Braintree
Now, if you have not created a developer account at Braintree, then please create one.
After that, you will be redirected to the Dashboard and then go to the Account >> My User and click the View Authorizations link under the API Keys, Tokenization Keys, Encryption Keys section.
 
After clicking the View Authorizations link, you will be redirected to your Sandbox API Keys page.
 
Now, you need to click the View links inside the table, and now you can see your following keys and data.
  1. Your Public Key
  2. Your Private Key
  3. Your Environment
  4. Your Merchant Id
You need to add these keys inside your .env file.
BRAINTREE_ENV=sandbox
BRAINTREE_MERCHANT_ID=your merchant id
BRAINTREE_PUBLIC_KEY=your public key
BRAINTREE_PRIVATE_KEY=your private key
Next step is that you should configure the following options inside your services.php file.
// services.php

'braintree' => [
    'model'  => App\User::class,
    'environment' => env('BRAINTREE_ENV'),
    'merchant_id' => env('BRAINTREE_MERCHANT_ID'),
    'public_key' => env('BRAINTREE_PUBLIC_KEY'),
    'private_key' => env('BRAINTREE_PRIVATE_KEY'),
],
Then you should add the following Braintree SDK calls to your AppServiceProvider’s boot method.
// AppServiceProvider.php

use Braintree_Configuration;

public function boot()
{
      Braintree_Configuration::environment(env('BRAINTREE_ENV'));
      Braintree_Configuration::merchantId(env('BRAINTREE_MERCHANT_ID'));
      Braintree_Configuration::publicKey(env('BRAINTREE_PUBLIC_KEY'));
      Braintree_Configuration::privateKey(env('BRAINTREE_PRIVATE_KEY'));
}
You can also set the currency. Dollar($) is by default.
You can change the default currency by calling the Cashier::useCurrency method from within the boot method of one of your service providers. In our case we have used the AppServiceProvider. The useCurrency method accepts two string parameters: the currency and the currency’s symbol.
// AppServiceProvider.php

use Laravel\Cashier\Cashier;

Cashier::useCurrency('eur', '€');
Step 4: Create plans on Braintree dashboard
Now, for this example, we will create only two plans. You can create as per your requirement. You can find the plans inside your dashboard on the left sidebar.
I have created two plans.
  1. Basic
  2. Professional
 
Also, you need to add the plans manually inside the plans table inside MySQL.
 
Make sure both plans have the same names as on MySQL and Braintree dashboard.
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', '[email protected]')->name('home');
    Route::get('/plans', '[email protected]')->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 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
Save the file and navigate to the URL: http://subscription.test/plans. If you have not registered yet, then please register one user in our application.
You will see something like this.
 
So, we have successfully displayed the plans from the database. Now, the next step is when a user chooses the plan, you will redirect to a page from where a user can be charged for that plan.
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', '[email protected]')->name('home');
    Route::get('/plans', '[email protected]')->name('plans.index');
    Route::get('/plan/{plan}', '[email protected]')->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. Also, define the fillable fields as well.
<?php

// Plan.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Plan extends Model
{
	protected $fillable = [
		'name',
		'slug',
		'braintree_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'));
}
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
So, here, we will display the payment form.
Step 7: Display the Payment Form
For displaying the Payment Form, we will use the Drop-in UI. You can find the complete documentation here. We are using Version 2 of the Drop-in UI. There is version 3 but let us stick with version 2 for this example. Now, we are implementing the Client Side configuration. Configure the container and from where the Drop-in UI will add the payment method nonce. Make sure to replace CLIENT_AUTHORIZATION with your generated client token.
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 @yield directive.
Also, we need to fetch the Client Token for authorization. So we will fetch that token from Backend using AJAX request.
So, define one route inside the web.php file.
// web.php

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', '[email protected]')->name('home');
    Route::get('/plans', '[email protected]')->name('plans.index');
    Route::get('/plan/{plan}', '[email protected]')->name('plans.show');
    Route::get('/braintree/token', '[email protected]')->name('token');
});
Create a new Controller called BraintreeTokenController using the following command.
php artisan make:controller BraintreeTokenController
Write the following code inside the BraintreeTokenController.php file.
<?php

// BraintreeTokenController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Braintree_ClientToken;

class BraintreeTokenController extends Controller
{
    public function index()
    {
        return response()->json([
            'data' => [
                'token' => Braintree_ClientToken::generate()
            ]
        ]);
    }
}
The index() function returns the ClientToken to the Clientside JS file, and now we can authorize our Laravel application with the Braintree developer account.
So, write the final 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">
                <form method="post" action="{{ route('subscription.create') }}">
                    @csrf
                    <div id="dropin-container"></div>
                    <hr />
                    <input type="hidden" name="plan" value="{{ $plan->id }}" />
                    <button type="submit" class="btn btn-outline-dark d-none" id="payment-button">Pay</button>
                </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

@section('scripts')
<script src="https://js.braintreegateway.com/js/braintree-2.32.1.min.js"></script>
<script>
    jQuery.ajax({
        url: "{{ route('token') }}",
    })
    .done(function(res) {
        braintree.setup(res.data.token, 'dropin', {
            container: 'dropin-container',
            onReady: function() {
                jQuery('#payment-button').removeClass('d-none')
            }
        });
    });
</script>
@endsection
So, when the document is loaded, we will send an Ajax request to the Laravel server and get the Client Auth token. From that token, included js will generate the Payment Form. We defined the form like this.
<form method="post" action="">
        @csrf
        <div id="dropin-container"></div>
        <hr />
        <input type="hidden" name="plan" value="{{ $plan->id }}" />
        <button type="submit" class="btn btn-outline-dark d-none" id="payment-button">Pay</button>
</form>
Also, write the Javascript like this.
@section('scripts')
<script src="https://js.braintreegateway.com/js/braintree-2.32.1.min.js"></script>
<script>
    jQuery.ajax({
        url: "{{ route('token') }}",
    })
    .done(function(res) {
        braintree.setup(res.data.token, 'dropin', {
            container: 'dropin-container',
            onReady: function() {
                jQuery('#payment-button').removeClass('d-none')
            }
        });
    });
</script>
@endsection
So, when the request succeeds, it will return the token, and we use that token to create a Payment Form. The form looks like this.
 
So, here we need to enter the two details. You can enter the details like below inputs.
  1. Card Number: 4242 4242 4242 4242
  2. Expiration Date: 10/21 or whatever you like
These are dummy details, but these details generally used in 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 plan details
Define the final route inside the web.php file.
<?php

// web.php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::group(['middleware' => 'auth'], function() {
    Route::get('/home', '[email protected]')->name('home');
    Route::get('/plans', '[email protected]')->name('plans.index');
    Route::get('/plan/{plan}', '[email protected]')->name('plans.show');
    Route::get('/braintree/token', '[email protected]')->name('token');
    Route::post('/subscription', '[email protected]')->name('subscription.create');
});
We have defined the post route for the subscription details. Now, create SubscriptionController using the following command.
php artisan make:controller SubscriptionController
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->braintree_plan)
            ->create($request->payment_method_nonce);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
    }
}
First, we have to fetch the plan according to the id. Then we need to pass that plan to the subscribedToPlan() function.
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, and 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

                    You are logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
So, if your 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.
 
That means, we have successfully subscribed the Professional plan. Also, there is updation on the user’s table. We have already added some more fields.
You can see inside your Dashboard that Sales and Transaction volume is there. So, our Laravel Cashier Braintree Payment Gateway Tutorial With Example is almost complete.
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->braintree_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->braintree_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->braintree_plan, 'main')) {
            return redirect()->route('home');
        }

        $plan = Plan::findOrFail($request->get('plan'));
        
        $request->user()
            ->newSubscription('main', $plan->braintree_plan)
            ->create($request->payment_method_nonce);
        
        return redirect()->route('home')->with('success', 'Your plan subscribed successfully');
}
Save the file, and now you are good to go. I am putting this Laravel Cashier Braintree Payment Gateway Tutorial’s whole code on Github.
Finally, Laravel Cashier Braintree Payment Gateway Tutorial With 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.
Github Code
 
The post Laravel Cashier Braintree Payment Gateway Tutorial With Example appeared first on AppDividend.