Stripe Payment Gateway Integration in Laravel

If you are running an online store or some kind of paid service, you probably need to accept card payments on your application. Stripe is one of the popular payment gateways which accept credit or debit card payments online. In this article, we study Stripe payment gateway integration in Laravel.

For integrating Stripe gateway in Laravel we are going to use the Omnipay library which is popular among the developers. I am going to integrate the Payment Intents API with Omnipay.

The benefits of using Stripe Payment Intents API are as follows.

  • Automatic authentication handling
  • No double charges
  • No idempotency key issues
  • Support for Strong Customer Authentication (SCA) and similar regulatory changes

The cards which require SCA or two-factor authentication will charge only after the customer validates the purchase.

Having said that, let’s learn Stripe integration in Laravel.

Get API Keys for Stripe Integration

First, you need to create a Stripe account if you don’t have one already. From your Stripe dashboard, grab the secret and publishable key. You will get these keys from the Developers->API Keys page.

stripe-api-credentials

I’d recommend first testing the code with sandbox credentials. It is better to test the transactions with sandbox keys. If everything works well then go for live keys.

Basic Setup in Laravel for Stripe Integration

To start accepting online payments using Stripe, you need to perform a basic setup.

We will require stripe keys during API integration so add these keys to the .env file.

STRIPE_PUBLISHABLE_KEY=PASTE_PUBLISHABLE_KEY
STRIPE_SECRET_KEY=PASTE_SECRET_KEY
STRIPE_CURRENCY=USD

Whenever you add new constants in the environment file, you should clear the configuration cache.

php artisan config:cache

When it comes to online payments, one should store the transaction details in the database. For this, create a migration file using the command:

php artisan make:migration create_payments_table

In the migration, edit the up method as follow:

public function up()
{
    Schema::create('payments', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('payment_id');
        $table->string('payer_email');
        $table->float('amount', 10, 2);
        $table->string('currency');
        $table->string('payment_status');
        $table->timestamps();
    });
}

Next, run the below command to execute the migration.

php artisan migrate

We will require an HTML form where a user can enter their card details and other information. Create a payment.blade.php file and add the code below to it.

<link rel="stylesheet" href="{{ asset('/css/style.css') }}" />
<script src="https://js.stripe.com/v3/"></script>

@if ($message = Session::get('success'))
    <div class="success">
        <strong>{{ $message }}</strong>
    </div>
@endif


@if ($message = Session::get('error'))
    <div class="error">
        <strong>{{ $message }}</strong>
    </div>
@endif

<form action="{{ url('charge') }}" method="post" id="payment-form">
    <div class="form-row">
        <p><input type="text" name="amount" placeholder="Enter Amount" /></p>
        <p><input type="email" name="email" placeholder="Enter Email" /></p>
        <label for="card-element">
        Credit or debit card
        </label>
        <div id="card-element">
        <!-- A Stripe Element will be inserted here. -->
        </div>
      
        <!-- Used to display form errors. -->
        <div id="card-errors" role="alert"></div>
    </div>
    <p><button>Submit Payment</button></p>
    {{ csrf_field() }}
</form>
<script>
var publishable_key = '{{ env('STRIPE_PUBLISHABLE_KEY') }}';
</script>
<script src="{{ asset('/js/card.js') }}"></script>

In the blade file, I included CSS and JS files which I would define in the next steps. I also added success and error messages which would display after completing a payment. Next, define the routes as follows.

routes/web.php

Route::get('/payment', 'PaymentController@index');
Route::post('/charge', 'PaymentController@charge');
Route::get('/confirm', 'PaymentController@confirm');

I will create the PaymentController in the later part of the tutorial.

Generate Stripe Card Elements

Stripe provides its own prebuilt UI components that collect customer card data securely without handling sensitive data. The card details are converted to ‘Token’ which then needs to be sent to your servers. Using this ‘Token’ you can charge the customers. This is a way secure as your application doesn’t need to store or interact with customer card details.

In the blade, we included card.js file. Create this JS file under the public/js directory and add the below code to it.

js/card.js

// Create a Stripe client.
var stripe = Stripe(publishable_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',
        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();
}

The blade file has also included a stylesheet style.css. Create style.css inside the public/css folder. This CSS will have the below code.

css/style.css

.StripeElement {
    box-sizing: border-box;
    
    height: 40px;
    
    padding: 10px 12px;
    
    border: 1px solid transparent;
    border-radius: 4px;
    background-color: white;
    
    box-shadow: 0 1px 3px 0 #e6ebf1;
    -webkit-transition: box-shadow 150ms ease;
    transition: box-shadow 150ms ease;
}
 
.StripeElement--focus {
    box-shadow: 0 1px 3px 0 #cfd7df;
}
 
.StripeElement--invalid {
    border-color: #fa755a;
}
 
.StripeElement--webkit-autofill {
    background-color: #fefde5 !important;
}

When you visit the ‘/payment’ route you will see a form with built-in UI elements for the card. Stripe is providing different kinds of UI elements for building a checkout form with credit card details. Read more about it in their documentation.

Stripe Payment Gateway Integration in Laravel

We set all required configurations. Now we can go ahead and integrate the Stripe payment gateway in Laravel. Run the command below to install the Omnipay library in your project.

composer require league/omnipay omnipay/stripe

To call the blade file and charge the transaction create a PaymentController using the artisan command:

php artisan make:controller PaymentController

As we should store transaction details in the database, create a model Payment which is associated with the payments table in the database.

php artisan make:model Payment

Stripe Payment Intents API

The Payment Intents API handles the checkout flow and triggers additional authentication steps if required. Your customers might get redirected to their bank page for the authentication process. Once they complete the purchase, the customer will redirect to the URL set by the application. In our case, we set the redirect URL to the route('confirm').

The PaymentController will have the following code which follows the Payment Intents API to charge the transaction and insert the transaction details into the database.

PaymentController.php

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Omnipay\Omnipay;
use App\Models\Payment;

class PaymentController extends Controller
{
    public $gateway;
    public $completePaymentUrl;

    public function __construct()
    {
        $this->gateway = Omnipay::create('Stripe\PaymentIntents');
        $this->gateway->setApiKey(env('STRIPE_SECRET_KEY'));
        $this->completePaymentUrl = url('confirm');
    }

    public function index()
    {
        return view('payment');
    }

    public function charge(Request $request)
    {
        if($request->input('stripeToken'))
        {
            $token = $request->input('stripeToken');

            $response = $this->gateway->authorize([
                'amount' => $request->input('amount'),
                'currency' => env('STRIPE_CURRENCY'),
                'description' => 'This is a X purchase transaction.',
                'token' => $token,
                'returnUrl' => $this->completePaymentUrl,
                'confirm' => true,
            ])->send();

            if($response->isSuccessful())
            {
                $response = $this->gateway->capture([
                    'amount' => $request->input('amount'),
                    'currency' => env('STRIPE_CURRENCY'),
                    'paymentIntentReference' => $response->getPaymentIntentReference(),
                ])->send();

                $arr_payment_data = $response->getData();

                $this->store_payment([
                    'payment_id' => $arr_payment_data['id'],
                    'payer_email' => $request->input('email'),
                    'amount' => $arr_payment_data['amount']/100,
                    'currency' => env('STRIPE_CURRENCY'),
                    'payment_status' => $arr_payment_data['status'],
                ]);

                return redirect("payment")->with("success", "Payment is successful. Your payment id is: ". $arr_payment_data['id']);
            }
            elseif($response->isRedirect())
            {
                session(['payer_email' => $request->input('email')]);
                $response->redirect();
            }
            else
            {
                return redirect("payment")->with("error", $response->getMessage());
            }
        }
    }

    public function confirm(Request $request)
    {
        $response = $this->gateway->confirm([
            'paymentIntentReference' => $request->input('payment_intent'),
            'returnUrl' => $this->completePaymentUrl,
        ])->send();
        
        if($response->isSuccessful())
        {
            $response = $this->gateway->capture([
                'amount' => $request->input('amount'),
                'currency' => env('STRIPE_CURRENCY'),
                'paymentIntentReference' => $request->input('payment_intent'),
            ])->send();

            $arr_payment_data = $response->getData();

            $this->store_payment([
                'payment_id' => $arr_payment_data['id'],
                'payer_email' => session('payer_email'),
                'amount' => $arr_payment_data['amount']/100,
                'currency' => env('STRIPE_CURRENCY'),
                'payment_status' => $arr_payment_data['status'],
            ]);

            return redirect("payment")->with("success", "Payment is successful. Your payment id is: ". $arr_payment_data['id']);
        }
        else
        {
            return redirect("payment")->with("error", $response->getMessage());
        }
    }

    public function store_payment($arr_data = [])
    {
        $isPaymentExist = Payment::where('payment_id', $arr_data['payment_id'])->first();  
 
        if(!$isPaymentExist)
        {
            $payment = new Payment;
            $payment->payment_id = $arr_data['payment_id'];
            $payment->payer_email = $arr_data['payer_email'];
            $payment->amount = $arr_data['amount'];
            $payment->currency = env('STRIPE_CURRENCY');
            $payment->payment_status = $arr_data['payment_status'];
            $payment->save();
        }
    }
}

For the sandbox mode, Stripe provides dummy credit cards to test a transaction.

Once you are done with a testing sandbox mode, replace your test API keys with the live one and your application will start accepting real payments automatically.

That’s it! You are done with the Stripe payment gateway integration in Laravel. I would like to hear your thoughts and suggestions in the comment section below.

Related Articles

If you liked this article, then please subscribe to our YouTube Channel for video tutorials.

19 thoughts on “Stripe Payment Gateway Integration in Laravel

  1. Hello,
    I got the error message “Your card’s expiration date is incomplete”. I want to enable expiry month, year and cvv number so that user can fill it. Please guide me.

  2. Hi,

    I have an issue. i followed all the process but finally i am getting this error

    Invalid API Key provided: publisha***_key

    Can you pls help

  3. Hi,

    I followed your tutorial but I got this error:

    Please call Stripe() with your publishable key. You used an empty string

    Can you please help

      1. Yes, and it was about the publish key that (I don’t know why) was unable to read from the .env file.
        I’ve copied/pasted and now it’s everything running.
        Thank you for the tutorial (stay coding!) and I hope that this post would help someone else 😀

          1. While deploying the Laravel project on the production server, you must set doc root to the public folder. It restricted accessing the `.env` file outside the world.

  4. hello ,
    My name is animesh mana from kolkata,India.i am a php developer. in API related coding i always take help from here.i like this website.

Leave a Reply

Your email address will not be published. Required fields are marked *