La semana pasada, se me presentó un "examen" de certificación en el que tenía que integrar a una web el checkout de Mercado Pago (de acá en más MP, porque me voy a cansar de escribirlo).
Por suerte, ya había integrado MP en CourseIt (fue más un rework igual), por lo que tenía cierta experiencia.
Igualmente, ni bien me encontré con este desafío, tuve varios problemas, entre ellos que la documentación de mercado pago no es la más amigable, y me las tuve que rebuscar mucho para poder probar las cosas. (Si tienen problemas utilizando sandbox de MP avisen!)
Es por eso, que escribo este artículo, con la intención de ayudar a personas que tengan que hacer algo similar
El propósito de este artículo es que practicamente cualquier persona pueda seguir y entender su contenido, por lo que las explicaciónes intentan ser lo mas abarcativas posibles
Conocimientos Necesarios
Para poder seguir con facilidad este tutorial, van a necesitar lo siguiente:
- Conocimientos de Javascript
- Tener instalado node
- Saber usar el comando
cdde la terminal - Ganas de Aprender!
- Las API keys/tokens de MP, que las pueden encontrar acá
Lo que vamos a hacer
- Vamos a crear una API en Node, más puntualmente express.
- En dicha API, vamos a crear dos rutas, una para generar el link de pago, y otra para recibir las notificaciones de pago que MP nos mande (webhooks).
- Esas rutas van a utilizar un controller y un service. En los que vamos a tener que conectarnos con la API de MP.
Empecemos con el Set Up
-
Lo primero que vamos a hacer es instalar express-generator, que nos va a permitir, en el próximo paso, generar una aplicación de express:
$ npm install express-generator -g -
Lo segundo que vamos a hacer es crear una aplicación de express
$ express --view=none MercadoPagoCheckoutCon este comando lo que estamos haciendo es crear una API, que se llama MercadoPagoCheckout
-
Una vez que estemos en la carpeta MercadoPagoCheckout (que se acaba de crear) ejecutamos:
$ npm installEsto va a instalar los node_modules.
Dependiendo de su computadora, puede tardar poco o mucho, asi que tengan paciencia. -
También tenemos que instalar axios, que lo vamos a utilizar para hacer los request a la API de MP
$ npm install axios --save
Y con eso ya hicimos el setup básico de la API!
Creación de Rutas
Una vez que tengamos todo instalado, tenemos que abrir nuestro proyecto en nuestro editor de código preferido.
Dentro de los archivos, nos vamos a encontrar con uno que se llama app.js, que contiene lo siguiente:
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var 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('/', indexRouter);
app.use('/users', usersRouter);
module.exports = app;
Lo que tenemos que hacer a continuación es crear las rutas.
Tengan en cuenta que vamos a crear 2 rutas: una para generar el link de MP y otra para recibir las notificaciones(webhook) de MP
Vamos a eliminar algunas rutas que NO vamos a usar como:
app.use('/', indexRouter);
app.use('/users', usersRouter);
Y agregar las rutas que SI vamos a usar que son:
app.post("/payment/new", (req, res) =>
PaymentInstance.getMercadoPagoLink(req, res)
);
Donde vamos a llamar a la función getMercadoPagoLink() que va a estar en el controller que vamos a crear más adelante.
Esta función se va a ejecutar cuando se realice un request POST a la url localhost:3000/payment/new
app.post("/webhook", (req, res) => PaymentInstance.webhook(req, res));
Esta línea lo que hace es muy similar a la linea anterior.
Cuando se reciba un request POST en la url localhost:3000/webhook se va a ejecutar la función webhook() que está dentro del controller (que todavía no creamos)
Por último, importé el Controller y el Service que vamos a estar creando en los próximos pasos:
const PaymentController = require("./controllers/PaymentController");
//importamos el controller
const PaymentService = require("./services/PaymentService");
//importamos el service
const PaymentInstance = new PaymentController(new PaymentService());
// Permitimos que el controller pueda usar el service
Entonces, nos tendría que quedar así:
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var app = express();
const PaymentController = require("./controllers/PaymentController");
//importamos el controller
const PaymentService = require("./services/PaymentService");
//importamos el service
const PaymentInstance = new PaymentController(new PaymentService());
// Permitimos que el controller pueda usar el service
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.post("/payment/new", (req, res) =>
PaymentInstance.getMercadoPagoLink(req, res)
);
app.post("/webhook", (req, res) => PaymentInstance.webhook(req, res));
module.exports = app;
Creación del Controller
Un controller, es una función o conjunto de funciones que nos van a permitir ordenar la información que recibamos de bases de datos, o en nuestro caso, datos de una API externa.
Lo primero que vamos a hacer es crear, dentro de una carpeta que se llame controllers (creala si es necesario), un nuevo archivo que se llame PaymentController.js , y adentro, vamos a crear una clase que se llame PaymentController.
Dentro de esa clase vamos a crear dos funciones, una que se llame getMercadoPagoLink(), y otra que se llame webhook().
class PaymentController {
constructor(paymentService) {
this.paymentService = paymentService;
}
async getMercadoPagoLink(req, res) {
}
webhook(req, res) {
}
}
module.exports = PaymentController;
La función getMercadoPagoLink(), va a llamar al service (que estamos importando desde el constructor) y ejecutar la función createPaymentMercadoPago() que va a recibir información del producto o servicio que queramos vender, como por ejemplo:
nombre, precio, cantidad
async getMercadoPagoLink(req, res) {
const { name, price, unit, img } = req.body;
try {
const checkout = await this.paymentService.createPaymentMercadoPago(
name, // nombre del producto o servicio
price, //precio del producto o servicio
unit, //cantidad que estamos vendiendo
img // imagen de referencia del producto o servicio
);
return res.redirect(checkout.init_point);
//si es exitoso los llevamos a la url de Mercado Pago
return res.json({url: checkout.init_point})
// o si queres devolver la url al front
} catch (err) {
// si falla devolvemos un status 500
return res.status(500).json({
error: true,
msg: "Hubo un error con Mercado Pago"
});
}
}
La función tiene que ser declarada con un async, ya que vamos a estar haciendo un await a la función del service.
También vamos a leer, del body del request, la información que nos va a llegar del frontend(name, price, unit, img).
Una vez resuelta PaymentService.createPaymentMercadoPago(), vamos a tener la url de pago que nos da MP.
La función webhook(), en cambio, debería verse así:
webhook(req, res) {
if (req.method === "POST") {
let body = "";
req.on("data", chunk => {
body += chunk.toString();
});
req.on("end", () => {
console.log(body, "webhook response");
res.end("ok");
});
}
return res.status(200);
}
En la función webhook(), vamos a verificar que el método de request sea POST, ya que es requisito para recibir la información que nos va a mandar MP.
Luego, declaramos una variable que se llama body, que vamos a sobre escribir a medida que el request se vaya resolviendo, porque la información nos va a llegar en chunks, y necesitamos llevarla a algo que sea legible (string).
En síntesis, nuestro PaymentController, debería quedar:
class PaymentController {
constructor(paymentService) {
this.paymentService = paymentService;
}
async getMercadoPagoLink(req, res) {
const { name, price, unit, img } = req.query;
try {
const checkout = await this.paymentService.createPaymentMercadoPago(
name,
price,
unit,
img
);
return res.redirect(checkout.init_point);
} catch (err) {
return res.status(500).json({
error: true,
msg: "Hubo un error con Mercado Pago"
});
}
}
webhook(req, res) {
if (req.method === "POST") {
let body = "";
req.on("data", chunk => {
body += chunk.toString();
});
req.on("end", () => {
console.log(body, "webhook response");
res.end("ok");
});
}
return res.status(200);
}
}
module.exports = PaymentController;
Creación del Service
Un service, es una función o conjunto de funciones que nos permiten ejecutar un query en nuestra base de datos, o conectarnos a una API externa.
Lo primero que vamos a hacer es crear, dentro de la carpeta services (creala si no existe), un nuevo archivo que se llame PaymentService.js , y adentro, vamos a crear una clase que se llame PaymentService.
Vamos a necesitar un constructor también, que es donde vamos a guardar las keys/tokens de MP.
Y por último, tenemos que importar axios, que es la herramienta que vamos a usar para conectarnos a la API de MP.
const axios = require("axios");
class PaymentService {
constructor() {
this.tokensMercadoPago = {
prod: {},
test: {
access_token:
"APP_USR-6317427424180639-042414-47e969706991d3a442922b0702a0da44-469485398"
// el access_token de MP
}
};
// declaramos de la siguiente manera el token
// para que sea más fácil cambiarlo dependiendo del ambiente
this.mercadoPagoUrl = "https://api.mercadopago.com/checkout";
// declaramos la url en el constructor para poder accederla a lo largo de toda la class
}
}
Dentro de esa clase, vamos a crear una sola función llamada CreatePaymentMercadoPago().
En esta función async, vamos a recibir las variables que le enviamos desde la función getMercadoPagoLink() que está en PaymentController.js.
También, vamos a declarar una variable llamada url que es la dirección de MP a donde vamos a ir a pedir la información mediante el método POST. Utilizamos las variables declaradas en el constructor para formarla.
async createPaymentMercadoPago(name, price, unit, img) {
const url = `${this.mercadoPagoUrl}/preferences?access_token=${this.tokensMercadoPago.test.access_token}`;
}
Para continuar, vamos a crear dentro de la función createPaymentMercadoPago() un array de objetos[{}] llamado items.
Este Array, va a contener la información de los productos o servicios que estamos vendiendo.
También vamos a utilizar las variables name, unit, y price que nos estan llegando de la función getMercadoPagoLink() que está en PaymentController.js
const items = [
{
id: "1234",
// id interno (del negocio) del item
title: name,
// nombre que viene de la prop que recibe del controller
description: "Descripción del producto o servicio",
// descripción del producto
picture_url: "https://localhost:3000/static/product.png",
// url de la imágen del producto, tiene que ser una url válida
category_id: "1234",
// categoría interna del producto (del negocio)
quantity: parseInt(unit),
// cantidad que estamos vendiendo, que tiene que ser un intiger
currency_id: "ARS",
// id de la moneda, que tiene que ser en ISO 4217
unit_price: parseFloat(price)
// el precio, que por su complejidad tiene que ser tipo FLOAT
}, {
// si queremos agregar otro item, pasamos la misma información acá
}
];
Ahora, vamos a declarar un objeto llamado preferences, que contiene las preferencias de pago de nuestro array items.
Todo esto lo estamos haciendo de acuerdo a la documentación de la API de MP que pueden encontrar acá
const preferences = {
items,
// el array de objetos, items que declaramos más arriba
external_reference: "referencia del negocio",
// referencia para identificar la preferenciaç
payer: {
// información del comprador, si estan en producción tienen que traerlos del request
//(al igual que hicimos con el precio del item)
name: "Lalo",
surname: "Landa",
email: "test_user_63274575@testuser.com",
// si estan en sandbox, aca tienen que poner el email de SU usuario de prueba si estan
//en producción, deberian completar esta información
//de la misma manera que lo hicimos con items, units, y price
phone: {
area_code: "11",
number: "22223333"
},
address: {
zip_code: "1111",
street_name: "False",
street_number: "123"
}
},
payment_methods: {
// declaramos el método de pago y sus restricciones
excluded_payment_methods: [
// aca podemos excluir metodos de pagos, tengan en cuenta que es un array de objetos
// donde el id de cada objeto es la exclusión
{
id: "amex"
// acá estamos excluyendo el uso de la tarjeta American Express
}
],
excluded_payment_types: [{ id: "atm" }],
// aca podemos excluir TIPOS de pagos, es un array de objetos
// Por ejemplo, aca estamos excluyendo pago por cajero
installments: 6,
// mayor cantidad de cuotas permitidas
default_installments: 6
// la cantidad de cuotas que van a aparecer por defecto
},
back_urls: {
// declaramos las urls de redireccionamiento
success: "https://localhost:3000/success",
// url a la que va a redireccionar si sale todo bien
pending: "https://localhost:3000.com/pending",
// url a la que va a redireccionar si decide pagar en efectivo por ejemplo
failure: "https://localhost:3000.com/error"
// url a la que va a redireccionar si falla el pago
},
notification_url: "https://localhost:3000/webhook",
// declaramos nuestra url donde recibiremos las notificaciones
// es la misma ruta que declaramos en app.js
auto_return: "approved"
// si la compra es exitosa automaticamente redirige a "success" de back_urls
};
//NOTA: TODAS las URLS que usemos tienen que ser reales,
// si prueban con localhost, va a fallar
Por último, nos queda realizar el POST con axios:
const request = await axios.post(url, preferences, {
// hacemos el POST a la url que declaramos arriba, con las preferencias
headers: {
// y el header, que contiene content-Type
"Content-Type": "application/json"
}
});
El PaymentService.js les debería haber quedado:
const axios = require("axios");
class PaymentService {
constructor() {
this.tokensMercadoPago = {
prod: {},
test: {
access_token:
"APP_USR-6317427424180639-042414-47e969706991d3a442922b0702a0da44-469485398"
// el access_token de MP
}
};
// declaramos de la siguiente manera el token, para que sea más fácil cambiarlo dependiendo del ambiente
this.mercadoPagoUrl = "https://api.mercadopago.com/checkout";
// declaramos la url en el constructor para poder accederla a lo largo de toda la clase
}
async createPaymentMercadoPago(name, price, unit, img) {
// recibimos las props que le mandamos desde el PaymentController
const url = `${this.mercadoPagoUrl}/preferences?access_token=${this.tokensMercadoPago.test.access_token}`;
// url a la que vamos a hacer los requests
const items = [
{
id: "1234",
// id interno (del negocio) del item
title: name,
// nombre que viene de la prop que recibe del controller
description: "Dispositivo movil de Tienda e-commerce",
// descripción del producto
picture_url: "https://courseit.com.ar/static/logo.png",
// url de la imágen del producto
category_id: "1234",
// categoría interna del producto (del negocio)
quantity: parseInt(unit),
// cantidad, que tiene que ser un intiger
currency_id: "ARS",
// id de la moneda, que tiene que ser en ISO 4217
unit_price: parseFloat(price)
// el precio, que por su complejidad tiene que ser tipo FLOAT
}
];
const preferences = {
// declaramos las preferencias de pago
items,
// el array de objetos, items que declaramos más arriba
external_reference: "referencia del negocio",
// referencia para identificar la preferencia, puede ser practicamente cualquier valor
payer: {
// información del comprador, si estan en producción tienen que //traerlos del request
//(al igual que hicimos con el precio del item)
name: "Lalo",
surname: "Landa",
email: "test_user_63274575@testuser.com",
// si estan en sandbox, aca tienen que poner el email de SU usuario de prueba
phone: {
area_code: "11",
number: "22223333"
},
address: {
zip_code: "1111",
street_name: "False",
street_number: "123"
}
},
payment_methods: {
// declaramos el método de pago y sus restricciones
excluded_payment_methods: [
// aca podemos excluir metodos de pagos, tengan en cuenta que es un array de objetos
{
id: "amex"
}
],
excluded_payment_types: [{ id: "atm" }],
// aca podemos excluir TIPOS de pagos, es un array de objetos
installments: 6,
// limite superior de cantidad de cuotas permitidas
default_installments: 6
// la cantidad de cuotas que van a aparecer por defecto
},
back_urls: {
// declaramos las urls de redireccionamiento
success: "https://localhost:3000/success",
// url que va a redireccionar si sale todo bien
pending: "https://localhost:3000.com/pending",
// url a la que va a redireccionar si decide pagar en efectivo por ejemplo
failure: "https://localhost:3000.com/error"
// url a la que va a redireccionar si falla el pago
},
notification_url: "https://mercadopago-checkout.herokuapp.com/webhook",
// declaramos nuestra url donde recibiremos las notificaciones
auto_return: "approved"
// si la compra es exitosa automaticamente redirige a "success" de back_urls
};
try {
const request = await axios.post(url, preferences, {
// hacemos el POST a la url que declaramos arriba, con las preferencias
headers: {
// y el header, que contiene content-Type
"Content-Type": "application/json"
}
});
return request.data;
// devolvemos la data que devuelve el POST
} catch (e) {
console.log(e);
// mostramos error en caso de que falle el POST
}
}
}
//NOTA: TODAS las URLS que usemos tienen que ser reales,
//si prueban con localhost, va a fallar
module.exports = PaymentService;
Y eso es todo del lado del back. Para terminar con el ciclo, desde el front, deberían realizar un POST request a /payment/new y eso les va a devolver un link, que es el del pago.
Recuerden que el POST ejecutado desde el front tiene que tener un body que contenga (por lo menos), name, unit,img, y price.
Espero que este tutorial les haya servido como guía para iniciarse en este tipo de integraciones o para resolver dudas!
Toda la documentación de Mercado Pago, y lo que use para crear este código, está acá
Para crear sus usuarios de prueba consulten acá
Me pueden mandar sus consultas o mejoras del código (porque hay mil cosas para mejorar) a mi twitter @ncastrogarcia
No respondo más los comentarios! si tienen dudas pueden consultarme por twitter!
Tambien les dejo este video, que explico lo mismo que este blog pero en formato video
Si te sirvió me podes comprar un cafecito
Muchas gracias por tu tiempo!

Latest comments (64)
Hola Nicolas, queria saber si habías tratado de realizar la integración con Ruby? Si lo hiciste que desafíos encontraste? Muy bueno el articulo!!!!
Hola buenas tardes, he intentado hacer el examen de integración checkout pro, pero al hacer el pago me marco un error. De que algún dato es de prueba e implemente todo lo que solicita el examen con todos los datos que proporciona alguien sabe por qué marca el error.
examen
Integrator ID:
★ dev_24c65fb163bf11ea96500......
Test User (Vendedor) | Producción
★ Access Token:
APP_USR-292655009721353......
★ Public Key:
APP_USR-a68157fb-55......
Test User (Comprador o pagador)
★ Email:
test_user_94....
Hola Nico!
estoy intentando integrar el CheckOut pro de mercado pago unicamente desde el Front con una aplicacion React.js, es posible realizar eso ? de ser asi sabes como ?
(busque en un monton de foros y probe 200 cosas y no le encuentro la vuelta)
desde ya gracias !!
Hola! necesitas un servidor para poder guardar claves de acceso de mercadopago. No hay una forma segura de hacerlo sólo desde el frontend. Sé que mercadopago ofrece algunas cosas para poder integrar botones de pago rápido y demás, pero no las usé
Jooyaa!! Gracias Nico
Nico como estas? Gracias por el tutorial, super claro.
Sabes si es posible integrar MP con aplicación desarrollada con Supabase + Next.js?
Hola Nicolas antes que nada felicitarte por tu excelente post, estoy haciendo el examen de Checkout Pro y el resultado es:
Estoy utilizando NextJS y el modulo next/script para cargar el script externo, si reviso las peticiones y el código de mi app si está el security.js.
¿Tienes alguna idea de qué puede estar pasando?
¡Muchas gracias!
Hola Nicolás.... Gracias por compartir esta info, para los que estamos arrancando esto vale oro.... Necesito consultarte algo. Que puede estar pasando que, en mi Marketplace con credenciales de producción, para pagos con tarjeta de débito y con crédito de mercado pago no tengo problemas para hacer compras, pero cuando lo quiero hacer en efectivo me dice... "Algo salió mal... No pudimos procesar tu pago" y en las notificaciones solo tengo esto {"resource":"api.mercadolibre.com/merchant_orde...}
Tengo todas las back_urls configuradas y funcionando.
Gracias...
Hola Nico, antes que nada, MUCHAS GRACIAS por compartir este laburo, la documentacion de MP deja bastante que desear y la integracion es bastante engorrosa. Con esta info siento luz al final del tunel jajaja.
Te consulto:
Cuando estoy haciendo al peticion POST a "/payment/new" estoy enviandole en el body los campos "name, unit,img, y price" y me esta devolviendo por consola un error, me dice:
data: {
message: 'items.0.unit_price must be a number',
error: 'invalid_items',
status: 400,
cause: null
}
Estoy enviando unit y price como numeros en el body.
Si cambio los valores en el PaymentService.js a valores fijos si me genera el link.
Donde puede estar el error?
Muchas gracias por tu tiempo y ayuda!
Buenas amigo, muy buen post me esta ayudando mucho, junto con los comentarios a los que has respondido, pero tengo una duda, en desarrollo ya cree mi orden o mi ticket para pagar en oxxo, mi duda es hay forma de cambiar o actualizar esa compra de pendiente a pagada de forma manual para poder checar como actuan mi webhook de payment.update ?
Hola ivan, tenes que usar los usuarios de prueba, tanto el vendedor como el comprador tienen que ser de prueba. Podes leer un poco más sobre eso acá mercadopago.com.ar/developers/es/g...
Hola como estás Nicolás , antes que nada mil gracias por tu tiempo y por la novedad en el tema que publicás, no hay casi material al respecto. Quería hacerte algunas consultas básicas, ya que no hace mucho que estoy en esto, y hasta lo más básico me resulta un poco confuso. Quería saber como levanto el servidor para realizar las peticiones desde postman. Vos utilizás el live server de Visual Studio, o algo como Xammp ?
Y en caso de levantar el server hacia que URL realizo el Post : Actualmente estoy intentando con localhost:3000/payment/new
Intengo iniciar el servidor :
[nodemon] 2.0.4
[nodemon] to restart at any time, enter
rs[nodemon] watching path(s): .
[nodemon] watching extensions: js,mjs,json
[nodemon] starting
node ./bin/wwwAgradecería si me orientas . Mil gracias de nuevo.
Hola! para ejecutar el servidor lo hago con npm, veo que usas nodemon, por lo que estas encaminado. Y por defecto node ejecuta el proceso en el puerto 3000. No sé si respondi tu duda. Saludos!
¿Hay alguien que haya implementado el checkout pro de node js en alguna app de react?
Intento hacerlo con gatsby (parecido a nextjs) pero no se cómo controlar la acción del botón que genera el script que se agrega
Hola! Si te referis a implementarlo en el front (con este tipo de checkout), la unica manera es con Nexjs, que nos va a permitir tener el back y front juntos. Si no, tenes que hacer un backend y desde tu front en React, hacer el request
Some comments may only be visible to logged-in visitors. Sign in to view all comments.