En este tutorial, implementaremos una simple aplicación de una tienda donde integraremos pagos online mediante la API de PayPal.
Angular App: Todo List 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 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.
Wheater Dasboard: Angular + OpenWeather 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.
Angular App Tool: Digitalize polygons
by Victor Valencia Rico
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}
});
Angular App Tool: Digitalize polygons 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: Todo List 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 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.