Laravel

how to integrate stripe checkout Process payment gateway in laravel

In this tutorial, we will learn how to integrate Stripe, a popular payment processing platform, into a Laravel web application. Stripe provides developers with powerful tools to handle online payments securely and efficiently. By the end of this tutorial, you will have a fully functional web application that allows users to make payments using Stripe.

Prerequisites: 
Before we start, make sure you have the following prerequisites:

  • Laravel installed on your local development environment.
  • A Stripe account. Sign up at https://stripe.com/ to obtain your API keys.

 

Step 1: Setting up Stripe and Laravel

Install Stripe PHP SDK: 
Begin by installing the Stripe PHP SDK using Composer. Run the following command in your terminal:

composer require stripe/stripe-php

Configure API Keys: 
In the .env file of your Laravel application, add your Stripe API keys:

STRIPE_KEY=your_stripe_publishable_key
STRIPE_SECRET=your_stripe_secret_key

Step 2: Creating the Payment Controller and Routes

Generate Controller: 
Run the following Artisan command to create a controller that will handle payment-related actions:

php artisan make:controller StripePaymentController
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use App\Models\Product;
use http\Env\Response;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class StripePaymentController extends Controller
{
   public const PRODUCT_ARRAY = [
       [
           "id" => 1,
           "name" => "Brief description",
           "price" => 40.50,
           "image" => "https://via.placeholder.com/640x480.png/0000ee?text=ut"
       ],
       [
           "id" => 2,
           "name" => "Second product",
           "price" => 60.50,
           "image" => "https://via.placeholder.com/640x480.png/007733?text=enim"
       ]
   ];
   public function index(Request $request)
   {
       $products = StripePaymentController::PRODUCT_ARRAY;
       return view('payment.index', compact('products'));
   }
   public function checkout(Request $request)
   {
       $this->validate($request, [
           'name' => 'required',
           'phone' => 'required',
           'email' => 'required'
       ]);
       \Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
       $products = StripePaymentController::PRODUCT_ARRAY;
       $lineItems = [];
       $totalPrice = 0;
       foreach ($products as $product) {
           $totalPrice += $product['price'];
           $lineItems[] = [
               'price_data' => [
                   'currency' => 'INR',
                   'product_data' => [
                       'name' => $product['name'],
                       'images' => [$product['image']]
                   ],
                   'unit_amount' => $product['price'] * 100,
               ],
               'quantity' => 1,
           ];
       }
       $session = \Stripe\Checkout\Session::create([
           'line_items' => $lineItems,
           'mode' => 'payment',
           'success_url' => route('checkout.success', [], true) . "?session_id={CHECKOUT_SESSION_ID}",
           'cancel_url' => route('checkout.cancel', [], true),
       ]);
       $order = new Order();
       $order->status = 'unpaid';
       $order->name = $request->name;
       $order->phone = $request->phone;
       $order->email = $request->email;
       $order->total_price = $totalPrice;
       $order->session_id = $session->id;
       $order->save();
       return redirect($session->url);
   }
   public function success(Request $request)
   {
       \Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
       $sessionId = $request->get('session_id');
       try {
           $session = \Stripe\Checkout\Session::retrieve($sessionId);
           if (!$session) {
               throw new NotFoundHttpException;
           }
           $customer = \Stripe\Customer::retrieve($session->customer);
           $order = Order::where('session_id', $session->id)->first();
           if (!$order) {
               throw new NotFoundHttpException();
           }
           if ($order->status === 'unpaid') {
               $order->status = 'paid';
               $order->save();
           }
           return view('payment.success', compact('customer'));
       } catch (\Exception $e) {
           throw new NotFoundHttpException();
       }
   }
   public function cancel()
   {
       // Payment failed
       return view('payment.failure');
   }
   public function webhook()
   {
       // This is your Stripe CLI webhook secret for testing your endpoint locally.
       $endpoint_secret = env('STRIPE_WEBHOOK_SECRET');
       $payload = @file_get_contents('php://input');
       $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
       $event = null;
       try {
           $event = \Stripe\Webhook::constructEvent(
               $payload,
               $sig_header,
               $endpoint_secret
           );
       } catch (\UnexpectedValueException $e) {
           // Invalid payload
           return response('', 400);
       } catch (\Stripe\Exception\SignatureVerificationException $e) {
           // Invalid signature
           return response('', 400);
       }
       // Handle the event
       switch ($event->type) {
           case 'checkout.session.completed':
               $session = $event->data->object;
               $order = Order::where('session_id', $session->id)->first();
               if ($order && $order->status === 'unpaid') {
                   $order->status = 'paid';
                   $order->save();
                   // Send email to customer
               }
               // ... handle other event types
           default:
               echo 'Received unknown event type ' . $event->type;
       }
       return response('');
   }
}

Implement Controller: 
In the StripePaymentController.php file, paste the provided code. This controller will handle actions such as displaying the checkout form, processing the payment, handling payment success, failure, and Stripe webhook events.

Define Routes: 
In the routes/web.php file, define the routes that correspond to the controller's methods. The routes will handle the display of the checkout form, payment processing, success, failure, and webhook endpoints.

routes/web.php

Route::get('/pay', [StripePaymentController::class, 'index']);
Route::post('/checkout', [StripePaymentController::class, 'checkout'])->name('checkout');
Route::get('/success', [StripePaymentController::class, 'success'])->name('checkout.success');
Route::get('/cancel', [StripePaymentController::class, 'cancel'])->name('checkout.cancel');
Route::post('/webhook', [StripePaymentController::class, 'webhook'])->name('checkout.webhook');

Step 3: Creating the Checkout Form View

Create Views Directory: 
Create a directory named payment inside the resources/views directory to store the views related to payment processing.

Index View: 
Create a file named index.blade.php inside the payment directory. This view will display the checkout form where users can enter their billing information and proceed to payment. Paste the provided index.blade.php code.

resources\views\payment\index.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">
   <meta name="csrf-token" content="{{ csrf_token() }}">
   <title>Document</title>
   <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
   <div class="container">
       <div class="py-5 text-center">
           <h2>Checkout form</h2>
       </div>
       <div class="row">
           <div class="col-md-4 order-md-2 mb-4">
               <h4 class="d-flex justify-content-between align-items-center mb-3">
                   <span class="text-muted">Your cart</span>
                   <span class="badge badge-secondary badge-pill">3</span>
               </h4>
               <ul class="list-group mb-3">
                   @php
                       $totalPrice = 0;
                   @endphp
                   @foreach ($products as $product)
                       @php
                           $totalPrice += $product['price'];
                       @endphp
                       <li class="list-group-item">
                           <div class="row">
                               <div class="col-4">
                                   <img src="{{ $product['image'] }}" style="max-width: 100%">
                               </div>
                               <div class="col-6">
                                   <h6 class="my-0">{{ $product['name'] }}</h6>
                               </div>
                               <span class="col-2" class="text-muted">${{ $product['price'] }}</span>
                           </div>
                       </li>
                   @endforeach
                   <li class="list-group-item d-flex justify-content-between">
                       <span>Total (USD)</span>
                       <strong>${{$totalPrice}}</strong>
                   </li>
               </ul>
           </div>
           <div class="col-md-8 order-md-1">
               <h4 class="mb-3">Billing address</h4>
               <form action="{{ route('checkout') }}" method="POST" class="needs-validation" novalidate>
                   @csrf
                   <div class="row">
                       <div class="col-md-6 mb-3">
                           <label for="name">Name</label>
                           <input type="text" class="form-control" name="name" value="{{ old('name') }}">
                           {!! $errors->first('name', '<p class="text-danger">:message</p>') !!}
                       </div>
                       <div class="col-md-6 mb-3">
                           <label for="phone">Phone</label>
                           <input type="text" class="form-control" name="phone" value="{{ old('phone') }}">
                           {!! $errors->first('phone', '<p class="text-danger">:message</p>') !!}
                       </div>
                       <div class="col-md-12 mb-3">
                           <label for="email">Email</label>
                           <input type="text" class="form-control" name="email" value="{{ old('email') }}">
                           {!! $errors->first('email', '<p class="text-danger">:message</p>') !!}
                       </div>
                   </div>
                   <hr class="mb-4">
                   <h4 class="mb-3">Payment</h4>
                   <div class="d-block my-3">
                       <div class="custom-control custom-radio">
                           <input id="credit" name="paymentMethod" type="radio" class="custom-control-input" checked required>
                           <label class="custom-control-label" for="credit">Debit/Credit (stripe)</label>
                       </div>
                   </div>
                   <hr class="mb-4">
                   <button class="btn btn-primary btn-lg btn-block" type="submit">Place Order</button>
               </form>
           </div>
       </div>
   </div>
</body>
</html>

Step 4: Creating the Success and Failure Views

Success View: 
Create a file named success.blade.php inside the payment directory. This view will be shown when the payment is successful. Paste the provided success.blade.php code.

resources\views\payment\success.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">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>Document</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
  <div class="container">
      <div class="py-5 text-center">
          <!-- Display payment success message -->
          <h1>Payment Successful!</h1>
          <h2>Name: {{ $customer->name }}</h2>
          <h2>Transaction ID: {{ $customer->id  }}</h2>
      </div>
  </div>
</body>
</html>

Failure View: 
Create a file named failure.blade.php inside the payment directory. This view will be shown when the payment fails or is canceled. Paste the provided failure.blade.php code.

resources\views\payment\failure.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">
  <meta name="csrf-token" content="{{ csrf_token() }}">
  <title>Document</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
  <div class="container">
      <div class="py-5 text-center">
          <h1>Payment Failed!</h1>
      </div>
  </div>
</body>
</html>

Step 5: Handling Stripe Webhooks

Implement Webhook Method: 
The webhook method in the StripePaymentController handles Stripe webhook events. Webhooks allow your application to receive real-time updates from Stripe. This method processes the checkout.session.completed event, updating the order status accordingly.

Step 6: Testing the Payment Flow

Run the Application: 
Start your Laravel development server and navigate to the /pay route in your web browser. You should see the checkout form displaying the products and total price.

Testing Payments: 
Use Stripe's test card numbers to make test payments. You can find the test card numbers on the Stripe documentation page.

Leave A Comment