¿Cómo integrar Paypal a tu web? | CourseIt Blog
logo courseit

¿Cómo integrar Paypal a tu web?

30

Hace unas semanas, tuvimos que implementar pagos únicos de Paypal en Courseit. Fue más dificil de lo que pensábamos, porque si bien Paypal ofrece un botón de pago muy bueno, debido a nuestro flujo de tiempo de suscripción, es necesario tenerlo integrado con un backend, donde podamos modificar a los usuarios de una manera más segura. Es por todo esto, que decidimos hacer una integración directa con la api de Paypal, y acá es cuando empezaron los problemas con la documentación.

La documentación de Paypal suele ser muy intuitiva, fácil de comprender, y fácil de probar, pero, debido a cómo manejan los pagos únicos, tuvimos dificultades.

Ahora bien, en este blog, les voy a mostrar paso a paso cómo replicar una integración con la api de paypal, utilizando express. La integración va a consistir en crear un link de pago, con la información correspondiente a la compra, capturar el pago, y recibir las notificaciones webhooks correspondientes.

Empecemos.


1. REQUISITOS

  1. Conocimientos de Javascript
  2. Tener instalado node
  3. Saber manejar la terminal
  4. Ganas de Aprender!
  5. Tener una cuenta creada de Paypal

2. GENERAR CREDENCIALES DE PAYPAL

Debemos ingresar al dashboard de desarrollador de paypal. Una vez dentro, tenemos que asegurarnos de estar parados sobre sandbox.

sandbox

Luego, tenemos que crear una nueva Aplicación. Lo hacemos clickeando en el botón create app

create

Luego, completamos el formulario como se muestra en la imágen, utilizando un nombre acorde.

Una vez tengamos creada la aplicación nos va a redirigir a la siguiente página, donde vamos a poder ver un email, el client ID, y el client Secret

secret

Otro detalle importante es este campo, que es la url de redirección luego de la transacción. Por ahora dejemoslo en blanco, pero sepan que si eventualmente van a llevar esto a producción, van a necesitar configurarlo


3. CREAR APLICACIÓN EXPRESS

Vamos a abrir la terminal, crear una carpeta que se llama paypal-api, o como ustedes deseen, y parados dentro de esa carpeta ejecutar el código:

npx express-generator --no-view

Una vez terminado, vamos a ejecutar el comando

npm install

También vamos a necesitar axios para hacer los llamados a la api de Paypal, asi que para instalarlo ejecutamos:

npm install axios

Y por último vamos a abrirlo en nuestro editor preferido, en mi caso visual studio code.

4. LIMPIANDO EL GENERADOR DE EXPRESS

Ni bien tengamos abierto el proyecto en visual studio code, vamos a ir al archivo app.js, y pasar todas las var a const. Vamos a hacer exactamente lo mismo en los archivos /routes/index.js y /routes/users.js

Adicionalmente, vamos a eliminar la carpeta public y todo su contenido.

5. Creando el Controller

El controller, es una clase de javascript, que vamos a instanciar, para poder utilizar las funciones que se encuentran en dicha clase. Su propósito va a ser manejar los request y las responses de manera eficiente, y ordenada.

Para eso, vamos a crear al mismo nivel que la carpeta routes una carpeta que se llame controllers, dentro de esa carpeta vamos a crear un archivo que se llame PaypalController.js

Dentro del controller, vamos a crear una clase que se llame PaypalController, que va a tener un constructor, y eventualmente 4 funciones.

const axios = require("axios");
//importamos axios para hacer las llamadas a la api de Paypal
const querystring = require("querystring");
//lo vamos a necesitar para pedir el token, no es necesario instalarlo

class PaypalController {
  constructor() {
    this.Paypal = {
    url: "https://api.sandbox.paypal.com",
    //usamos sandbox porque nuestro clientId y clientSecret son de sandbox
    clientId: "clientId" //acá va su client ID,
    clientSecret: "clientSecret" //acá va su client Secret
    //acá vamos a guardar de una manera muy insegura las keys de paypal (lo ideal es guardarlas en .env, y llamarlas donde sea indicado)
    };
  }

  async getPaymentLink(req, res) {
    //esta función va a devolver el link de pago
  }

  async generateLink({ price }) {
    //esta función va a generar el link de pago
  }

  generateToken() {
    //esta función va a generar el token para poder hacer los request a Paypal
  }

  async webhook(req, res) {
    //esta función va a recibir los webhooks, solamente los vamos a poder probar si tenemos nuestra api deployada
  }
}

module.exports = PaypalController;
//exportamos la clase

6. GENERAR EL TOKEN DE PAYPAL

Dentro de nuestra función generateToken() que se encuentra dentro de nuestro Controller. Vamos a hacer un request a una url

Esa url es la siguiente: ${this.Paypal.url}/v1/oauth2/token

Estamos usando this, para acceder al entorno de la clase, y luego hacemos this.Paypal, para acceder al objeto que declaramos en nuestro constructor, y por último this.Paypal.url para acceder a la url de la api de Paypal.

Luego, tenemos que hacer un request del tipo POST, y envíar en el body un objeto. Este objeto, contiene las preferencias que vamos a utilizar para generar el token, algunas son requeridas por Paypal, para poder autenticarnos y obtener el token.

Como vamos a utilizar axios, para hacer este request POST, tenemos que aclararle el tipo de request, y el tipo de información que estamos enviando, por eso creamos el objeto settings. En síntesis, el request, quedaría de la siguiente manera

    const settings = {
      method: "POST",
      //aclaramos que vamos a hacer un POST
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      //aclaramos en los headers el tipo de contenido a enviar
      data: querystring.stringify({ grant_type: "client_credentials" }),
      //mandamos en el body el grant_type, que es algo requerido por paypal
      auth: {
        username: this.Paypal.clientId,
        password: this.Paypal.clientSecret
      },
      //enviamos el objeto auth, que contiene el clientId y el clientSecret
      url: `${this.Paypal.url}/v1/oauth2/token`
      //determinamos la url a dónde lo queremos enviar
    };

    return axios(settings).then((response) => {
      //esperamos a que se complete el request, y devolvemos la información requerida
      return response.data;
    });

Este token de paypal, lo vamos a necesitar para generar el link de pago, que es lo que vamos a hacer a continuación

7. GENERAR EL LINK DE PAGO

Dentro de nuestra función generateLink() vamos a generar el link de pago. Para hacerlo, tenemos que decirle a paypal determinada información, entre esas cosas se encuentran: el precio y la moneda de pago.

  async generateLink({ price }) {
    const url = `${this.Paypal.url}/v2/checkout/orders`;
    //determinamos la url
    const data = await this.generateToken();
    //generamos el token
    const access_token = await data.access_token;
    //leemos el access_token

    const settings = {
      method: "POST",
      //hacemos un POST
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${access_token}`
      },
      //en el header mandamos el TOKEN
      data: {
        //enviamos la información requerida por PAYPAL
        intent: "CAPTURE",
        purchase_units: [
          //este es un array de artículos, con su respectivo precio
          {
            amount: {
              currency_code: "USD",
              //la moneda a utilizar
              value: price.toString()
              //el precio, que deber ser un string
            }
          }
        ],
        application_context: {
          //información adicional sobre cómo queremos que sea el checkout
          brand_name: "Marca",
          //el nombre de la marca que va a aparecer cuando el usuario intente comprar
          locale: "es-ES",
          //el idioma que va a intentar a utilizar en el checkout
          user_action: "PAY_NOW",
          //la acción que va a realizar el usuario, generalmente siempre queremos PAY_NOW
          landing_page: "NO_PREFERENCE",
          //esto es por si lo queremos enviar a un flujo puntual de paypal, por ejemplo al login, a pagar, o a otro lado.
          payment_method: {
            //esta es la información que limita los métodos de pago
            payer_selected: "PAYPAL",
            payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED"
          },
          shipping_preference: "NO_SHIPPING",
          //aclaramos que no vamos a relizar un envio, ya que es un servicio en este ejemplo
          return_url: ""
          //tenemos la opción de ingresar la url a la cual será redirigido el usuario una vez que la transacción sea completada
        }
      },
      url
      //el endpoint de la api de Payapal
    };

    return axios(settings).then(async (response) => {
      // suelen venir muchos links en la respuesta, es por eso que tenemos que hacer un find para encontrar el que necesitamos
      if (response && response.data && response.data.links) {
        const link = response.data.links.find((link) => {
          return link.rel == "approve";
          //el que necesitamos viene con un rel: "approve"
        });
        //retornamos el link que tiene rel: "approve"
        return { link: link.href };
      }
      return;
    });
  }

8. RECIBIR EL REQUEST Y ENVÍAR EN LA RESPONSE EL LINK DE PAGO

Muy bien, ya estamos terminando con la generación del link. Lo que tenemos que hacer ahora es escuchar los request, y enviar la response. Para eso en nuestra función getPaymentLink(req, res)

  async getPaymentLink(req, res) {
    try {
      const payment = await this.generateLink({ price: 100 });
      //ejecutamos la función de generarl el link, pasandole por param, el precio, esto es para simular un POST a nuestra api
      return res.json({
        linkPaypal: payment.link
      });
      //retornamos el link
    } catch (e) {
      //si pasa algo mal, devolvemos un status 500, y un mensaje de error
      return res.sendStatus(500).json({
        error: true,
        message: "Hubo un error al general el link de pago"
      });
    }
  }

9. WEBHOOKS

Hasta el paso 8, hicimos toda la generación del link de pago, pero hay un problema. Paypal no solamente requiere que el usuario pague, si no también que nosotros, capturemos el pago, ¿cómo hacemos eso? con los webhooks.

Lo que vamos a tener que hacer es esperar a recibir una notificación webhook donde el valor de event_type sea CHECKOUT.ORDER.APPROVED

Una vez que sabemos que la orden está aprobada, podemos pasar a capturarla. Para capturarla tenemos que hacer un request del tipo POST al endpoint siguiente:

`${this.Paypal.url}/v2/checkout/orders/${req.body.resource.id}/capture

Donde resourse.id, es el id de la orden que estamos tratando de capturar.

Entonces, sabiendo esto, nuestra función webhook() queda de la siguiente manera:

  async webhook(req, res) {
    if (req.body.event_type == "CHECKOUT.ORDER.APPROVED") {
      //verificamos que el tipo de webhook sea CHECKOUT.ORDER.APPROVED
      const data = await this.paypalService.getToken();
      const access_token = await data.access_token;
      //generamos nuevo token

      //tenemos que capturar la orden para cobrarla
      await axios({
        method: "POST",
        //hacemos un post
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${access_token}`
          //enviamos el token
        },
        url: `${this.Paypal.url}/v2/checkout/orders/${req.body.resource.id}/capture`
        //esta url contiene la orden que estamos tratando de capturar
      });
    }
    return res.status(200);
    //este return es obligatorio para hacerle saber a Paypal que se recibió la notificación que nos envió
  }

10. CREAR LA RUTA

Antes que nada, necesitamos un endpoint en nuestra api para poder ejecutar la función que genera el link de pago. Asi como también otra ruta que permita que paypal nos envíe las notificaciones webhooks.

Por estas razones, vamos a crear dentro de la carpeta routes un archivo que se llame paypal.js

Dentro de ese archivo paypal.js vamos a insertar lo siguiente:

const express = require("express");
const router = express.Router();

const PaypalController = require("../controllers/PaypalController");
//importamos el Controller
const PaypalInstance = new PaypalController();
//instanciamos la clase que esta en el controller

router.get("/", (req, res) => {
  //creamos la ruta, que en definitiva es /paypal/
  //ejecutamos la funcion getPaymentLink de nuestro controller
  PaypalInstance.getPaymentLink(req, res);
});

router.post("/webhook", (req, res) => {
  //tiene que ser una ruta tipo POST para recibir los webhooks
  //creamos la ruta, que en definitiva es /paypal/webhook
  //ejecutamos la funcion webhook que esta en nuestro controller
  PaypalInstance.webhook(req, res);
});

module.exports = router;

Y por último, vamos a modificar el archivo app.js para que todo funcione bien. Este archivo, debería quedar de la siguiente forma:

const express = require("express");
const path = require("path");
const cookieParser = require("cookie-parser");
const logger = require("morgan");

const paypalRouter = require("./routes/paypal");

const app = express();

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/paypal", paypalRouter);

module.exports = app;

Y eso es todo folks

ACLARACIONES

  • Toda la documentación de paypal la pueden encontrar acá
  • Si tienen dudas me pueden hablar al discord de CourseIt o por twitter
  • Si se les complica mucho, o quieren aprender más, les recomiendo que hagan nuestros cursos de backend.
  • Para probar los webhooks, van a tener que deployar de alguna manera. Si no quieren hacerlo pueden utilizar ngrok
  • La url de los webhooks la tienen que configurar en la página de su app, abajo de todo, hay un boton que dice add webhook. Recuerden de poner la url completa, más /paypal/webhook
  • Les dejo acá un respositorio público con este código, sin Client ID ni Client Secret

Gracias por leer!