Meu nome é Julio e hoje vou mostrar como integrar a API do Mercado Livre com o S3 da AWS, para envio e armazenamento de infoprodutos. Escolhi utiliza-lo, pois é permitido o upload de arquivos de até 100gb manualmente. Você pode me encontrar no LinkedIn ou no GitHub.
Criando um webhook
Antes de tudo, precisamos criar uma "aplicação", que é como os webhooks do Mercado Livre são chamados. Acesse o link: https://developers.mercadolivre.com.br/devcenter, vincule sua conta e clique em "criar nova aplicação". Na página que aparecer, você pode inserir as informações que preferir; não fará muita diferença para o que vou ensinar. No campo "URIs de redirect", você pode inserir qualquer link acessível. Pode ser o YouTube, seu portfólio ou algo assim. Vou explicar mais adiante para que isso serve.
Quanto aos escopos, aconselho a marcar todos. Eles representam as permissões que seu software terá no Mercado Livre, por meio da sua conta.
Em "Tópicos", você pode selecionar as opções das quais deseja ser notificado. No nosso caso, vamos marcar "Orders_v2".
Agora, a parte mais importante, o local que você configura para receber as notificações. Pode ocorrer um BUG que informa não ser possível inserir a URL desejada. Para resolver, basta abrir a seção "Outros", marcar algum tópico e depois desmarcá-lo. Recomendo utilizar o ngrok para testar os webhooks. Caso tenha interesse, pode ler mais aqui, foi o mesmo artigo que me ajudou a entender melhor o ngrok.
Pegando dados para realizar a autenticação
Após ter feito o primeiro passo, você será redirecionado para a página inicial, onde deverá clicar nos três pontinhos e em "editar". Nessa parte, você verá dois campos, um sendo o ID da aplicação (vou usá-lo como 2200211202, para melhorar a explicação) e o outro sendo o secret (vou usá-lo como 10ZEWGtPdD7jxhUdYAAO3lGOQTvm3UJX, para melhorar a explicação). Você vai precisar de ambos. Com as informações em mãos, o próximo passo é autenticar na aplicação.
Aqui está a URL completa para o BRASIL, basta inserir as informações que você obteve:
Então, no nosso caso, ficaria assim:
REDIRECT_URL: https://www.youtube.com/
CLIENT_ID: 2200211202
Após inserir as informações, basta colar a URL no navegador, autorizar e você será redirecionado. Ao ser redirecionado, você deverá ver na URL algo assim: TG-65d65b17d124c20001f0c119-1694513136. Guarde esse código. Vamos precisar dele no futuro.
Autenticação e autorização
Agora, uma parte essencial: Autenticação e autorização. Com o código obtido, precisamos trocá-lo por um token. Para fazer isso, você deve colar a seguinte requisição no Postman:
curl -X POST \
-H 'accept: application/json' \
-H 'content-type: application/x-www-form-urlencoded' \
'https://api.mercadolibre.com/oauth/token' \
-d 'grant_type=authorization_code' \
-d 'client_id=$APP_ID' \
-d 'client_secret=$SECRET_KEY' \
-d 'code=$SERVER_GENERATED_AUTHORIZATION_CODE' \
-d 'redirect_uri=$REDIRECT_URI' \
-d 'code_verifier=$CODE_VERIFIER'
grant_type: authorization_code
APP_ID: será nosso CLIENT_ID (2200211202)
client_secret será nosso secret (10ZEWGtPdD7jxhUdYAAO3lGOQTvm3UJX)
code: Aqui você deverá colocar o código recebido na URL (algo como TG-65d65b17d124c20001f0c119-1694513136)
redirect_uri: https://www.youtube.com/
code_verifier: $CODE_VERIFIER
Para colar uma requisição no Postman, você pode seguir estes passos: Na página inicial, clique em "Import" (fica ao lado do seu workspace), e pressione Ctrl + V.
A resposta deverá ser algo assim:
{
"access_token": "APP_USR-123456-090515-8cc4448aac10d5105474e1351-1234567",
"token_type": "bearer",
"expires_in": 21600,
"scope": "offline_access read write",
"user_id": 1234567,
"refresh_token": "TG-5b9032b4e23464aed1f959f-1234567"
}
Após fazer essa troca, devemos obter um refresh token. Esse token serve para ser trocado por outro access token após 6 horas do primeiro uso. Caso você utilize esse refresh token para qualquer ação no Mercado Livre, será necessário inserir o link de autenticação no navegador para obter outro código. Cada ação na plataforma deverá ser feita usando o access_token.
curl -X POST \
-H 'accept: application/json' \
-H 'content-type: application/x-www-form-urlencoded' \
'https://api.mercadolibre.com/oauth/token' \
-d 'grant_type=refresh_token' \
-d 'client_id=$APP_ID' \
-d 'client_secret=$SECRET_KEY' \
-d 'refresh_token=$REFRESH_TOKEN'
grant_type: refresh_token
client_id: 2200211202
client_secret: 10ZEWGtPdD7jxhUdYAAO3lGOQTvm3UJX
refresh_token: TG-5b9032b4e23464aed1f959f-1234567
A resposta deverá ser algo assim:
{
"access_token": "APP_USR-5387223166827464-090515-b0ad156bce700509ef81b273466faa15-8035443",
"token_type": "bearer",
"expires_in": 21600,
"scope": "offline_access read write",
"user_id": 8035443,
"refresh_token": "TG-5b9032b4e4b0714aed1f959f-8035443"
}
Testando
Como mencionado anteriormente, com esse access_token você poderá fazer qualquer requisição/ação na plataforma. Para testar se está tudo funcionando, podemos fazer a seguinte requisição:
curl -H 'Authorization: Bearer APP_USR-12345678-031820-X-12345678' \
https://api.mercadolibre.com/users/me
No header "Authorization", será necessário colocar o access_token que recebemos anteriormente. Caso tudo dê certo, você deve ver as informações da conta autenticada.
Aproveitando este tópico de testes, gostaria de compartilhar como eu fiz para testar minha aplicação, já que seria necessário fazer compras.
Você deve fazer a seguinte requisição para criar um usuário de testes:
curl -X POST -H 'Authorization: Bearer $ACCESS_TOKEN' -H "Content-Type: application/json" -d
'{
"site_id":"MLB"
}'
https://api.mercadolibre.com/users/test_user
a resposta deverá ser algo assim:
{
"id":120506781,
"nickname":"TEST0548",
"password":"qatest328",
"site_status":"active"
}
Salve as informações, principalmente os últimos 6 dígitos do ID, você vai precisar colocá-los no lugar do código enviado por e-mail. Vale ressaltar que você pode criar até 10 usuários. Dito isso, precisamos de um cartão de crédito, né? O que adianta ter o usuário mas não conseguir comprar. Uma solução possível é utilizar os cartões de teste que o Mercado Pago disponibiliza. Você pode acessar os dados aqui
Para dúvidas que não foram respondidas, pode deixar nos comentários ou acessar a documentação.
Código
Controller
import { Request, Response } from 'express';
import axios from 'axios';
import { NotificationsService } from '../services/notifications';
import { z } from 'zod';
import { env } from '../env';
const notificationsService = new NotificationsService();
export default class NotificationsController {
async receiveTheRequestFromMercadoLivre(req: Request, res: Response): Promise<Response> {
const bodySchema = z.object({
_id: z.string(),
resource: z.string().startsWith('/orders/'),
user_id: z.number(),
topic: z.string().startsWith('orders_v2').endsWith('orders_v2'),
application_id: z.number(),
attempts: z.number(),
sent: z.string(),
received: z.string(),
actions: z.array(z.string()).nonempty(),
});
const isBodySchema = bodySchema.safeParse(req.body);
if (!isBodySchema.success) {
return res.sendStatus(400);
}
await axios.post(
'https://infoprodutos.onrender.com/notification/handle',
{
body: req.body,
},
{
headers: {
Authorization: env.SECRET,
},
},
);
return res.sendStatus(200);
}
async handleTheRequestFromMercadoLivre(req: Request, res: Response): Promise<Response> {
const bodySchema = z.object({
_id: z.string(),
resource: z.string().startsWith('/orders/'),
user_id: z.number(),
topic: z.string().startsWith('orders_v2').endsWith('orders_v2'),
application_id: z.number(),
attempts: z.number(),
sent: z.string(),
received: z.string(),
actions: z.array(z.string()).nonempty(),
});
const isBodySchema = bodySchema.safeParse(req.body.body);
if (!isBodySchema.success) {
return res.sendStatus(400);
}
if (req.headers.authorization !== env.SECRET) {
return res.sendStatus(401);
}
await notificationsService.handleRequest(req.body);
return res.sendStatus(200);
}
}
Como o Mercado Livre fica enviando uma grande quantidade de requisições, resolvi validar através das "actions", já que em algumas delas eles enviam um array vazio e em outras enviam um "status".
AWS
import * as AWS from 'aws-sdk';
import { env } from '../../env';
export function createS3Instance() {
const s3 = new AWS.S3();
s3.config.update({
region: env.AWS_BUCKET_REGION,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
accessKeyId: env.AWS_ACCESS_KEY,
signatureVersion: 'v4',
});
return s3;
}
import { createS3Instance } from './config';
import { env } from '../../env';
export async function getProductFromS3(key: string) {
const s3 = createS3Instance();
const expiresInThreeHours = 10800;
const params = {
Bucket: env.AWS_BUCKET_NAME,
Key: key,
Expires: expiresInThreeHours,
};
const url = await s3.getSignedUrlPromise('getObject', params);
return url;
}
Service
import { PrismaClient, Tokens } from '@prisma/client';
import { MercadoLivreNotification, Order } from '../@types';
import { updateMercadoLivreRefreshToken } from './refresh-token';
const prisma = new PrismaClient();
export class NotificationsService {
private async getToken(): Promise<Tokens> {
const token = await prisma.tokens.findMany({
orderBy: {
created_at: 'desc',
},
take: 1,
});
return token[0];
}
private async getOrder(token: Tokens, { body }: MercadoLivreNotification): Promise<Order | void> {
try {
const orderRequestDetails = await fetch(`https://api.mercadolibre.com/${body.resource}`, {
headers: {
Authorization: `Bearer ${token.access_token}`,
},
});
if (orderRequestDetails.status >= 400) {
await updateMercadoLivreRefreshToken();
return;
}
const order: Order = await orderRequestDetails.json();
if (!order.pack_id) {
order.pack_id = order.id;
}
return order;
} catch (error) {
console.log(error);
}
}
async handleRequest(body: MercadoLivreNotification): Promise<void> {
const token = await this.getToken();
const order = await this.getOrder(token, body);
if (!order) {
return;
}
await this.messageSenderHandler(order, token);
}
private async messageSenderHandler(order: Order, token: Tokens) {
const key = this.transformOrderItemInAwsKey(order.order_items[0].item.title);
const url = await getProductFromS3(key);
try {
console.log('chego na mensagem');
const message = await fetch(
`https://api.mercadolibre.com/messages/action_guide/packs/${order.pack_id}/option?tag=post_sale`,
{
method: 'post',
body: JSON.stringify({
option_id: 'OTHER',
text: `Obrigado por sua compra, clique no link abaixo para fazer o download do produto:<br><br> ${url}`,
}),
headers: {
Authorization: `Bearer ${token.access_token}`,
},
},
);
return message;
} catch (error) {
console.log(error);
}
}
private transformOrderItemInAwsKey(name: string): string {
return name.split(' ').join('_').toLowerCase();
}
}
Como mencionei anteriormente, o token do Mercado Livre expira após 6 horas. Para resolver esse problema, implementei o seguinte código:
import axios, { AxiosResponse } from 'axios';
import { env } from '../env';
import { RefreshToken } from '../@types';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function updateMercadoLivreRefreshToken() {
const getTheLastTokenRegistered = await prisma.tokens.findMany({
orderBy: {
created_at: 'desc',
},
take: 1,
});
const token: AxiosResponse<RefreshToken> = await axios.post('https://api.mercadolibre.com/oauth/token', {
grant_type: 'refresh_token',
client_id: env.MERCADO_LIVRE_APP_ID,
client_secret: env.MERCADO_LIVRE_SECRET_KEY,
refresh_token: getTheLastTokenRegistered[0].refresh_token,
});
await prisma.tokens.create({
data: {
access_token: token.data.access_token,
refresh_token: token.data.refresh_token,
},
});
return token;
}
Basicamente, minha estratégia foi a seguinte: esperar receber um "unauthorized" e então realizar um refresh no token, já que o Mercado Livre enviará uma segunda tentativa da requisição. O primeiro token era introduzido manualmente.
Conclusão
Obrigado às pessoas que leram até aqui! Sintam-se à vontade para me dar dicas sobre escrita, código ou até mesmo sugestões de algo que poderia ter sido feito de forma diferente, como usar uma stack diferente ou qualquer outra sugestão :D
Top comments (2)
Obrigado por compartilhar! Parabéns muito bom!
Muito obrigado pelo comentário, Eduardo. Sucesso!