Create Stripe checkout form in Laravel

In this tutorial I will create a simple Stripe checkout form. Form will be validated using Parsley.js (front-end validation) and then submitted to the server to create a Stripe customer and after that the customer will be charged with the defined amount, depending on what product is selected. The customer data and purchase data will be stored in the database.

You can find entire code used in this tutorial on Github.

I will divide this tutorial into following steps:

Creating tables and models 

For this project we will need two tables: users and purchases. To create these tables we first need to create migrations for them. For the users table we will use create_users_table migration with slight modifications. Because our project does not have authentication we will modify it to look like this:


<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('first_name');
            $table->string('last_name');
            $table->string('email')->unique();
            $table->string('stripe_customer_id')->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('users');
    }
}

Next we need a purchases table so we will create a migration called create_purchases_table. We can do this using the console command:

php artisan make:migration create_purchases_table --create=”purchases”

This will create a migration called create_purchases_table with the table name “purchases” predefined. We will add some more fields to the table so it looks like on the picture below:


<?php

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

class CreatePurchasesTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('purchases', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->string('product');
            $table->integer('amount');
            $table->string('stripe_transaction_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('purchases');
    }
}

As you can see we are referencing an id on the users table so we can keep track of which customer bought the product. Also if we delete the user we will delete all of his purchases. Besides this we are keeping record of the product name, cost amount and Stripe transaction id.

Now lets create the tables using the console command php artisan migrate.

The tables should now exist and we can now create a model for our purchases table. In the console type php artisan make:model Purchase. Also, we will make the fields fillable (for both users and purchases tables) so we can create table entries using User::create() and Purchase::create() methods.


<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
    use Authenticatable, CanResetPassword;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['first_name', 'last_name', 'email', 'stripe_customer_id'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [];
}

and


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Purchase extends Model
{
    protected $fillable = ['user_id', 'product', 'amount', 'stripe_transaction_id'];
}

Now that we have our tables and models ready we can start defining the routes and building our form.

Defining routes and creating controllers 

For our project we will have only 3 routes: the home (or "root") route which will redirect to our "order" page, the GET order route and the POST order route. The code below will make things more clear...


<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('/', function () {
    return redirect()->route('order');
});

Route::get('order', ['as' => 'order', 'uses' => '[email protected]']);
Route::post('order', ['as' => 'order-post', 'uses' => '[email protected]']);

We named our routes so we don't have to think about their paths. This is not necessary for such a small project, but its a good practice. Our routes are managed by the PagesController so we have to create it now. We can do this with php artisan make:controller PagesController --plain (--plain because we want an empty PagesController class). We will add two methods to the class, one for handling the GET request and the other for handling the POST request. We will later return to the POST method logic, for now lets just handle the GET request so it shows the "order" view. Your PagesController should look like this:


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PagesController extends Controller
{
    public function getOrder()
    {
        return view('pages.order');
    }

    public function postOrder(Request $request)
    {
        //
    }
}

We did not create views yet so our application will not work correctly if we try to open it. We will do this in the next step...

Creating checkout form 

 

Our checkout form will look like the one shown on the following picture:

pic2

Checkout form

First of all lets start by creating our views. The main view will be app.blade.php and it will be located in resources/views. We will use Bootstrap for styling so we need to include Bootstrap and jQuery in our project. Instructions on how to add Bootstrap CDN can be found on the bootstrap page http://getbootstrap.com/getting-started/#download-cdn and instructions how to add jQuery can be found here https://jquery.com/download/#using-jquery-with-a-cdn. Your app.blade.php should now look like this:


<!DOCTYPE html>
<html>

<head>

    <title>Stripe Tutorial</title>

    <!-- Latest compiled and minified Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

    <!-- Including the jQuery libraries -->
    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>

    <!-- Latest compiled and minified Bootstrap JavaScript library -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

</head>

<body>
    <div class="container">

        @yield('content')

    </div>
</body>

</html>

Next, in your resources/views folder create a new folder called pages. In this folder we will keep all our pages (for this project we will have only one). Lets create a new view for our purchasing form, we will call the view order.blade.php and place it in our pages folder. The initial view should look like this and if we run our application now we should see "Hello!" printed in our browser.


@extends('app')

@section('content')
    Hello!
@endsection

Because we will be using Laravel Collective HTML, before we start creating our purchasing form we will have to install this package and add it to our Service Providers and Facades. Instructions on how to do this can be found here.

Now we can start building... Our order.blade.php view should look like this:


@extends('app')

@section('content')
<div class="row">
    <div class="col-md-8 col-md-offset-2">
        <h1 class="text-primary" style="text-align: center;">Create order</h1>
    </div>
</div>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    {!! Form::open(['url' => route('order-post')]) !!}

      <div class="form-group">
          {!! Form::label('firstName', 'First Name:') !!}
          {!! Form::text('first_name', null, ['class' => 'form-control']) !!}
      </div>

      <div class="form-group">
          {!! Form::label('lastName', 'Last Name:') !!}
          {!! Form::text('last_name', null, ['class' => 'form-control']) !!}
      </div>

      <div class="form-group">
          {!! Form::label('email', 'Email address:') !!}
          {!! Form::email('email', null, ['class' => 'form-control', 'placeholder' => '[email protected]']) !!}
      </div>

      <div class="form-group">
          {!! Form::label('product', 'Select product:') !!}
          {!! Form::select('product', ['book' => 'Book ($10)', 'game' => 'Game ($20)', 'movie' => 'Movie ($15)'], 'Book', ['class' => 'form-control']) !!}
      </div>

      <div class="form-group">
          {!! Form::label(null, 'Credit card number:') !!}
          {!! Form::text(null, null, ['class' => 'form-control']) !!}
      </div>

      <div class="form-group">
          {!! Form::label(null, 'Card Validation Code (3 or 4 digit number):') !!}
          {!! Form::text(null, null, ['class' => 'form-control']) !!}
      </div>

      <div class="row">
        <div class="col-md-4">
          <div class="form-group">
              {!! Form::label(null, 'Ex. Month') !!}
              {!! Form::selectMonth(null, null, ['class' => 'form-control'], '%m') !!}
          </div>
        </div>
        <div class="col-md-4">
          <div class="form-group">
              {!! Form::label(null, 'Ex. Year') !!}
              {!! Form::selectYear(null, date('Y'), date('Y') + 10, null, ['class' => 'form-control']) !!}
          </div>
        </div>
      </div>

        <div class="form-group">
            {!! Form::submit('Place order!', ['class' => 'btn btn-primary btn-order', 'id' => 'submitBtn', 'style' => 'margin-bottom: 10px;']) !!}
        </div>

    {!! Form::close() !!}

  </div>
</div>

@endsection

You can check now how the purchasing form looks like. I added 3 products to the select field, you can create some other ones if you like.

Front-end validation with Parsley.js 

Next step is to add Parsley.js (front-end) validation and format the error messages. For more details you can check http://parsleyjs.org/doc/index.html#installation.

We will add Parsley.js to the bottom of our app.blade.php file, just before the ending </body> tag:


<!-- PARSLEY -->
    <script>
        window.ParsleyConfig = {
            errorsWrapper: '<div></div>',
            errorTemplate: '<div class="alert alert-danger parsley" role="alert"></div>',
            errorClass: 'has-error',
            successClass: 'has-success'
        };
    </script>
    <script src="http://parsleyjs.org/dist/parsley.js"></script>
</body>

</html>

First we added the Parsley configuration script so that error messages are displayed in the template we specified and also we defined the classes which are to be assigned to the input fields in case of error or success. After that we included the Parsley.js library. Also, we will add some styling to the error message template. Just add this in the app.blade.php header under the Bootstrap CSS:


<!-- PARSLEY CSS config -->
    <style>
        .alert.parsley {
            margin-top: 5px;
            margin-bottom: 0px;
            padding: 10px 15px 10px 15px;
        }

        .check .alert {
            margin-top: 20px;
        }
    </style>

Next we will add Parsley validators to our HTML Form. Our order.blade.php should now look like this:


@extends('app')

@section('content')
<div class="row">
    <div class="col-md-8 col-md-offset-2">
        <h1 class="text-primary" style="text-align: center;">Create order</h1>
    </div>
</div>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    {!! Form::open(['url' => route('order-post'), 'data-parsley-validate']) !!}

      <div class="form-group" id="first-name-group">
          {!! Form::label('firstName', 'First Name:') !!}
          {!! Form::text('first_name', null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-required-message' => 'First name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-pattern'          => '/^[a-zA-Z]*$/',
              'data-parsley-minlength'        => '2',
              'data-parsley-maxlength'        => '32',
              'data-parsley-class-handler'    => '#first-name-group'
              ]) !!}
      </div>

      <div class="form-group" id="last-name-group">
          {!! Form::label('lastName', 'Last Name:') !!}
          {!! Form::text('last_name', null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-required-message' => 'Last name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-pattern'          => '/^[a-zA-Z]*$/',
              'data-parsley-minlength'        => '2',
              'data-parsley-maxlength'        => '32',
              'data-parsley-class-handler'    => '#last-name-group'
              ]) !!}
      </div>

      <div class="form-group" id="email-group">
          {!! Form::label('email', 'Email address:') !!}
          {!! Form::email('email', null, [
              'class' => 'form-control',
              'placeholder'                   => '[email protected]',
              'required'                      => 'required',
              'data-parsley-required-message' => 'Email name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-class-handler'    => '#email-group'
              ]) !!}
      </div>

      <div class="form-group" id="product-group">
          {!! Form::label('product', 'Select product:') !!}
          {!! Form::select('product', ['book' => 'Book ($10)', 'game' => 'Game ($20)', 'movie' => 'Movie ($15)'], 'Book', [
              'class'                       => 'form-control',
              'required'                    => 'required',
              'data-parsley-class-handler'  => '#product-group'
              ]) !!}
      </div>

      <div class="form-group" id="cc-group">
          {!! Form::label(null, 'Credit card number:') !!}
          {!! Form::text(null, null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-type'             => 'number',
              'maxlength'                     => '16',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-class-handler'    => '#cc-group'
              ]) !!}
      </div>

      <div class="form-group" id="ccv-group">
          {!! Form::label(null, 'Card Validation Code (3 or 4 digit number):') !!}
          {!! Form::text(null, null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-type'             => 'number',
              'data-parsley-trigger'          => 'change focusout',
              'maxlength'                     => '4',
              'data-parsley-class-handler'    => '#ccv-group'
              ]) !!}
      </div>

      <div class="row">
        <div class="col-md-4">
          <div class="form-group" id="exp-m-group">
              {!! Form::label(null, 'Ex. Month') !!}
              {!! Form::selectMonth(null, null, [
                  'class'                 => 'form-control',
                  'required'              => 'required'
              ], '%m') !!}
          </div>
        </div>
        <div class="col-md-4">
          <div class="form-group" id="exp-y-group">
              {!! Form::label(null, 'Ex. Year') !!}
              {!! Form::selectYear(null, date('Y'), date('Y') + 10, null, [
                  'class'             => 'form-control',
                  'required'          => 'required'
                  ]) !!}
          </div>
        </div>
      </div>

        <div class="form-group">
            {!! Form::submit('Place order!', ['class' => 'btn btn-primary btn-order', 'id' => 'submitBtn', 'style' => 'margin-bottom: 10px;']) !!}
        </div>

    {!! Form::close() !!}

  </div>
</div>

@endsection

We can now test our ordering form for front-end validation errors, it should all work correctly.

Stripe configuration 

Finally we arrived to the step which is about Stripe, that is what this tutorial is about :)

Stripe is developer friendly and their documentation is very clean and understandable, so checkout their site if you need more details. There is also an embedded form which can be added to your website, it makes using Stripe much easier. But that is not our goal in this tutorial, we are making a custom purchasing form.

You will have to register a Stripe account in order to get your API keys. There are 2 keys, one which is publishable and which you include in your client-side application (this is used for data checking and generating a Stripe token) and the other one which is a secret key (used for server to server communication). You can find these keys in Account Settings -> API keys. We will explain this after we configure and include the Stripe scripts.

So, first we have to include and configure Stripe. Lets add Stripe library and scripts to our app.blade.php file (we will do this right under our Parsley.js scripts):


<!DOCTYPE html>
<html>

<head>

    <title>Stripe Tutorial</title>

    <!-- Latest compiled and minified Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

    <!-- STRIPE CSS config -->
    <style>
        .alert.parsley {
            margin-top: 5px;
            margin-bottom: 0px;
            padding: 10px 15px 10px 15px;
        }

        .check .alert {
            margin-top: 20px;
        }
    </style>

    <!-- Including the jQuery libraries -->
    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>

    <!-- Latest compiled and minified Bootstrap JavaScript library -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

</head>

<body>
    <div class="container">

        @yield('content')

    </div>

    <!-- PARSLEY -->
    <script>
        window.ParsleyConfig = {
            errorsWrapper: '<div></div>',
            errorTemplate: '<div class="alert alert-danger parsley" role="alert"></div>',
            errorClass: 'has-error',
            successClass: 'has-success'
        };
    </script>
    <script src="http://parsleyjs.org/dist/parsley.js"></script>

    <!-- Inlude Stripe.js -->
    <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
    <script>
        // This identifies your website in the createToken call below
        Stripe.setPublishableKey('{!! env('STRIPE_PK') !!}');

        jQuery(function($) {
            $('#payment-form').submit(function(event) {
                var $form = $(this);

                // Before passing data to Stripe, trigger Parsley Client side validation
                $form.parsley().subscribe('parsley:form:validate', function(formInstance) {
                    formInstance.submitEvent.preventDefault();
                    return false;
                });

                // Disable the submit button to prevent repeated clicks
                $form.find('#submitBtn').prop('disabled', true);

                Stripe.card.createToken($form, stripeResponseHandler);

                // Prevent the form from submitting with the default action
                return false;
            });
        });

        function stripeResponseHandler(status, response) {
            var $form = $('#payment-form');

            if (response.error) {
                // Show the errors on the form
                $form.find('.payment-errors').text(response.error.message);
                $form.find('.payment-errors').addClass('alert alert-danger');
                $form.find('#submitBtn').prop('disabled', false);
                $('#submitBtn').button('reset');
            } else {
                // response contains id and card, which contains additional card details
                var token = response.id;
                // Insert the token into the form so it gets submitted to the server
                $form.append($('<input type="hidden" name="stripeToken" />').val(token));
                // and submit
                $form.get(0).submit();
            }
        };
    </script>
</body>

</html>

Notice: Stripe keys are configured in .env file as STRIPE_PK and STRIPE_SK.

Now we have to make some changes to our order.blade.php file and add fields which the Stripe scripts will check for. Also we will add a Stripe response error message to appear at the bottom of our page. After this I will provide explanation of what exactly is happening and what we did so far. This is how our order.blade.php should look like:


@extends('app')

@section('content')
<div class="row">
    <div class="col-md-8 col-md-offset-2">
        <h1 class="text-primary" style="text-align: center;">Create order</h1>
    </div>
</div>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    {!! Form::open(['url' => route('order-post'), 'data-parsley-validate', 'id' => 'payment-form']) !!}

      <div class="form-group" id="first-name-group">
          {!! Form::label('firstName', 'First Name:') !!}
          {!! Form::text('first_name', null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-required-message' => 'First name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-pattern'          => '/^[a-zA-Z]*$/',
              'data-parsley-minlength'        => '2',
              'data-parsley-maxlength'        => '32',
              'data-parsley-class-handler'    => '#first-name-group'
              ]) !!}
      </div>

      <div class="form-group" id="last-name-group">
          {!! Form::label('lastName', 'Last Name:') !!}
          {!! Form::text('last_name', null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-parsley-required-message' => 'Last name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-pattern'          => '/^[a-zA-Z]*$/',
              'data-parsley-minlength'        => '2',
              'data-parsley-maxlength'        => '32',
              'data-parsley-class-handler'    => '#last-name-group'
              ]) !!}
      </div>

      <div class="form-group" id="email-group">
          {!! Form::label('email', 'Email address:') !!}
          {!! Form::email('email', null, [
              'class' => 'form-control',
              'placeholder'                   => '[email protected]',
              'required'                      => 'required',
              'data-parsley-required-message' => 'Email name is required',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-class-handler'    => '#email-group'
              ]) !!}
      </div>

      <div class="form-group" id="product-group">
          {!! Form::label('product', 'Select product:') !!}
          {!! Form::select('product', ['book' => 'Book ($10)', 'game' => 'Game ($20)', 'movie' => 'Movie ($15)'], 'Book', [
              'class'                       => 'form-control',
              'required'                    => 'required',
              'data-parsley-class-handler'  => '#product-group'
              ]) !!}
      </div>

      <div class="form-group" id="cc-group">
          {!! Form::label(null, 'Credit card number:') !!}
          {!! Form::text(null, null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-stripe'                   => 'number',
              'data-parsley-type'             => 'number',
              'maxlength'                     => '16',
              'data-parsley-trigger'          => 'change focusout',
              'data-parsley-class-handler'    => '#cc-group'
              ]) !!}
      </div>

      <div class="form-group" id="ccv-group">
          {!! Form::label(null, 'Card Validation Code (3 or 4 digit number):') !!}
          {!! Form::text(null, null, [
              'class'                         => 'form-control',
              'required'                      => 'required',
              'data-stripe'                   => 'cvc',
              'data-parsley-type'             => 'number',
              'data-parsley-trigger'          => 'change focusout',
              'maxlength'                     => '4',
              'data-parsley-class-handler'    => '#ccv-group'
              ]) !!}
      </div>

      <div class="row">
        <div class="col-md-4">
          <div class="form-group" id="exp-m-group">
              {!! Form::label(null, 'Ex. Month') !!}
              {!! Form::selectMonth(null, null, [
                  'class'                 => 'form-control',
                  'required'              => 'required',
                  'data-stripe'           => 'exp-month'
              ], '%m') !!}
          </div>
        </div>
        <div class="col-md-4">
          <div class="form-group" id="exp-y-group">
              {!! Form::label(null, 'Ex. Year') !!}
              {!! Form::selectYear(null, date('Y'), date('Y') + 10, null, [
                  'class'             => 'form-control',
                  'required'          => 'required',
                  'data-stripe'       => 'exp-year'
                  ]) !!}
          </div>
        </div>
      </div>

        <div class="form-group">
            {!! Form::submit('Place order!', ['class' => 'btn btn-primary btn-order', 'id' => 'submitBtn', 'style' => 'margin-bottom: 10px;']) !!}
        </div>

        <div class="row">
          <div class="col-md-12">
              <span class="payment-errors" style="color: red;margin-top:10px;"></span>
          </div>
        </div>

    {!! Form::close() !!}

  </div>
</div>

@endsection

So, the submit button will trigger the Stripe script which will disable the default action of the submit button and make the button unclickable until a response from Stripe is received. The response is handled by the function stripeResponseHandler() and if verification is successful we will receive a stripeToken, which will be added to the form. If the verification failed we will receive an error response which will be printed to the user.

After the correct response has been received the POST request will be sent to our server. With this step, we have finished most of our front-end work. Now it is time to handle the back-end programming.

Back-end validation 

Before we can create our Stripe customer and charge him with the selected amount we will quickly validate the request which we got. Lets check our [email protected] method:


public function postOrder(Request $request)
    {
        /*
         * Validation of the $request
         */
        $validator = \Validator::make(\Input::all(), [
            'first_name' => 'required|string|min:2|max:32',
            'last_name' => 'required|string|min:2|max:32',
            'email' => 'required|email',
            'product' => 'required|string',
        ]);

        if ($validator->fails()) {
            return redirect()->back()
                ->withErrors($validator);
        }
    }

If the request fails to validate it will return to our order page with validation error messages. Lets show these messages right under our form:


{!! Form::close() !!}

  </div>

  {{-- Show $request errors after back-end validation --}}
  <div class="col-md-6 col-md-offset-3">
      @if($errors->has())
          <div class="alert alert-danger fade in">
              <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
              <h4>The following errors occurred</h4>
              <ul>
                  @foreach($errors->all() as $error)
                      <li>{{ $error }}</li>
                  @endforeach
              </ul>
          </div>
      @endif
  </div>

</div>

@endsection

Creating Stripe customer and charging the client. Storing data in the database 

To make our life easier we will use a Stripe library for PHP. Just add "stripe/stripe-php": "3.*" to your composer.json file and type composer update in the console. If you have trouble doing this check the Stripe API libraries page https://stripe.com/docs/libraries#php-library.

Now we have everything set up for the creation of Stripe Customer and charging the customer with the amount which corresponds to the selected product. We will add the following code to the [email protected] method:


public function postOrder(Request $request)
    {
        $validator = \Validator::make(\Input::all(), [
            'first_name' => 'required|string|min:2|max:32',
            'last_name' => 'required|string|min:2|max:32',
            'email' => 'required|email',
            'product' => 'required|string',
        ]);

        if ($validator->fails()) {
            return redirect()->back()
                ->withErrors($validator)
                ->withInput();
        }

        // Checking is product valid
        $product = $request->input('product');
        switch ($product) {
            case 'book':
                $amount = 1000;
                break;
            case 'game':
                $amount = 2000;
                break;
            case 'movie':
                $amount = 1500;
                break;
            default:
                return redirect()->route('order')
                    ->withErrors('Product not valid!')
                    ->withInput();
        }

        $token = $request->input('stripeToken');
        $first_name = $request->input('first_name');
        $last_name = $request->input('last_name');
        $email = $request->input('email');
        $emailCheck = User::where('email', $email)->value('email');

        \Stripe\Stripe::setApiKey(env('STRIPE_SK'));

        // If the email doesn't exist in the database create new customer and user record
        if (!isset($emailCheck)) {
            // Create a new Stripe customer
            try {
                $customer = \Stripe\Customer::create([
                'source' => $token,
                'email' => $email,
                'metadata' => [
                    "First Name" => $first_name,
                    "Last Name" => $last_name
                ]
                ]);
            } catch (\Stripe\Error\Card $e) {
                return redirect()->route('order')
                    ->withErrors($e->getMessage())
                    ->withInput();
            }

            $customerID = $customer->id;

            // Create a new user in the database with Stripe
            $user = User::create([
            'first_name' => $first_name,
            'last_name' => $last_name,
            'email' => $email,
            'stripe_customer_id' => $customerID,
            ]);
        } else {
            $customerID = User::where('email', $email)->value('stripe_customer_id');
            $user = User::where('email', $email)->first();
        }

        // Charging the Customer with the selected amount
        try {
            $charge = \Stripe\Charge::create([
                'amount' => $amount,
                'currency' => 'usd',
                'customer' => $customerID,
                'metadata' => [
                    'product_name' => $product
                ]
                ]);
        } catch (\Stripe\Error\Card $e) {
            return redirect()->route('order')
                ->withErrors($e->getMessage())
                ->withInput();
        }

        // Create purchase record in the database
        Purchase::create([
            'user_id' => $user->id,
            'product' => $product,
            'amount' => $amount,
            'stripe_transaction_id' => $charge->id,
        ]);

        return redirect()->route('order')
            ->with('successful', 'Your purchase was successful!');
    }

Because this is a lot of code and we did add several things I will explain it step by step. So after the back-end validation we are handling the request inputs. First of all, we are checking the product which was selected and add the correct amount to it (ex: 10$ = 1000 , because the amount we are passing to Stripe is in cents). After that we are handling the other inputs and putting them into variables. Next is the Stripe API key setting, here we provide the secret API key we got.

Next we are checking if the user with that email already exists in our database. If it is a new user we will create Stripe customer and at the same time create a new user in our database (with all the data stored, including the Stripe customer ID which we can then reuse without creating the customer again). If an error occurred during the customer creation process  we will redirect the user back to the ordering page and display the error.

If the user exists in our database we will not create a new user nor Stripe customer. We will just pull the Stripe customer ID and user ID from our database, because we will need them in the next step.

Next we are charging the user with the cost amount of the product. If an error occurs during this process we are catching it and redirecting the user to the ordering page with a proper error message.

Finally, if everything goes well a purchase record is created in the database and we are redirected back to the ordering page with a "purchase successful" message.

While your Stripe account is in Test mode use credit card numbers from Stripe Testing section.

Final words 

We saw how easy it is to implement Stripe payment service in our web application. One thing to keep in mind is that we should always protect our buttons from multi-clicking, at the front-end and at the back-end. We didn't cover that part here but it shouldn't be a problem for anyone to add this protection.

You can find entire code used in this tutorial on Github.
Online computer science courses to jumpstart your future.
WP Engine Managed WordPress Hosting

Trending

Newsletter

Subscribe to our newsletter for good news, sent out every month.

Tags