Integrate PayPal payment gateway in Laravel

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

Access keys and test users

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:

 

Dialog to create an app in PayPal

 

 

 

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:

 

 

PayPal Credentials

 

 

 

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":

 

Sandbox account

 

 

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>"
),

Implement a simple payment system

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.

Client

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

En español

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.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.

!Courses from!

10$

On Udemy

There are 4d 19:58!


Udemy

!Courses from!

4$

In Academy

View courses

!Books from!

1$

See the books
¡Become an affiliate on Gumroad!