Tutoriales

En este tutorial, implementaremos una simple aplicación de una tienda donde integraremos pagos online mediante la API de PayPal.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

¿Qué vamos a construir?

Crearemos una simple aplicación de una tienda donde se hará la compra de un libro, posteriormente se realiza el pago mediante la API de PayPal, al finalizar el pago, si se realiza de manera correcta se tendrá acceso a la descarga del libro en digital.

Usaremos AdonisJS 4.1 en este tutorial y a continuación se muestra la tabla de contenido de cómo se desarrollará la aplicación final:

Tabla de contenido

Requerimientos

Este tutorial asume que tienes lo siguiente instalado en tu computadora:

node >= 8.0 o mayor

$ node --version

npm >= 5.0 o mayor

$ npm --version

Adicional a esto se requerirán las credenciales: (CLIENT_ID y CLIENT_SECRET) de una cuenta de Paypal Sandbox. La cual se utilizará para realizar pruebas y todo el dinero que se mueve sea ficticio. No hay que especificar ni tarjetas de crédito ni nada por el estilo, todo será ficticio, de pruebas. Como si pagaras en tu tienda con billetes del Monopoly.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Instalación de Adonis CLI

Primero necesitamos instalar la Adonis CLI que nos ayudará a crear nuevas aplicaciones de AdonisJS:

$ npm i -g adonisjs-cli

Crear nuevo proyecto

Comenzaremos creando una nueva aplicación de AdonisJS. Haremos uso de Adonis CLI.

$ adonis new adonisjs-paypal #Creamos la aplicación

El comando anterior creará una nueva aplicación de AdonisJS con el nombre adonisjs-paypal utilizando la plantilla de la aplicación fullstack. Para asegurarnos de que todo funcione como se esperaba, ejecutemos la aplicación recién creada. Primero, ingresamos a la carpeta adonisjs-paypal y ejecutamos el siguiente comando:

$ cd adonisjs-paypal #Ingresar al proyecto
$ adonis serve --dev #Ejecutamos la aplicación

#info: serving app on http://127.0.0.1:3333

Abrimos http://localhost:3333 en el navegador para ver la página de bienvenida.

¡Bien! Ahora comencemos a desarrollar la aplicación.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Creando el controlador ShopController

Utilizaremos de momento solo un controlador principal llamado ShopController. Usaremos el comando adonis make:controller Shop de Adonis CLI para crealo:

$ adonis make:controller Shop

Cuando se le solicite, elija la opción For HTTP requests y presione Enter. Ahora tenemos un archivo llamado ShopController.js dentro del directorio app/Controllers/Http.

El controlador ShopController tendrá 4 métodos: index(), paySuccess(), payError() y , download(), además de un objeto llamado book, el cual describirá los datos del libro principal a comprar en nuestra tienda. Abra el archivo ShopController.js y agregue el siguiente código:


//app/Controllers/Http/ShopController.js
'use strict'

// Definimos el objeto "book" como variable global
const book = {
  sku: 'P001',
  title: 'Build Apps with Adonis.JS',
  image: 'http://www.victorvr.com/img/resources/Book-P001.png',
  description: 'Building Node.JS Applications with Javascript.',
  author: 'Victor Valencia Rico',  
  price: 5,
  currency: 'USD'
}

class ShopController {

  // Desplegará el producto principal
  async index ({ view, request }) {
    const paymentId = request.input('paymentId')
    return view.render('index', {book: book, paymentId: paymentId} )
  }    

  // Desplegará la notificación de un pago exitoso
  async paySuccess ({ request, response, session }) {
    const paymentId = request.input('paymentId')
    session.flash({ 
      paymentId: paymentId,
      notification_class: 'alert-success', 
      notification_icon: 'fa-check',
      notification_message: 'Thanks for you purchase! ' + paymentId
    })
    response.redirect('/?paymentId=' + paymentId);
  }
  
  // Desplegará la notificación de un pago fallido o de otros errores
  async payError ({ request, response, session }) {
    const name = request.input('name')
    const message = request.input('message')
    session.flash({ 
      notification_class: 'alert-danger', 
      notification_icon: 'fa-times-circle',
      notification_message: 'Payment error! ' + name + ': ' + message
    })
    response.redirect('/');
  }

  // Desplegará mensaje de descarga
  async download () {
    return 'Download File...'
  }

}

module.exports = ShopController
  

El método index() simplemente desplegará el libro principal de la tienda mediante la vista index (la cual crearemos previamente). El método paySuccess() solo recibe el parámetro paymentId y guarda en la variable session los datos de la notificación exitosa a desplegar al redireccionar a la ruta principal. El método payError() recibe 2 parámetros: name y message para asignarlos a la variable session de acuerdo a la notificación fallida a desplegar al redireccionar a la ruta principal. Por último, el método donwload() simplemente desplegará un simple mensaje

Creando las rutas de la aplicación

Abra el archivo start/routes.js y lo actualizamos como a continuación:


//start/routes.js
...
Route.get('/', 'ShopController.index').as('book.index')
Route.get('/pay/success', 'ShopController.paySuccess').as('pay.success')
Route.get('/pay/error', 'ShopController.payError').as('pay.error')
Route.get('/download', 'ShopController.download').as('book.download')
...
  

Las rutas definidas nos servirán para otorgarle a la aplicación el comportamiento inicial. Es muy importante definirles a cada una de ellas y su alias, ya que este alias nos servirá para hacer referencia a nuestras rutas en la vista principal.

Creando la vista principal

Vamos a crear una sola vista llamada index para nuestra aplicación. Todos los archivos de las vistas deben estar dentro del directorio resources/views. Entonces, dentro del directorio, crearemos una nueva vista y le asignamos el nombre index.edge. Abra el archivo recién creado y pegue el siguiente código:

  
  

Usaremos el framework CSS llamado Bootstrap y la librería de iconos FontAwesome, además de la función global style() de AdonisJS para hacer referencia a nuestros estilos .css en CDN.

Hasta este punto, simplemente hemos realizado la base de la aplicación. Cuando se presione el botón Buy for only $5 USD (Button - Buy), se dará por hecho que se ha realizado el pago y mostrará la nueva vista para descargar el libro comprado, Además, cuando se presione el botón Download PDF (Button - Download), iniciará la descarga del libro comprado en digital. Si visitamos la aplicación en el navegador, deberíamos obtener algo similar a la siguiente imagen:

Instalación del SDK de PayPal

Para continuar, instalaremos el SDK de Paypal el cual nos permitirá procesar los pagos, Entonces, necesitamos instalar el controlador Node.js para el SDK de PayPal.

$ npm install paypal-rest-sdk --save #Instalamos el SDK de PayPal

Después de instalar el SDK, debemos ponerlo a disposición de la aplicación y configurar algunas variables de entorno. La configuración del SDK de PayPal incluye: mode, que es sandbox para pruebas o en live para producción, y sus credenciales client_id y client_secret.


var paypal = require('paypal-rest-sdk');

paypal.configure({
  mode: 'sandbox', // sandbox | live
  client_id: {YOUR_CLIENT_ID},
  client_secret: {YOUR_CLIENT_SECRET}
});
  
Wheater Dasboard: Angular + OpenWeather
Publicidad
Wheater Dasboard: Angular + OpenWeather by Victor Valencia Rico

Creando archivo de configuración

Para tener nuestra aplicación estructurada, crearemos un nuevo archivo de configuración llamado paypal.js dentro de la carpeta config, donde concentraremos nuestra variables globales de PayPal. Entonces, abra el archivo paypal.js y agregue el siguiente código:


'use strict'
const Env = use('Env')

module.exports = {
  configure: {
    mode: Env.get('PAYPAL_MODE', 'sandbox'),
    client_id: Env.get('PAYPAL_CLIENT_ID'),
    client_secret: Env.get('PAYPAL_CLIENT_SECRET')
  },
  url_success: Env.get('APP_URL') + "/pay/success",
  url_error: Env.get('APP_URL') + "/pay/error"
}
  

Este archivo de configuración leerá e inicializará nuestras variables de entorno: PAYPAL_MODE, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET y APP_URL mediante el archivo .env. Ahi será donde las podremos definir. A continuación, configuramos las variables de entorno ingresando a nuestra configuración en el archivo .env. Entonces, abra el archivo .env y agregue las siguientes líneas:

//.env
... 
PAYPAL_MODE=sandbox // sandbox | live
PAYPAL_CLIENT_ID={YOUR_CLIENT_ID}
PAYPAL_CLIENT_SECRET={YOUR_CLIENT_SECRET}
...
  

Recuerde actualizar sus credenciales con las suyas y además recuerde que utilizaremos el modo sandbox para realizar todas nuestras transacciones en modo de pruebas. Realizada esta configuración ahora podremos acceder a ella desde cualquiera de nuestros controladores.

Creando el controlador PaypalController

Para el uso esclusivo de la API de Paypal, crearemos un nuevo controlador llamado PaypalController. Usaremos el comando adonis make:controller Paypal de Adonis CLI para crealo:

$ adonis make:controller Paypal

Cuando se le solicite, elija la opción For HTTP requests y presione Enter. Ahora tenemos un archivo llamado PaypalController.js dentro del directorio app/Controllers/Http.

El controlador PaypalController tendrá 4 métodos: getSuccessURL(), getErrorURL(), createPay() y getPay(), que nos ayudarán a interactuar con la API de PayPal. Abra el archivo PaypalController.js y agregue el siguiente código:


//app/Controllers/Http/PaypalController.js
'use strict'

const Config = use('Config')
const Paypal = use('paypal-rest-sdk')
// Configuramos el SDK de PayPal con nuestra configuración
Paypal.configure(Config.get('paypal.configure'));

class PaypalController {

  // Returna la URL para procesar un pago exitoso
  getSuccessURL () {
    return Config.get('paypal.url_success')
  }

  // Returna la URL para procesar un pago fallido u otros errores
  getErrorURL () {
    return Config.get('paypal.url_error')
  }

  // Función "Promise" para crear pagos en la API de PayPal.
  createPay ( payment ) {
    return new Promise( ( resolve, reject ) => {
      Paypal.payment.create( payment, function( err, payment ) {
        if ( err ) {
          reject(err); 
        }
        else {
          resolve(payment); 
        }
      }); 
    });
  }

  // Función "Promise" para obtener un pago en la API de PayPal.  
  getPay ( paymentId ) {
    return new Promise( ( resolve, reject ) => {
      Paypal.payment.get( paymentId, function( err, payment ) {
        if ( err ) {
          reject(err); 
        }
        else {
          resolve(payment); 
        }
      }); 
    });
  }

}

module.exports = PaypalController
  

Los métodos getSuccessURL() y getErrorURL() retornan la URL para procesar un pago exitoso o fallido, según sea el caso. Los otros métodos createPay() y getPay() retornan funciones Promise para crear y obtener pagos en la API de PayPal.

Las funcines Promise o "Promesas" nos facilitan mucho el control de flujos de datos asíncronos en una aplicación, además las promesas son la base para luego poder implementar características más avanzadas de JavaScript como async/await que nos facilitan aún más nuestro código.

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Crear pago

Ahora ligaremos nuestra aplicación para poder realizar el pago del libro dentro de la API de PayPal. Modificamos el controlador ShopController.js en el cual haremos referencia al controlador PaypalController.js y crearemos una nueva instancia de este controlador. Tambien se agregará un nuevo método llamado tryPay(), el cúal usaremos para realizar el pago en la API de PayPal. Este método definirá el pago dentro de la variable payment. Esta variable será enviada al método createPay() del controlador PaypalController.js. Utilizaremos el prefijo async al llamar el método createPay() ya que este es del tipo Promise. Abra el archivo ShopController.js y agregue el siguiente código:


//app/Controllers/Http/ShopController.js
// Recuerde referenciar el controlador PaypalController en la parte de arriba
const PaypalController = use('App/Controllers/Http/PaypalController')
const Paypal = new PaypalController()
...
// Método para realizar el pago en la API de PayPal.
async tryPay({ response }) {    
  const success_url = Paypal.getSuccessURL()
  const error_url = Paypal.getErrorURL()
  // Crear el objecto payment
  var payment = {
    "intent": "authorize",
    "payer": {
      "payment_method": "paypal"
    },
    "redirect_urls": {
      "return_url": success_url,
      "cancel_url": error_url
    },
    "transactions": [{
      "item_list": {
        "items": [{
          "name": book.title,
          "sku": book.sku,
          "price": book.price,
          "currency": book.currency,
          "quantity": 1
        }]
      },
      "amount": {
        "total": book.price,
        "currency": book.currency
      },
      "description": book.sku + ': ' + book.title
    }]
  }         
  await Paypal.createPay( payment ) 
    // Indica que el pago fue exitoso
    .then( ( transaction ) => {                         
      var links = transaction.links;
      var counter = links.length; 
      while( counter -- ) {
        if ( links[counter].method == 'REDIRECT') {
          // Redirige a PayPal donde el usuario aprueba la transacción
          return response.redirect( links[counter].href )
        }
      }
    })
    // Indica que el pago fue fallido
    .catch( ( err ) => { 
      var details = err.response
      if(err.response.httpStatusCode == 401) {
        return response.redirect(error_url + '?name=' + details.error + '&message=' + details.error_description);
      }
      else {
        return response.redirect(error_url + '?name=' + details.name + '&message=' + details.message);
      }        
    });
}
...
  

El objeto payment de la API de PayPal define la forma de pago a PayPal, los detalles de la transacción, la URL a la que redirige al cliente después de que el cliente acepta o cancela el pago de PayPal y además de otra información.

No olvidemos agregar una nueva ruta al archivo start/routes.js donde enlacemos el metodo tryPay() del controlador ShopController.js y lo actualizamos como a continuación:


//start/routes.js
...
Route.get('/pay/try', 'ShopController.tryPay').as('book.pay')
...
  

Para terminar, modificamos la vista donde solo remplazaremos el atributo href del boton para pagar (Button - Buy), reemplazando route('book.index') por la nueva ruta agregada route('book.pay') y eliminamos el parametro ficticio ?paymentId=PAYID-XYZ

  
  

Hasta este punto, podremos realizar el pago de nuestro libro en la API de PayPal. Cuando se presione el botón Buy for only $5 USD (Button - Buy), nos redireccionará a la página de PayPal, donde tendremos que ingresar los datos de inicio de sessión de nuestro usuario de prueba. Una vez ingresado, en el carrito de compras de PayPal nos mostrará la descripción y el precio de nuestra compra. Posteriormente presionamos el botón Continuar para confirmar nuestra compra. Una vez autorizada la compra la página de PayPal nos redireccionará a nuestra tienda, donde desplegará la notificación de la compra exitosa y habilitará la descarga de nuestro libro adquirido, deberíamos obtener algo similar a la siguiente imagen:

Verificar pago

Para teminar con la aplicación, modificaremos por último el controlador ShopController en el método download(). Abra el archivo ShopController.js y agregue el siguiente código:


//app/Controllers/Http/ShopController.js
// Recuerde referenciar la librería Helpers en la parte de arriba
const Helpers = use('Helpers')
...
// Verificará el pago antes de iniciar la descarga
async download ({ response, request }) {
  const paymentId = request.input('paymentId')
  await Paypal.getPay( paymentId ) 
    // Indica que el pago existe
    .then( ( payment ) => {
      const item = payment.transactions[0].item_list.items[0]
      //Descargar Libro
      const name = item.sku + ' - ' + item.name + '.pdf'
      const source = Helpers.resourcesPath('/files/Book-' + item.sku + '.pdf')
      response.attachment(source, name)
    })
    // Indica que el pago no existe
    .catch( ( err ) => { 
      //Mostrar Error
      var details = err.response
      response.send('ERROR: ' + details.name + ' => ' + details.message)
    });
}
...
  

Ya que el método download() recibe el párametro paymentId, verificaremos a través del método getPay() del controlador PaypalController.js, si es un pago existente a través de la API de PayPal. De ser así permitiremos la descarga del libro en digital. De lo contrario solo mostrará un mensaje con el error correspondiente.

Si visitamos la aplicación en el navegador, deberíamos obtener el siguiente resultado con un parámetro paymentId existente y uno ficticio:

Angular App Tool: Digitalize polygons
Publicidad
Angular App Tool: Digitalize polygons by Victor Valencia Rico

Conclusión

Antes de concluir, veamos el resultado final:

Ahora si, hemos terminado con nuestra aplicación, han visto lo fácil que es implementar los pagos online a través del API de PayPal utilizando AdonisJS. Esta aplicación es básica e incluso se pueden agregar mas funcionalidades, tales como agregar varios libros en una sola compra, guardar los datos de las compras realizadas en una base de datos, imprimir la factura de una compra, etc. Estas actividades se las dejo a su imaginación y criterio para tener una aplicación más completa.