In the ever-evolving landscape of web development, integrating secure and efficient payment gateways is crucial for any application, especially when it comes to e-commerce websites. Laravel, a powerful PHP framework, provides an excellent foundation for building robust web applications, and integrating the Stripe payment gateway into a Laravel project can significantly enhance the user experience. In this comprehensive guide, we'll walk you through the process of seamlessly integrating Stripe into your Laravel application.
Before diving into the integration process, ensure you have a valid Stripe account. Head over to the Stripe website and sign up or log in to your existing account. Once logged in, navigate to the dashboard, where you'll find essential information such as your API keys, which we'll use in the integration process.
composer create-project --prefer-dist laravel/laravel your-project-name
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous" />
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Include jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<title>Book Appointment</title>
<style>
#second,
#third,
#forth,
#fifth {
display: none;
}
.time-slot {
padding: 5px;
border: 1px solid #ccc;
margin-bottom: 5px;
width: 100px;
}
.fancy-btn {
background: #000;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 5px;
font-weight: bold;
transition: background 0.3s ease-in-out;
}
.fancy-btn:hover {
background: l #004db2;
filter: brightness(1.1);
}
.time-slot-btn {
background-color: white;
color: #252424;
border: 1px solid gray;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
font-weight: bold;
transition: background 0.3s ease-in-out;
}
.time-slot-btn:hover {
background-color: #007bff;
color: white;
filter: brightness(1.1);
}
.booked-slot {
background-color: #f4acac;
color: #868282;
cursor: not-allowed;
}
.booked-slot:hover {
background-color: #f4acac;
color: #868282;
cursor: not-allowed;
}
input[type="date"]:disabled {
background-color: #f0f0f0;
color: #888888;
cursor: not-allowed;
}
.highlighted {
background-color: #007bff;
color: white;
/* Change to the highlight color you prefer */
}
.highlighted:hover {
background-color: #2178DA;
color: white;
/* Change to the highlight color you prefer */
}
</style>
</head>
<body>
<section class="bg-gray">
<div class="container">
<div class="row d-flex justify-content-center">
<div class="col-md-8 pt-5">
<h1>Book an Appointment</h1>
{{-- @if (count($errors) > 0)
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
</div>
</div>
@endif --}}
@if (session()->has('success'))
<div class="alert alert-dismissable alert-success">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>
{!! session()->get('success') !!}
</strong>
</div>
@endif
<div class="progress mb-3" style="height: 40px">
<div class="progress-bar bg-success rounded border" role="progressbar" style="width: 20%"
id="progressBar">
<b class="lead fw-bold" id="progressText">Step - 1</b>
</div>
</div>
<div class="card bg-white p-3">
<form action="{{ route('create.appointment') }}" method="post">
@csrf
<h4 class="bg-primary text-center p-1 rounded text-light">
Personal Information
</h4>
<div class="mb-3">
<label for="">Name</label>
<input type="text" name="name" id="name" class="form-control"
placeholder="Enter your name">
<span class="form-text text-danger" id="nameError"></span>
@error('name')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="mb-3">
<label for="">Email</label>
<input type="email" id="email" class="form-control" name="email"
placeholder="Enter your email">
<span class="form-text text-danger" id="emailError"></span>
@error('email')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="mb-3">
<label for="">Phone</label>
<input type="tel" id="phone" class="form-control" name="email"
placeholder="Enter your email">
<span class="form-text text-danger" id="phoneError"></span>
@error('email')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<input type="hidden" id="paymentMethod" name="payment_method"
value="Cash on delivery">
<div class="form-check">
<input class="form-check-input" type="radio" name="payment_method"
id="cash_on_delivery" value="Cash on delivery">
<label class="form-check-label" for="cash_on_delivery">
Cash On Delivery
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="payment_method"
id="stripe" value="Stripe">
<label class="form-check-label" for="stripe">
Stripe
</label>
</div>
<div class="form-group d-flex justify-content-between pt-3">
<a href="javascript:void(0);" class="btn btn-danger" id="prev-5">Previous</a>
<button id="submit" type="submit" class="btn btn-success" disabled>
Submit
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
This is the blade code to book the appointment on two options first on cash on delivery and second on the stripe
php artisan make:model Booking -mcr
This command will create the model Booking and along with model it will create migration, controller and resource
now let set up migration
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('appointments', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->string('email')->nullable();
$table->string('phone')->nullable();
$table->string('payment_method')->nullable();
$table->json('payment')->nullable();
$table->enum('payment_status')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('appointments');
}
};
Now let set up model and controller
App\Models\Booking.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Booking extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'phone',
'email',
'payment',
'amount',
'payment_method',
'payment_status',
'order_id',
'price'
];
protected $casts = [
'payment' => 'json'
];
}
Now controller
App\Http\Controllers\BookingController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\Booking;
class BookingController extends Controller
{
public function createAppointment(Request $request)
{
$data = $this->validate($request ,[
'name' => 'required|string|max:45',
'order_id' => 'nullable|string|max:6',
'email' => 'required|email:rfc,dns|max:45',
'phone' => 'required|string|max:20',
'payment' => 'nullable|json',
'payment_status' => 'nullable|boolean',
'payment_method' => 'nullable|string|max:50',
'price' => 'required|string|max:50'
]);
$appointment = Appointment::create($data);
Session::put('appointment',$appointment);
if($appointment->payment_method == 'Stripe')
{
return redirect()->route('stripe');
}else{
Session::put('data',$appointment);
return redirect()->route('thanks');
}
}
}
composer require stripe/stripe-php
STRIPE_KEY=pk_test_xxxxxx
STRIPE_SECRET=sk_test_xxxxxx
config/services.php
'stripe' => [
'secret' => env('STRIPE_SECRET'),
],
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel 10 Stripe Payment Gateway Integration - VFIX TECHNOLOGY</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<section class="p-4 p-md-5"
style="
background-image: url(https://mdbcdn.b-cdn.net/img/Photos/Others/background3.webp);
">
<div class="row d-flex justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-5">
<div class="card rounded-3">
<div class="card-body p-4">
<div class="text-center mb-4">
<h3>{{ $settingData->bname }}</h3>
<h6>Payment</h6>
</div>
<form role="form" action="{{ route('stripe.post') }}" method="post"
class="require-validation" data-cc-on-file="false"
data-stripe-publishable-key="{{ env('STRIPE_KEY') }}" id="payment-form">
@csrf
<p class="fw-bold mb-0">Service details:</p>
<span>Service name: {{ $appointment->service->name }}</span><span class="ps-4">Amount to pay:
{{ $appointment->amount }}</span>
<p class="fw-bold mb-0 pt-3">Safe Checkout:</p>
<img src="{{ asset('images/credit-cards.webp') }}" class="w-100" alt="">
<p class="fw-bold mb-4 pt-3">Fill below details to pay:</p>
<div class="form-outline mb-4">
<input type="text" id="formControlLgXsd" class="form-control form-control-lg shadow-none"
value="" placeholder="Enter Cardholder's Name" />
<label class="form-label" for="formControlLgXsd">Cardholder's Name</label>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="form-outline">
<input type="text" id="formControlLgXM"
class="form-control form-control-lg shadow-none card-number" size="20"
placeholder="Enter 16 digit card number"/>
<label class="form-label" for="formControlLgXM">Card Number</label>
</div>
</div>
<div class="col-4">
<div class="form-outline">
<input type="password" id="formControlLgExpk"
class="form-control form-control-lg shadow-none card-expiry-month" placeholder="MM" />
<label class="form-label" for="formControlLgExpk">Expire Month</label>
</div>
</div>
<div class="col-4">
<div class="form-outline">
<input type="password" id="formControlLgExll"
class="form-control form-control-lg shadow-none card-expiry-year" placeholder="YYYY" />
<label class="form-label" for="formControlLgExll">Expire Year</label>
</div>
</div>
<div class="col-4">
<div class="form-outline">
<input type="password" id="formControlLgcvv"
class="form-control form-control-lg shadow-none card-cvc" size="4"
placeholder="Cvv" />
<label class="form-label" for="formControlLgcvv">Cvv</label>
</div>
</div>
</div>
<button class="btn btn-success btn-lg btn-block">Pay Now</button>
</form>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
$(function() {
var $form = $(".require-validation");
$('form.require-validation').bind('submit', function(e) {
var $form = $(".require-validation"),
inputSelector = ['input[type=email]', 'input[type=password]', 'input[type=text]',
'input[type=file]', 'textarea'
].join(', '),
$inputs = $form.find('.required').find(inputSelector),
$errorMessage = $form.find('div.error'),
valid = true;
$errorMessage.addClass('hide');
$('.has-error').removeClass('has-error');
$inputs.each(function(i, el) {
var $input = $(el);
if ($input.val() === '') {
$input.parent().addClass('has-error');
$errorMessage.removeClass('hide');
e.preventDefault();
}
});
if (!$form.data('cc-on-file')) {
e.preventDefault();
Stripe.setPublishableKey($form.data('stripe-publishable-key'));
Stripe.createToken({
number: $('.card-number').val(),
cvc: $('.card-cvc').val(),
exp_month: $('.card-expiry-month').val(),
exp_year: $('.card-expiry-year').val()
}, stripeResponseHandler);
}
});
function stripeResponseHandler(status, response) {
if (response.error) {
$('.error')
.removeClass('hide')
.find('.alert')
.text(response.error.message);
} else {
/* token contains id, last4, and card type */
var token = response['id'];
$form.find('input[type=text]').empty();
$form.append("<input type='hidden' name='stripeToken' value='" + token + "'/>");
$form.get(0).submit();
}
}
});
</script>
now when the user will click on the pay now button payment of the amount will be visible and user can proceed to payment
Now after the payment has been done we have to save the response like the transaction id methods and many details which stripe send us as a response so to save that thing in the table we have to update the table by matching the order_id as it is unique in the table you can use id too but using order_id is better and proffectional so use it.
php artisan make:controller StripePaymentController
Now add this code in the controller
<?php
namespace App\Http\Controllers\payment;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Stripe;
use Session;
use App\Models\Setting;
use App\Models\Appointment;
class StripeController extends Controller
{
public function index()
{
$appointment = Session::get('appointment');
Session::put('appointment',$appointment);
return view('stripe',compact('appointment'));
}
public function store(Request $request, Appointment $appointment)
{
$app = Session::get('appointment');
$sett = Setting::first();
Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
try {
$charge = Stripe\Charge::create([
"amount" => $app->amount * 100,
"currency" => $sett->payment_gateway['currency'],
"source" => $request->stripeToken,
"description" => "This payment is testing purpose of astrology",
]);
$chargeJson = $charge->jsonSerialize();
$orderId = $app->order_id;
$data = Appointment::where('order_id',$orderId)->firstOrFail();
$data->payment_status = 1;
$data->payment = $chargeJson;
$data->save();
// return response()->json(['message' => 'Payment successful', 'charge' => $chargeJson]);
} catch (\Exception $e) {
// Handle any exceptions, log errors, and return an error response
return response()->json(['error' => $e->getMessage()], 500);
}
Session::put('data', $data);
return redirect()->route('thanks');
}
}
Now create a route
Route::get('stripe', [StripeController::class, 'index'])->name('stripe');
Route::post('stripe', [StripeController::class, 'store'])->name('stripe.post');
Here in the index method we are getting the appointment details after creating the appointment through session and pass that details in the strip.blade.php
In store method we are getting the appointment details throught session form the index method
now in the try there is $charge where we are passing the required details and after passing the details we are jsonSerialize() function to convert the response in the json format and after that we are matching the order id with the session order id if the condition is true we are updating the appointment table like updating the payment status to 1 after the successfull payment and storing the response in the payment column in the table $jsonCharges contain the response so we are saving the response in the payment column