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