
Good content takes time and effort to come up with.
Please consider supporting us by just disabling your AD BLOCKER and reloading this page again.
Hello Fellas, In this article let's discuss how to integrate Stripe with Laravel.
We will cover the following topics
NOTE: Code available in GitHub @ Stripe Integration With Laravel
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
.
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
The other way you can find the testing or production keys is by navigating to Developers -> API Keys, as shown in the following image
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
Add the keys you had got from Step 2 in .env
file as following
STRIPE_PUBLISH_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
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');
First let's create the payment page. The following is how it looks
public function create()
{
return view('payments.create');
}
<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
<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>
$('.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;
}
});
@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
<!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>
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
<?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');
}
}
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.
public function thankyou()
{
return view('payments.thankyou');
}
I am using the built in receipt from Stripe for the customers.
@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
I hope this article helped you. Please share it with your friends.
NOTE: Code available in GitHub @ Stripe Integration With Laravel
Install Linux, NGINX, MYSQL, PHP (LEMP Stack) on Ubuntu
What Is Method Chaining In PHP?
Install Packages Parallel For Faster Development In Composer
Send Email In PHP With PHPMailer
Generate Sitemap in PHP Laravel
Laravel Last Executed Query In Plain SQL Statement For Debugging
Firebase Cloud Messaging (FCM) Browser Web Push Notifications Using Javascript And PHP
Free SSL Certificate For CPanel
Accessors And Mutators In PHP Laravel
Why You Should Run Cronjobs In Laravel?
Create Gmail App Password For SMTP Mails
Create A Composer Package? Test It Locally And Add To Packagist Repository
Unable to prepare route [{fallbackPlaceholder}] for serialization. Uses Closure In Laravel