PayPal is the electronic wallet par excellence and is widely used for online purchases, that is, over the Internet, and we can use it very easily in any web project and Laravel is no exception.
In this section we are going to learn the steps to configure PayPal as a payment method based on a product that we want to sell.
Although we do not have an exclusive PayPal package for Laravel, we do have a solution in JavaScript, which does not require using an exclusive package for PHP as we will see later:
https://www.npmjs.com/package/@paypal/paypal-js
The first thing we must do is install the package using the following command, only if you want to use the Node option:
$ npm install @paypal/paypal-js
In order to integrate PayPal into our application, we must have a PayPal account, once achieved, if we go to the PayPal development site:
https://developer.paypal.com/home
We click on the option that says "API Credentials" and create an application. To do this, we press the "Create App" button and a dialog like the following will appear:
You can leave the option of "Merchant" and "Create App".
Once the credentials have been created to be able to use the PayPal API, they will appear listed below:
We have a secret key that we will use on the server and a public key that we will use on the client, therefore, it will be exposed to anyone who views the source code of the page from their browser, in turn, we have access to some keys test accounts, with which we can make requests to a test or sandbox account.
Apart from the keys, test users are generated that we will use to make connections to the PayPal test API available in "Sandbox accounts":
On the server we must configure the access keys, as a recommendation, define the test keys as environment variables:
.env
PAYPAL_CLIENT_ID="<YOUR_DEV_PAYPAL_CLIENT_ID>"
PAYPAL_SECRET="<YOUR_DEV_PAYPAL_SECRET>"
And in the configurations the production lines:
config\app.php
return [
***
'paypal_id' => env('PAYPAL_CLIENT_ID', "<YOUR_PRO_PAYPAL_CLIENT_ID>"),
'paypal_secrect' => env('PAYPAL_SECRET',"<YOUR_PRO_PAYPAL_SECRET>"
),
When using this package, we have to do developments on both sides, on the client and on the server. Let's start with the client, which is where we do the most configuration.
On the client, let's start by creating a DIV that will serve as a container element for the PayPal widget, you can place any identifier that we will later place to reference the HTML element:
<div id="paypalCard"></div>
The following function allows us to create the PayPal widget, first, we obtain a reference to the PayPal API that we will use to create the widget by:
paypal = await loadScript
We have to do this if we work with the package in Node and some CDN options.
The next step is to create the order, there are many configuration parameters but, in this example we only use the amount:
{
amount: {
value: this.price,
},
},
Continuing with the implementation, we have the onApprove() callback that is executed when the user clicks on the widget and authorizes the payment, in it, we have reference to the already created order that consists of the payment information as well as the information from the client that we will later use on the server, therefore, the next step is to pass this data to the server to authorize it (and it is the server where the private key is used), for example, using a request by axios:
onApprove: function (data, actions) {
// TODO send data.orderID to server
}
The implementation with the PayPal CDN, we have two formats:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
src="https://unpkg.com/@paypal/paypal-js@8.0.0/dist/iife/paypal-js.min.js"></script>
</head>
<body>
<div id="paypalButtons"></div>
<script>
window.paypalLoadScript({
clientId: "{{config('app')['paypal_id']}}"
}).then((paypal) => {
paypal.Buttons().render("#paypalButtons");
});
</script>
</body>
</html>
Or the following one, which is what we are going to use:
resources\views\paypal.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<script src="https://www.paypal.com/sdk/js?client-id={{config('app')['paypal_id']}}"></script>
</head>
<body>
<div id="paypalButtons"></div>
<script>
paypal.Buttons({
createOrder: function(data, actions){
return actions.order.create({
purchase_units:[
{
amount: {
value:50
}
}
]
})
},
onApprove: function(data, actions){
// TODO send order to server
console.log(data.orderID)
axios.post('paypal-process-order/'+data.orderID)
}
}).render("#paypalButtons");
</script>
</body>
</html>
In the previous code, we send the order identifier through axios, for that, we use vite (remember that axios is installed by default in Laravel in the resources\js\bootstrap.js file).
We create the controller and the route:
routes\web.php
Route::get('/paypal', [PaymentPaypalController::class, 'paypal']);
Route::post('/paypal-process-order/{order}', [PaymentPaypalController::class, 'paypalProcessOrder']);
app\Http\Controllers\PaymentPaypalController.php
<?php
namespace App\Http\Controllers;
class PaymentPaypalController extends Controller
{
public function paypal() {
return view('paypal');
}
function paypalProcessOrder(string $order) {
dd($order);
}
}
In the controller you can see that we make a simple conditional to always use the PayPal production passwords when we are in production or the development passwords when we are in development.
In case you want to use Node, the code looks like this:
function setPaypal() {
let paypal;
try {
paypal = await loadScript({
"client-id":{{ config('app')['paypal_id'] }},
});
} catch (error) {
console.error("failed to load the PayPal JS SDK script", error);
}
if (paypal) {
try {
await paypal
.Buttons({
createOrder: function (data, actions) {
// This function sets up the details of the transaction, including the amount and line item details.
return actions.order.create({
purchase_units: [
{
amount: {
value: this.price,
},
},
],
});
}.bind(this),
onApprove: function (data, actions) {
// TODO send data.orderID to server
}.bind(this),
})
.render("#paypalCard");
} catch (error) {
console.error("failed to render the PayPal Buttons", error);
}
}
},
}
It is important to note that when you are in a testing environment, the PayPal URL indicates that it is in sandbox mode:
Figure 20-5: Sandbox Authentication in PayPal
https://www.sandbox.paypal.com/checkoutnow?***
And in production it looks like this:
https://www.paypal.com/checkoutnow?***
With createOrder() we create the order, which is created based on the data provided (the price in this example is $50) and the user authentication, which indicates that they are going to pay for the order, with the onApprove() function we create executed when the order has been approved and is passed to the server for later completion.
With this, the PayPal plugin is generated on the client, missing the server-side order processing.
- Andrés Cruz
This material is part of my complete course and book; You can purchase them from the books and/or courses section, Curso y Libro Laravel 11 con Tailwind Vue 3, introducción a Jetstream Livewire e Inerta desde cero - 2025.
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter
I agree to receive announcements of interest about this Blog.
!Courses from!
10$
On Udemy
There are 4d 19:58!
!Courses from!
4$
In Academy
View courses!Books from!
1$
See the books