Stripe Payment Integration With Laravel


03rd September 2020 12 mins read
Share On        


Hello Fellas, In this article let's discuss how to integrate Stripe with Laravel.


We will cover the following topics


  1. Laravel Installation
  2. Stripe Testing Keys
  3. Stripe Package Installation
  4. Adding Keys In .env File
  5. Defining Routes
  6. Form Fields With Stripe Javascript Validations
  7. Backend Stripe Checkout
  8. Thank You Page


NOTE: Code available in GitHub @ Stripe Integration With Laravel

Step 1 - Laravel Installation


If you already have the Laravel application skip to Step 2 else let's install the Laravel application with composer using the following command


composer create-project --prefer-dist laravel/laravel stripe


The above command creates a new Laravel project with a name blog.


Step 2 - Stripe Testing Keys


Stripe testing keys can be found on the dashboard once you log in and has been redirected to the stripe dashboard, as shown in the following image


Stripe Testing Keys From Dashboard


The other way you can find the testing or production keys is by navigating to Developers -> API Keys, as shown in the following image


Stripe Testing Or Live Keys


Step 3 - Stripe Package Installation


To work with the stripe we need to install the Stripe package, we will do it using composer as following


composer require stripe/stripe-php

Step 4 - Adding Keys In .env File


Add the keys you had got from Step 2 in .env file as following


STRIPE_PUBLISH_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...

Step 5 - Defining Routes


You might have customer orders listing page and from there you might have checkout and make payment, that time just uses the POST URL.


For the sake of simplicity I am using a form with hardcoded test amount in controller, which I will show in coming steps.


Route::get('/payments', 'PaymentsController@create');
Route::post('/payments', 'PaymentsController@store');
Route::get('/thankyou', 'PaymentsController@thankyou');

Step 6 - Form Fields With Stripe Javascript Validations


First let's create the payment page. The following is how it looks


Stripe Payment Demo Page


PaymentsController@create

public function create()
{
    return view('payments.create');
}


payment.create blade

<div class="row">
    <div class="col-md-4 col-md-offset-4">
        <h1 class="text-center">Stripe Payment Demo</h1>
        <hr>
        @if (session()->has('error'))
            <div class="text-danger font-italic">{{ session()->get('error') }}</div>
        @endif
        <form action="{{ url('payments') }}" method="post" id="payment-form">
            @csrf
            <div class="row form-group">
                <div class="col-md-12">
                    <label for="name">Name</label>
                    @error('name')
                    <div class="text-danger font-italic">{{ $message }}</div>
                    @enderror
                    <input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}">
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12">
                    <label for="email">Email</label>
                    @error('email')
                    <div class="text-danger font-italic">{{ $message }}</div>
                    @enderror
                    <input type="text" name="email" id="email" class="form-control" value="{{ old('email') }}">
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12">
                    <label>Billing Amount in US Dollars</label> <br>
                    <h2 class="text-muted">$1</h2>
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12">
                    <!-- Display errors returned by createToken -->
                    <label>Card Number</label>
                    <div id="paymentResponse" class="text-danger font-italic"></div>
                    <div id="card_number" class="field form-control"></div>
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-3">
                    <label>Expiry Date</label>
                    <div id="card_expiry" class="field form-control"></div>
                </div>
                <div class="col-md-3">
                    <label>CVC Code</label>
                    <div id="card_cvc" class="field form-control"></div>
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12">
                    <div class="form-check form-check-inline custom-control custom-checkbox">
                        <input type="checkbox" name="terms_conditions" id="terms_conditions" class="custom-control-input">
                        <label for="terms_conditions" class="custom-control-label">
                            I agree to terms & conditions
                        </label>
                    </div>
                    @error('terms_conditions')
                    <div class="text-danger font-italic">{{ $message }}</div>
                    @enderror
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12 small text-muted">
                    <div class="alert alert-warning">
                        <strong>NOTE: </strong> All the payments are handled by <a target="_blank"
                            href="https://stripe.com">STRIPE</a>. We don't store any of your data.
                    </div>
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-12">
                    <div class="text-danger font-italic generic-errors"></div>
                </div>
            </div>
            <div class="row form-group">
                <div class="col-md-6">
                    <input type="submit" value="Pay via Stripe" class="btn btn-primary pay-via-stripe-btn">
                </div>
            </div>
        </form>
    </div>
</div>


Now we have our frontend ready let's integrate the javascript Stripe payment token, card validations code


Stripe Javascript Code

<script src="https://code.jquery.com/jquery-3.5.1.min.js" crossorigin="anonymous"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>
    // Create an instance of the Stripe object
    // Set your publishable API key
    var stripe = Stripe('{{ env("STRIPE_PUBLISH_KEY") }}');

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

    var style = {
        base: {
            fontWeight: 400,
            fontFamily: '"DM Sans", Roboto, Open Sans, Segoe UI, sans-serif',
            fontSize: '16px',
            lineHeight: '1.4',
            color: '#1b1642',
            padding: '.75rem 1.25rem',
            '::placeholder': {
                color: '#ccc',
            },
        },
        invalid: {
            color: '#dc3545',
        }
    };

    var cardElement = elements.create('cardNumber', {
        style: style
    });
    cardElement.mount('#card_number');

    var exp = elements.create('cardExpiry', {
        'style': style
    });
    exp.mount('#card_expiry');

    var cvc = elements.create('cardCvc', {
        'style': style
    });
    cvc.mount('#card_cvc');

    // Validate input of the card elements
    var resultContainer = document.getElementById('paymentResponse');
    cardElement.addEventListener('change', function (event) {
        if (event.error) {
            resultContainer.innerHTML = '<p>' + event.error.message + '</p>';
        } else {
            resultContainer.innerHTML = '';
        }
    });

    // Get payment form element
    var form = document.getElementById('payment-form');

    // Create a token when the form is submitted.
    form.addEventListener('submit', function (e) {
        e.preventDefault();
        createToken();
    });

    // Create single-use token to charge the user
    function createToken() {
        stripe.createToken(cardElement).then(function (result) {
            if (result.error) {
                // Inform the user if there was an error
                resultContainer.innerHTML = '<p>' + result.error.message + '</p>';
            } else {
                // Send the token to your server
                stripeTokenHandler(result.token);
            }
        });
    }

    // Callback to handle the response from stripe
    function stripeTokenHandler(token) {
        // Insert the token ID into the form so it gets submitted to the server
        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>


Javascript Form Validation

$('.pay-via-stripe-btn').on('click', function () {
    var payButton   = $(this);
    var name        = $('#name').val();
    var email       = $('#email').val();


    if (name == '' || name == 'undefined') {
        $('.generic-errors').html('Name field required.');
        return false;
    }
    if (email == '' || email == 'undefined') {
        $('.generic-errors').html('Email field required.');
        return false;
    }


    if(!$('#terms_conditions').prop('checked')){
        $('.generic-errors').html('The terms conditions must be accepted.');
        return false;
    }
});


Overall Payment Page

@extends('master')

@section('javascripts')
<script src="https://code.jquery.com/jquery-3.5.1.min.js" crossorigin="anonymous"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>
    // Create an instance of the Stripe object
    // Set your publishable API key
    var stripe = Stripe('{{ env("STRIPE_PUBLISH_KEY") }}');

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

    var style = {
        base: {
            fontWeight: 400,
            fontFamily: '"DM Sans", Roboto, Open Sans, Segoe UI, sans-serif',
            fontSize: '16px',
            lineHeight: '1.4',
            color: '#1b1642',
            padding: '.75rem 1.25rem',
            '::placeholder': {
                color: '#ccc',
            },
        },
        invalid: {
            color: '#dc3545',
        }
    };

    var cardElement = elements.create('cardNumber', {
        style: style
    });
    cardElement.mount('#card_number');

    var exp = elements.create('cardExpiry', {
        'style': style
    });
    exp.mount('#card_expiry');

    var cvc = elements.create('cardCvc', {
        'style': style
    });
    cvc.mount('#card_cvc');

    // Validate input of the card elements
    var resultContainer = document.getElementById('paymentResponse');
    cardElement.addEventListener('change', function (event) {
        if (event.error) {
            resultContainer.innerHTML = '<p>' + event.error.message + '</p>';
        } else {
            resultContainer.innerHTML = '';
        }
    });

    // Get payment form element
    var form = document.getElementById('payment-form');

    // Create a token when the form is submitted.
    form.addEventListener('submit', function (e) {
        e.preventDefault();
        createToken();
    });

    // Create single-use token to charge the user
    function createToken() {
        stripe.createToken(cardElement).then(function (result) {
            if (result.error) {
                // Inform the user if there was an error
                resultContainer.innerHTML = '<p>' + result.error.message + '</p>';
            } else {
                // Send the token to your server
                stripeTokenHandler(result.token);
            }
        });
    }

    // Callback to handle the response from stripe
    function stripeTokenHandler(token) {
        // Insert the token ID into the form so it gets submitted to the server
        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();
    }

    $('.pay-via-stripe-btn').on('click', function () {
        var payButton   = $(this);
        var name        = $('#name').val();
        var email       = $('#email').val();

        if (name == '' || name == 'undefined') {
            $('.generic-errors').html('Name field required.');
            return false;
        }
        if (email == '' || email == 'undefined') {
            $('.generic-errors').html('Email field required.');
            return false;
        }

        if(!$('#terms_conditions').prop('checked')){
            $('.generic-errors').html('The terms conditions must be accepted.');
            return false;
        }
    });

</script>
@endsection

@section('content')
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <h1 class="text-center">Stripe Payment Demo</h1>
            <hr>
            @if (session()->has('error'))
                <div class="text-danger font-italic">{{ session()->get('error') }}</div>
            @endif
            <form action="{{ url('payments') }}" method="post" id="payment-form">
                @csrf
                <div class="row form-group">
                    <div class="col-md-12">
                        <label for="name">Name</label>
                        @error('name')
                        <div class="text-danger font-italic">{{ $message }}</div>
                        @enderror
                        <input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}">
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12">
                        <label for="email">Email</label>
                        @error('email')
                        <div class="text-danger font-italic">{{ $message }}</div>
                        @enderror
                        <input type="text" name="email" id="email" class="form-control" value="{{ old('email') }}">
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12">
                        <label>Billing Amount in US Dollars</label> <br>
                        <h2 class="text-muted">$1</h2>
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12">
                        <!-- Display errors returned by createToken -->
                        <label>Card Number</label>
                        <div id="paymentResponse" class="text-danger font-italic"></div>
                        <div id="card_number" class="field form-control"></div>
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-3">
                        <label>Expiry Date</label>
                        <div id="card_expiry" class="field form-control"></div>
                    </div>
                    <div class="col-md-3">
                        <label>CVC Code</label>
                        <div id="card_cvc" class="field form-control"></div>
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12">
                        <div class="form-check form-check-inline custom-control custom-checkbox">
                            <input type="checkbox" name="terms_conditions" id="terms_conditions" class="custom-control-input">
                            <label for="terms_conditions" class="custom-control-label">
                                I agree to terms & conditions
                            </label>
                        </div>
                        @error('terms_conditions')
                        <div class="text-danger font-italic">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12 small text-muted">
                        <div class="alert alert-warning">
                            <strong>NOTE: </strong> All the payments are handled by <a target="_blank"
                                href="https://stripe.com">STRIPE</a>. We don't store any of your data.
                        </div>
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-12">
                        <div class="text-danger font-italic generic-errors"></div>
                    </div>
                </div>
                <div class="row form-group">
                    <div class="col-md-6">
                        <input type="submit" value="Pay via Stripe" class="btn btn-primary pay-via-stripe-btn">
                    </div>
                </div>
            </form>
        </div>
    </div>
@endsection


master.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Stripe Payment Integration</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
    
    @yield('content')

    @yield('javascripts')
</body>
</html>

Step 7 - Backend Stripe Checkout


Here I have hardcoded the amount to $1 and currency to US Dollars, please change accordingly.


Validation - I could have added PaymentStoreRequest as a request class and put my validation, but for the sake of simplicity I have added in the controller


Payments Controller

<?php

namespace App\Http\Controllers;

use Exception;
use App\Payment;
use Stripe\Charge;
use Stripe\Stripe;
use Stripe\Customer;
use Illuminate\Http\Request;

class PaymentsController extends Controller
{
    public function create()
    {
        return view('payments.create');
    }

    public function store()
    {
        request()->validate([
            'name' => 'required',
            'email' => 'required|email',
            'terms_conditions' => 'accepted'
        ]);

        /** I have hard coded amount. You may fetch the amount based on customers order or anything */
        $amount     = 1 * 100;
        $currency   = 'usd';

        if (empty(request()->get('stripeToken'))) {
            session()->flash('error', 'Some error while making the payment. Please try again');
            return back()->withInput();
        }
        Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
        try {
            /** Add customer to stripe, Stripe customer */
            $customer = Customer::create([
                'email'     => request('email'),
                'source'    => request('stripeToken')
            ]);
        } catch (Exception $e) {
            $apiError = $e->getMessage();
        }

        if (empty($apiError) && $customer) {
            /** Charge a credit or a debit card */
            try {
                /** Stripe charge class */
                $charge = Charge::create(array(
                    'customer'      => $customer->id,
                    'amount'        => $amount,
                    'currency'      => $currency,
                    'description'   => 'Some testing description'
                ));
            } catch (Exception $e) {
                $apiError = $e->getMessage();
            }

            if (empty($apiError) && $charge) {
                // Retrieve charge details 
                $paymentDetails = $charge->jsonSerialize();
                if ($paymentDetails['amount_refunded'] == 0 && empty($paymentDetails['failure_code']) && $paymentDetails['paid'] == 1 && $paymentDetails['captured'] == 1) {
                    /** You need to create model and other implementations */
                    /*
                    Payment::create([
                        'name'                          => request('name'),
                        'email'                         => request('email'),
                        'amount'                        => $paymentDetails['amount'] / 100,
                        'currency'                      => $paymentDetails['currency'],
                        'transaction_id'                => $paymentDetails['balance_transaction'],
                        'payment_status'                => $paymentDetails['status'],
                        'receipt_url'                   => $paymentDetails['receipt_url'],
                        'transaction_complete_details'  => json_encode($paymentDetails)
                    ]);
                    */
                    return redirect('/thankyou/?receipt_url=' . $paymentDetails['receipt_url']);
                } else {
                    session()->flash('error', 'Transaction failed');
                    return back()->withInput();
                }
            } else {
                session()->flash('error', 'Error in capturing amount: ' . $apiError);
                return back()->withInput();
            }
        } else {
            session()->flash('error', 'Invalid card details: ' . $apiError);
            return back()->withInput();
        }
    }

    public function thankyou()
    {
        return view('payments.thankyou');
    }
}

Step 8 - Thank You Page


I would like to add the thanks page along with Stripe inbuilt receipt URL. The reason I didn't add the receipt URL in the session is that if the user refreshes the page then it will go away.


PaymentsController@thankyou

public function thankyou()
{
    return view('payments.thankyou');
}


I am using the built in receipt from Stripe for the customers.


thankyou.blade.php

@extends('master')

@section('content')
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <div class="row">
                <div class="col-md-12">
                    <h2 class="text-center">Thank you for making payment.</h2>
                </div>
                @if (Request::has('receipt_url'))
                    <h4 class="text-center">
                        <a href="{{ Request::get('receipt_url') }}" target="_blank">
                            Click here to download you payment receipt
                        </a>
                    </h4>
                @endif
            </div>
            <br>
        </div>
    </div>
@endsection


Stripe Payment Receipt

Stripe Payment Receipt


Conclusion


I hope this article helped you. Please share it with your friends.


NOTE: Code available in GitHub @ Stripe Integration With Laravel




AUTHOR

Channaveer Hakari

I am a full-stack developer working at WifiDabba India Pvt Ltd. I started this blog so that I can share my knowledge and enhance my skills with constant learning.

Never stop learning. If you stop learning, you stop growing