Cómo integrar pagos con PayPal JS SDK y Laravel

Tenemos varias formas de integrar pagos con PayPal en nuestros sitios web.

Veamos cómo lograrlo haciendo uso de el SDK de PayPal para Javascript, sobre un proyecto Laravel.

Cabe resaltar que la idea será muy similar para proyectos PHP en general, e incluso para cualquier otra tecnología backend, siguiendo los mismos pasos.

Paso 1: Obtener credenciales PayPal

El primer paso consiste en dirigirnos al PayPal Developer Dashboard.

Aquí debemos iniciar sesión con nuestra cuenta de PayPal y crear una aplicación usando el botón "Create App".

Si ya tienes una, basta con hacer clic en su nombre y obtener tus credenciales.

Por ejemplo, yo tengo estas 2 aplicaciones PayPal:

Obtener credenciales PayPal

Como puedes ver, tenemos aplicaciones en modo Sandbox y modo Live.

La integración debemos hacerla usando credenciales Sandbox, es decir, de prueba.

Una vez que el proyecto funcione correctamente, solo actualizaremos nuestras credenciales a Live.

Los datos que necesitamos son 2:

  • Client ID
  • Secret

Estos valores debemos copiarlos porque los usaremos en nuestro proyecto Laravel.

Credenciales PayPal a usar en nuestro proyecto

Para que puedas ver el valor de "Secret" solo haz clic en "Show".

Paso 2: Cargar botones y formulario PayPal con Javascript

PayPal puede implementarse de muchas formas. Lo que vamos a usar en esta ocasión se llama Standard Payments.

Lo primero que debemos hacer es decidir dónde vamos a mostrar el formulario de PayPal en nuestro proyecto web.

El SDK de PayPal nos pregunta sobre qué elemento HTML queremos cargar el formulario de pago, y entonces lo hará sobre ese contenedor.

Para que esta lógica se active debemos cargar el SDK de PayPal, de la siguiente manera:

<script src="https://www.paypal.com/sdk/js?client-id=TU_CLIENT_ID&currency=USD"></script>

Ten en cuenta que:

  • Debes reemplazar el valor de client-id en la URL del script, con tu ID de cliente que obtuviste en el paso anterior.
  • El script acepta varios parámetros que te permiten personalizar la forma de pago. Por ejemplo, currency=USD significa que los pagos se realizarán en dólares.

Si bien puedes escribir tu client-id directamente en la URL, lo recomendable es organizar tus credenciales en el archivo .env de tu proyecto.

Entonces, allí debes definir las siguientes 3 variables:

PAYPAL_CLIENT_ID=AfjYp...
PAYPAL_SECRET=EHJdU...
PAYPAL_MODE=sandbox

He puesto unos puntos suspensivos pero tú debes escribir tus datos completos.

Luego de cargar el SDK de PayPal lo siguiente es ejecutar el siguiente código Javascript para mostrar el formulario de pago:

paypal.Buttons({
    createOrder: function(data, actions) {
        return actions.order.create({
            // ...
            purchase_units: [{
                amount: {
                    value: 100
                }
            }],
        });
    },
    onApprove: function(data, actions) {
        // ...
    }
}).render('#paypal-button-container'); 

Este fragmento de código:

  • Lo puedes ubicar en un archivo JS independiente y cargarlo en tu página, o bien definirlo inline dentro de etiquetas <script>. Sólo asegúrate de ejecutarlo.
  • El método render es el que cargará el formulario sobre el elemento que se le indique. En este caso, sobre un elemento con id paypal-button-container.

Con esto, el formulario se mostrará en tu proyecto web, de manera similar a la siguiente:

Botones de pago PayPal

Esto, sin embargo, se puede personalizar.

Paso 3: Personalizar la experiencia de pago

Es posible personalizar:

  • los botones de pago, y
  • los datos que solicita el formulario de PayPal

Aunque esto es posible desde la URL del script que cargamos, es preferible personalizar a través de código Javascript.

Por ejemplo, cuando invocamos al método paypal.Buttons le pasamos un objeto con la configuración que necesitamos.

  • Un atributo de este objeto es fundingSource y nos permite definir qué opciones de pago queremos mostrar.
  • La función createOrder nos permite llenar los campos del formulario de pago con datos que ya tenemos.
  • La función onApprove nos permite decidir qué hacer luego de confirmar los datos del usuario, tan pronto estemos listos para procesar la orden.

Para el proyecto que estoy desarrollando, por ejemplo:

  • No necesito pedir una dirección de entrega.
  • Ya cuento con datos básicos del usuario al momento del pago (como su correo, nombres, apellidos y país).

Entonces indicamos todo ello, además del precio, de la siguiente manera:

paypal.Buttons({
    // ...
    createOrder: function(data, actions) {
        return actions.order.create({
            application_context: {
                shipping_preference: "NO_SHIPPING"
            },
            payer: {
                email_address: '{{ $email }}',
                name: {
                    given_name: '{{ $firstName }}',
                    surname: '{{ $lastName }}'
                },
                address: {
                    country_code: "{{ $country->code }}"
                }
            },
            purchase_units: [{
                amount: {
                    value: {{ $solution->getAmountToPay() }}
                }
            }],
        });
    },
    // ...
})

En este código de ejemplo:

  • Las variables que estoy imprimiendo con Blade las asigno desde mi controlador.
  • Estos datos los puedes obtener de tu base de datos, según la información que hayas recopilado, del usuario que va a hacer el pago.

Esto es útil, porque así los usuarios no necesitan escribir nuevamente toda su información.

El monto a pagar, en este ejemplo, está determinado por la expresión $solution->getAmountToPay().

Lo que ocurre es que, para el proyecto que estoy desarrollando, los usuarios pagan por acceder a sus resultados, luego de resolver un test online.

Es decir:

  • Tienes que reemplazar todos estos valores con tus propias variables.
  • Lo importante es el código Javascript, en este ejemplo.

Paso 4: Completar un pago client-side

Llegados a este punto estamos listos para completar nuestro pago de lado del cliente.

Una vez que un usuario aprueba un pago, nosotros recibimos información del mismo a través de nuestro callback onApprove.

Para empezar, puedes dejar esta función sin lógica y simplemente imprimir la información que recibe.

Por ejemplo:

paypal.Buttons({
    fundingSource: paypal.FUNDING.CARD,
    createOrder: function(data, actions) {
        // ...
    },
    onApprove: function(data, actions) {
        console.log('data', data);
        console.log('actions', actions);
    }
}).render('#paypal-button-container');
  • Aquí no he repetido la definición de createOrder pero es la que mostramos más arriba, adaptada a lo que necesites.
  • Para el proyecto que estoy haciendo, estoy indicando que sólo me interesan los pagos a través de tarjetas. Según tu caso, puedes considerar más opciones de pago, disponibles para el SDK de PayPal.

Cuentas Sandbox

Si te preguntas qué datos de prueba puedes usar, no te preocupes.

Esta información la encuentras en el Dashboard de PayPal Developer, en la opción Sandbox > Accounts.

Cuentas de prueba para ingresar al formulario PayPal

Desde aquí puedes crear cuentas Sandbox o bien usar una ya existente.

Completar y enviar formulario de pago

Una vez que hayas ingresado los datos de un usuario de prueba, el formulario se verá de la siguiente manera:

Procesando pago PayPal

Y una vez que termine de cargar, en nuestra consola nos encontraremos con los objetos que hemos decidido imprimir:

Datos mostrados desde onApprove

Si bien todos los datos son importantes, el que necesitamos principalmente es el ID de la orden. Ya que de esta manera vamos a poder capturar el pago (hasta este punto el estado es Approved).

Si tienes dudas de cómo completar estos datos, puedes ver el video que tengo publicado en YouTube, respecto a este tema, donde seguimos cada uno de los pasos:

Si bien el pago podría ser capturado de lado del cliente, en nuestro caso vamos a implementarlo en nuestro backend.

Allí mismo vamos a registrar en nuestra base de datos la información de cada orden exitosa.

Paso 5: Validar y capturar pago server-side

Hasta el paso anterior hemos visto únicamente implementación de lado del cliente.

Ahora sí, finalmente, veremos código backend.

Debemos tener una ruta para atender las órdenes aprobadas por nuestros usuarios, a fin de capturar tales pagos.

Route::get('/paypal/process/{orderId}', 'PayPalCardController@process')->name('paypal.process');

Esta ruta tiene un parámetro de ruta orderId, que usaremos desde nuestro controlador para capturar el pago y así mismo registrarlo en nuestra base de datos.

Así, en nuestro controlador tendremos un método process encargado de procesar el pago.

El método estará definido de esta manera:

public function process($orderId, Request $request)
{
    $accessToken = $this->getAccessToken();

    $requestUrl = "/v2/checkout/orders/$orderId/capture";

    $response = $this->client->request('POST', $requestUrl, [
        'headers' => [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer $accessToken"
        ]
    ]);

    $data = json_decode($response->getBody(), true);

    dd($data);
    // ...
}
  • Aquí primero obtenemos un accessToken.
  • Luego usamos este token y hacemos una petición POST a la URL que nos permitirá hacer capture de la orden.
  • Hacemos json_decode de la respuesta que nos da PayPal e imprimimos esta data usando la función dd de Laravel.

Hasta aquí la idea es que puedas analizar qué formato tiene la respuesta que estás recibiendo.

Una vez que tienes una respuesta de ejemplo, puedes hacer lo que necesites.

Procesando la respuesta de la captura

Luego de hacer una petición para capturar una orden (a partir de su id), lo siguiente es ejecutar código con nuestra lógica, según la respuesta que nos da PayPal.

Por ejemplo, para el proyecto que estoy implementando, he comentado el llamado a dd y he escrito lo siguiente:

if ($data['status'] === 'COMPLETED') {
    // Ubicar la solución del usuario
    $solutionId = $request->input('solution_id');
    $solution = Solution::findOrFail($solutionId);

    // Obtener el paymentId y el monto pagado, de $data
    $payPalPaymentId = $data['purchase_units'][0]['payments']['captures'][0]['id'];
    $amount = $data['purchase_units'][0]['payments']['captures'][0]['amount']['value'];

    // Registrar un pago exitoso en la BD
    $payment = $this->registerSuccessfulPayment($solution, $amount, $payPalPaymentId);

    // Dar una respuesta de error si el pago no se pudo registrar
    if (!$payment) {
        return $this->responseFailure();
    }

    // Dar una respuesta de éxito si todo salió bien
    return [
        'success' => true,
        'url' => $solution->getResultsLink(),
        'payment_id' => $payment->id,
        'amount' => $amount
    ];
}

// Dar una respuesta de error si el status no es COMPLETED
return $this->responseFailure();

En este caso hablo de una solución, ya que el usuario ha resuelto un test y está pagando para ver sus resultados.

En tu proyecto podría tratarse de un modelo Product, Item, u otro, según lo que estés implementando.

Lo importante aquí es entender que desde $data puedes acceder:

  • al monto pagado por el usuario,
  • y así mismo al id del pago realizado a través de PayPal.

Es importante que guardes estos valores en tu base de datos, por si en algún momento quieres contrastar los registros de tu base de datos con PayPal.

Importante:

  • Como puedes ver, al final estoy devolviendo un arreglo.
  • Laravel se encargará de dar una respuesta JSON con esta información.
  • En este objeto JSON estoy incluyendo una URL, que es a donde quiero redirigir al usuario.

Cómo obtener el accessToken

En el ejemplo anterior estoy invocando al método de clase getAccessToken().

Éste método lo he definido en el mismo controlador de la siguiente manera:

private function getAccessToken()
{
    $response = $this->client->request('POST', '/v1/oauth2/token', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
            'body' => 'grant_type=client_credentials',
            'auth' => [
                $this->clientId, $this->secret, 'basic'
            ]
        ]
    );

    $data = json_decode($response->getBody(), true);
    return $data['access_token'];
}

Como puedes ver, estamos haciendo una petición POST al endpoint /v1/oauth2/token, enviando nuestro clientId y secret, para obtener un accessToken como respuesta.

Las peticiones HTTP las puedes hacer usando un cliente de Guzzle, que puedes definir en el constructor, donde además accedemos a nuestras credenciales de PayPal.

private $client;
private $clientId;
private $secret;

public function __construct()
{
    $this->client = new Client([
        // 'base_uri' => 'https://api-m.paypal.com'
        'base_uri' => 'https://api-m.sandbox.paypal.com'
    ]);

    $this->clientId = env('PAYPAL_CLIENT_ID');
    $this->secret = env('PAYPAL_SECRET');
}

El método registerSuccessfulPayment para el proyecto que estoy haciendo consiste en registrar un pago en la base de datos, y actualizar la columna payment_id para la solución del usuario que está pagando por sus resultados.

En tu caso tendrás que hacer lo equivalente, según los requerimientos de tu proyecto.

Bonus: Redirigir al usuario luego de un pago exitoso

Luego de capturar un pago puedes hacer muchas cosas.

Por ejemplo:

  • Enviar un correo al usuario.
  • Actualizar la página que está viendo con un mensaje de felicitación / agradecimiento.
  • O simplemente redirigirlo a otra página, a la que aún no tenía acceso pero ahora sí.

Esto lo puedes hacer en el método onApprove.

¿Recuerdas que lo definimos en un inicio sólo para imprimir el valor de data y actions?

Es momento de actualizarlo, para actuar frente a la respuesta que devuelve nuestro backend.

Para el proyecto que he desarrollado lo tengo de la siguiente manera:

return fetch('/paypal/process/' + data.orderID + '?solution_id=' + {{ $solution->id }})
    .then(res => res.json())
    .then(function(response) {

        // Show a failure message
        if (!response.success) {
            const failureMessage = 'Sorry, your transaction could not be processed.';
            alert(failureMessage);                        
            return;
        }

        processSuccessfulPayment(response);
    });

Aquí:

  • Estoy haciendo una petición a la ruta que definimos hace un momento.
  • Le estoy enviando el id de la orden, que es lo que necesita nuestro controlador.
  • Y así mismo el id de la solución por la que está pagando el usuario.

Entonces:

  • Si la respuesta NO es exitosa, le mostramos un mensaje de error al usuario.
  • Si la respuesta tuvo éxito, procesamos la respuesta que nos devuelve nuestro backend.

En mi caso, la función processSuccessfulPayment simplemente:

  • envía un evento a Google Analytics (que me sirve para hacer un seguimiento de las conversiones), y luego,
  • oculta la sección de pago y muestra un ícono de "cargando", para finalmente,
  • redirigir al usuario a la URL que nos indica el backend.
sendPurchaseEvent(response.payment_id, response.amount);

document.getElementById('sectionPayments').classList.add('hidden');
document.getElementById('loading').classList.remove('hidden');            

setTimeout(function () {
    location.href = response.url;
}, 200);

Siéntete libre de actualizar este código de ejemplo según tus necesidades.

Conclusión

Generalmente, integrar pagos online se considera como uno de los temas más avanzados en cuanto a desarrollo de software.

Sin embargo, como puedes ver, una solución básica como la que hemos visto en este tutorial no es tan complicada de implementar.

Si esta guía te ha sido de ayuda y/o te gustaría aprender más sobre Laravel y Javascript, te invito a seguir mis cursos, que aparecen aquí en la parte inferior.

# paypal # laravel # javascript

Logo de Programación y más

Comparte este post si te fue de ayuda 🙂.

Regístrate

Accede a todos los cursos, y resuelve todas tus dudas.

Cursos Recomendados

Imagen para el curso Aprende Laravel

Aprende Laravel

Aprende Laravel desde cero y desarrolla aplicaciones web reales, en tiempo récord, de la mano de Laravel.

Iniciar curso
Imagen para el curso Laravel Upgrade

Laravel Upgrade

Actualiza tus proyectos desde cualquier versión hasta la última versión estable de Laravel.

Iniciar curso
Imagen para el curso Laravel y Vue

Laravel y Vue

Desarrollemos un Messenger! Aprende sobre Channels, Queues, Vuex, JWT, Sesiones, BootstrapVue y mucho más.

Iniciar curso

Espera un momento ...

¿Te gustaría llevar mi curso de Laravel, gratis?

Sólo debes ingresar tus datos: