npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mp-facil

v0.2.0

Published

Biblioteca Node.js simples para criar cobranças, consultar pagamentos e interpretar webhooks do Mercado Pago.

Readme

MP Facil

Uma biblioteca ESM para Node.js que deixa o básico do Mercado Pago direto: criar uma cobrança, mandar o cliente para o checkout, consultar o pagamento e entender webhooks sem ficar atravessando a resposta crua da API.

A lib usa os endpoints oficiais:

  • criar cobrança simples: POST /checkout/preferences
  • criar Pix direto: POST /v1/payments
  • consultar pagamento: GET /v1/payments/{id}
  • webhook: evento payment, com validação opcional de x-signature
  • alternativa ao webhook: loop local com memória em JSON

Instalação

npm install mp-facil

Requer Node.js 18 ou superior.

import { MPFacil } from 'mp-facil';

Neste repositório de teste:

import { MPFacil } from './src/index.js';

Primeiro pagamento

Imagine uma lojinha vendendo uma assinatura chamada "Clube do Café".

import { MPFacil } from 'mp-facil';

const mp = new MPFacil({
  apiKey: process.env.MP_ACCESS_TOKEN,
  webhookUrl: 'https://minhaloja.com/webhooks/mercado-pago'
});

const cobranca = await mp.criarCobranca({
  produto: 'Clube do Café - Plano Mensal',
  id: 'assinatura-cafe-mensal',
  preco: 39.9
});

if (!cobranca.ok) {
  console.log(cobranca.mensagem);
  process.exit(1);
}

console.log('Envie o cliente para:', cobranca.dados.link);

Retorno esperado:

{
  ok: true,
  dados: {
    id: '123456789-abcdef',
    referencia: 'assinatura-cafe-mensal',
    link: 'https://www.mercadopago.com.br/checkout/...',
    linkProducao: 'https://www.mercadopago.com.br/checkout/...',
    linkTeste: 'https://sandbox.mercadopago.com.br/checkout/...',
    criadoEm: '2026-05-15T10:00:00.000Z',
    modoTeste: false,
    bruto: {}
  }
}

Qual Função Usar?

| Quero... | Use | O que recebo | | --- | --- | --- | | Mandar o cliente para o Checkout Pro do Mercado Pago | criarCobranca | Link de checkout (dados.link). | | Gerar Pix copia e cola/QR Code direto na minha tela | criarPix | ID real do pagamento, QR Code, copia e cola e link Pix. | | Criar assinatura recorrente com link de pagamento Pix | criarPixRecorrente | Link de assinatura (dados.link) e ID da assinatura. | | Dividir pagamento em modelo marketplace | criarCobrancaDividida | Link de checkout com marketplace_fee. | | Ver status uma vez | consultarCobranca | Status normalizado do pagamento. | | Ver status da assinatura | consultarAssinatura | Status normalizado da assinatura recorrente. | | Verificar até pagar/recusar | acompanharCobranca | Loop local com memória em JSON. |

Diferença importante:

  • criarCobranca cria uma preferência e retorna um link;
  • criarPix cria um pagamento real e já retorna dados.id, que pode ser consultado ou acompanhado por loop.
  • criarPixRecorrente cria uma assinatura no Mercado Pago e retorna um link para o cliente concluir o pagamento inicial.

Preferência Não É Pagamento

Esse detalhe é pequeno, mas salva tempo.

Quando você chama:

const cobranca = await mp.criarCobranca({
  produto: 'Clube do Café',
  id: 'pedido-123',
  preco: 39.9
});

o campo abaixo é o ID da preferência de checkout:

cobranca.dados.id

Ele serve para montar/identificar o link de checkout, mas não é o ID do pagamento real. Por isso, isto aqui não é o fluxo correto:

await mp.consultarCobranca(cobranca.dados.id);

Para consultar status, você precisa do ID do pagamento:

await mp.consultarCobranca(paymentId);

Esse paymentId aparece depois que o comprador passa pelo checkout, normalmente por:

  • webhook do Mercado Pago;
  • URL de retorno (success, failure, pending);
  • parâmetros como payment_id ou collection_id.

Resumo rápido:

| Valor | De onde vem | Para que serve | | --- | --- | --- | | cobranca.dados.id | resposta de criarCobranca | ID da preferência/link de checkout. | | cobranca.dados.link | resposta de criarCobranca | URL para enviar o comprador ao Mercado Pago. | | payment_id / collection_id | retorno do checkout ou webhook | ID real do pagamento, usado em consultarCobranca e acompanharCobranca. | | pagamento.referencia | id que você enviou ao criar cobrança | Seu ID interno de pedido/cobrança. |

Criando o cliente

Você pode usar o nome curto:

const mp = new MPFacil({
  apiKey: 'APP_USR...',
  appId: '123456789',
  webhookUrl: 'https://minhaloja.com/webhook'
});

Ou os aliases mantidos para quem prefere nomes mais explícitos:

import { MercadoPago, MercadoPagoSimplificado } from 'mp-facil';

Campos aceitos no construtor:

| Campo | Aliases | Obrigatório | O que faz | | --- | --- | --- | --- | | apiKey | api_key, accessToken, access_token | Sim | Access Token do Mercado Pago. | | appId | id_app, applicationId, application_id | Não | ID da aplicação. Hoje é validado e guardado, mas não é enviado em cobranças simples. | | webhookUrl | webhook, urlWebhook, notificationUrl, notification_url | Não | URL enviada como notification_url ao criar cobrança. | | webhookSecret | segredoWebhook, secret, webhook_secret | Não | Segredo usado para validar x-signature nos webhooks. | | sandbox | modoTeste | Não | Liga modo teste desde o construtor. Padrão: false. | | baseUrl | base_url | Não | Troca a URL base da API. Útil para testes. | | fetch | fetchImpl | Não | Injeta uma implementação de fetch. Útil para testes. | | memoryLoopPath | pastaMemoryLoop, memory_loop_path | Não | Pasta onde os loops de consulta ficam salvos. Padrão: memory loop. | | autoResumeLoops | autoRetomarLoops | Não | Retoma automaticamente loops salvos quando o cliente é criado. Padrão: true. |

Se apiKey não for informada, o construtor lança erro API_KEY_AUSENTE.

Modo Teste

Por padrão, a lib trabalha em produção:

const mp = new MPFacil({ apiKey: 'APP_USR...' });

Para tratar as ações como teste:

const mp = new MPFacil({ apiKey: 'APP_USR...' }).modoTeste(true);

Também existe o alias em inglês:

mp.testMode(true);

modoTeste(true) retorna a própria instância, então dá para encadear. Para desligar:

mp.modoTeste(false);

Quando o modo teste está ligado:

  • criarCobranca retorna sandbox_init_point em dados.link;
  • criarCobranca adiciona metadata.mp_facil_modo_teste = true ao payload da preferência;
  • criarCobranca retorna dados.modoTeste: true;
  • consultarCobranca retorna dados.modoTeste: true.

Importante: a lib não troca suas credenciais sozinha. Use credenciais de teste do Mercado Pago quando quiser uma operação realmente isolada.

criarCobranca / createCharge

Cria uma preferência do Checkout Pro. É o caminho mais simples para criar uma cobrança usando só produto, referência interna e preço.

const resultado = await mp.criarCobranca({
  produto: 'Workshop JavaScript Sem Drama',
  id: 'workshop-js-2026',
  preco: 89.9,
  quantidade: 1,
  emailPagador: '[email protected]',
  urlSucesso: 'https://minhaloja.com/obrigado',
  urlFalha: 'https://minhaloja.com/tentar-novamente',
  urlPendente: 'https://minhaloja.com/aguardando',
  metadados: {
    campanha: 'maio-2026'
  }
});

Campos aceitos:

| Campo | Aliases | Obrigatório | Enviado para o Mercado Pago | | --- | --- | --- | --- | | produto | product, productName, nome, title | Sim | items[0].title | | id | produtoId, productId, externalReference, external_reference | Não | items[0].id e external_reference. Se ausente, a lib gera um UUID. | | preco | price, valor, amount | Sim | items[0].unit_price | | quantidade | quantity | Não | items[0].quantity. Padrão: 1. | | moeda | currency, currencyId, currency_id | Não | items[0].currency_id. Padrão: BRL. | | webhookUrl | webhook, urlWebhook, notificationUrl, notification_url | Não | notification_url. Sobrescreve o webhook do cliente. | | successUrl | urlSucesso, success_url | Não | back_urls.success | | failureUrl | urlFalha, failure_url | Não | back_urls.failure | | pendingUrl | urlPendente, pending_url | Não | back_urls.pending | | payerEmail | emailPagador, payer_email | Não | payer.email | | metadata | metadados | Não | metadata |

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: 'PREFERENCE_ID',
    referencia: 'workshop-js-2026',
    link: 'https://www.mercadopago.com.br/checkout/...',
    linkProducao: 'https://www.mercadopago.com.br/checkout/...',
    linkTeste: 'https://sandbox.mercadopago.com.br/checkout/...',
    criadoEm: '2026-05-15T10:00:00.000Z',
    modoTeste: false,
    bruto: {}
  }
}

Campos de dados:

| Campo | Significado | | --- | --- | | id | ID da preferência gerada pelo Mercado Pago. | | referencia | Sua referência interna, vinda de external_reference. | | link | URL que você deve abrir para o comprador. Usa sandbox quando modoTeste(true) está ativo. | | linkProducao | URL de checkout de produção retornada pela API. | | linkTeste | URL de checkout sandbox retornada pela API. | | criadoEm | Data de criação retornada pelo Mercado Pago. | | modoTeste | true se a instância está em modo teste. | | bruto | Resposta original da API para casos em que você precisa investigar algo. |

Retorno com erro:

{
  ok: false,
  erro: 'PRECO_INVALIDO',
  mensagem: 'O preco precisa ser maior que zero'
}

criarCobrancaDividida / createSplitCharge

Cria uma cobrança com split de pagamento no modelo marketplace 1:1 do Mercado Pago.

Antes de usar, entenda a limitação oficial: no Checkout Pro, o split não envia dinheiro para duas contas aleatórias informadas por ID. O fluxo é:

  • conta A: vendedor, dono do access_token OAuth usado para criar a preferência;
  • conta B: marketplace/plataforma, que recebe uma comissão via marketplace_fee;
  • a comissão do Mercado Pago é descontada do vendedor antes da comissão do marketplace.

Exemplo: produto de R$ 100,00, vendedor fica com 80% nominal e marketplace com 20%.

const cobranca = await mp.criarCobrancaDividida({
  produto: 'Ingresso - Noite do Jazz',
  id: 'pedido-jazz-001',
  preco: 100,
  split: {
    contaA: {
      accessToken: 'ACCESS_TOKEN_OAUTH_DO_VENDEDOR',
      percentual: 80
    },
    contaB: {
      percentual: 20
    }
  }
});

A lib calcula:

marketplace_fee = preco * percentualDaContaB / 100

E envia a preferência para:

POST /checkout/preferences
Authorization: Bearer ACCESS_TOKEN_OAUTH_DO_VENDEDOR

Campos aceitos:

| Campo | Obrigatório | O que faz | | --- | --- | --- | | produto, id, preco, etc. | Sim | Mesmos campos de criarCobranca. | | split.contaA.accessToken | Sim | Token OAuth do vendedor. Também aceita access_token, apiKey ou api_key. | | split.contaA.percentual | Sim | Percentual nominal do vendedor. | | split.contaB.percentual | Sim | Percentual da plataforma, enviado como marketplace_fee. | | marketplace | Não | ID marketplace explícito. Se não informar e houver appId, a lib usa MP-MKT-${appId}. |

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: 'PREFERENCE_ID',
    referencia: 'pedido-jazz-001',
    link: 'https://www.mercadopago.com.br/checkout/...',
    linkProducao: 'https://www.mercadopago.com.br/checkout/...',
    linkTeste: 'https://sandbox.mercadopago.com.br/checkout/...',
    criadoEm: '2026-05-15T10:00:00.000Z',
    modoTeste: false,
    split: {
      contaA: {
        percentual: 80,
        valorEstimado: 80
      },
      contaB: {
        percentual: 20,
        valor: 20
      },
      observacao: 'A comissao do Mercado Pago e descontada antes da comissao do marketplace.'
    },
    bruto: {}
  }
}

Campos importantes:

| Campo | Significado | | --- | --- | | dados.split.contaA.valorEstimado | Valor antes de considerar taxas do Mercado Pago. A API desconta taxas do vendedor conforme regras oficiais. | | dados.split.contaB.valor | Valor enviado como marketplace_fee. | | dados.bruto | Resposta original do Mercado Pago. |

Erros comuns:

{
  ok: false,
  erro: 'SPLIT_PERCENTUAL_INVALIDO',
  mensagem: 'As porcentagens do split precisam somar 100'
}

Quando usar:

  • marketplace com vendedores conectados por OAuth;
  • plataforma que cobra comissão;
  • cobrança simples via Checkout Pro.

Quando não usar:

  • dividir um mesmo pagamento entre duas contas arbitrárias só por e-mail/ID;
  • mandar parte do valor para conta bancária externa;
  • criar split com mais de um vendedor no mesmo pagamento;
  • substituir repasses financeiros manuais fora do Mercado Pago.

criarPix / createPix

Cria um pagamento Pix direto pela API de pagamentos. Diferente de criarCobranca, aqui a resposta já é um pagamento real e pode trazer QR Code, Pix copia e cola e link de pagamento.

Use essa função quando você quer mostrar o Pix dentro da sua própria aplicação, sem mandar o cliente primeiro para uma página de checkout.

const pix = await mp.criarPix({
  produto: 'Clube do Café - Plano Mensal',
  id: 'pedido-pix-001',
  preco: 39.9,
  emailPagador: '[email protected]'
});

if (!pix.ok) {
  console.log(pix);
} else {
  console.log('Pix copia e cola:', pix.dados.copiaECola);
  console.log('QR Code base64:', pix.dados.qrCodeBase64);
  console.log('Link Pix:', pix.dados.ticketUrl);
}

Exemplo com metadados e loop:

const pix = await mp.criarPix({
  produto: 'Clube do Café - Plano Mensal',
  id: 'pedido-pix-cafe',
  preco: 39.9,
  emailPagador: '[email protected]',
  metadados: {
    idUser: 'user-123'
  }
});

if (pix.ok) {
  await mp.acompanharCobranca(pix.dados.id, {
    intervaloMs: 5000,
    duracaoMs: 30 * 60 * 1000
  });
}

Campos aceitos:

| Campo | Aliases | Obrigatório | Enviado para o Mercado Pago | | --- | --- | --- | --- | | produto | product, productName, nome, title | Sim | description | | id | produtoId, productId, externalReference, external_reference | Não | external_reference. Se ausente, a lib gera UUID. | | preco | price, valor, amount | Sim | transaction_amount | | quantidade | quantity | Não | Multiplica o valor final. Padrão: 1. | | emailPagador | payerEmail, payer_email | Sim | payer.email | | webhookUrl | webhook, urlWebhook, notificationUrl, notification_url | Não | notification_url. | | metadata | metadados | Não | metadata | | idempotencyKey | chaveIdempotencia | Não | Header X-Idempotency-Key. Se ausente, a lib gera uma chave simples. |

Por que emailPagador é obrigatório? Porque a API de pagamento Pix do Mercado Pago exige payer.email no payload. Esse e-mail não precisa ser usado como identificador interno do seu usuário; para isso, prefira id e metadados.

Payload oficial usado pela lib:

{
  transaction_amount: 39.9,
  description: 'Clube do Café - Plano Mensal',
  payment_method_id: 'pix',
  external_reference: 'pedido-pix-001',
  payer: {
    email: '[email protected]'
  }
}

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: 123456789,
    status: 'pending',
    statusDetalhado: 'pending_waiting_transfer',
    situacao: 'pendente',
    valor: 39.9,
    moeda: 'BRL',
    descricao: 'Clube do Café - Plano Mensal',
    referencia: 'pedido-pix-001',
    metadados: {},
    metodoPagamento: 'pix',
    tipoPagamento: 'bank_transfer',
    qrCode: '000201...',
    qrCodeBase64: 'iVBORw0KGgo...',
    ticketUrl: 'https://www.mercadopago.com.br/payments/...',
    copiaECola: '000201...',
    expiraEm: '2026-05-16T10:00:00.000Z',
    criadoEm: '2026-05-15T10:00:00.000Z',
    atualizadoEm: '2026-05-15T10:00:00.000Z',
    modoTeste: false,
    bruto: {}
  }
}

Campos principais:

| Campo | Significado | | --- | --- | | dados.id | ID real do pagamento. Pode ser usado em consultarCobranca e acompanharCobranca. | | dados.copiaECola | Código Pix copia e cola. É o mesmo valor de qrCode. | | dados.qrCode | Código Pix completo retornado pela API. | | dados.qrCodeBase64 | Imagem do QR Code em base64, quando retornada pela API. | | dados.ticketUrl | Link renderizado do Pix, quando retornado pela API. | | dados.situacao | Normalmente começa como pendente até o pagamento ser aprovado. |

Como mostrar no front:

res.json({
  pagamentoId: pix.dados.id,
  copiaECola: pix.dados.copiaECola,
  qrCodeBase64: pix.dados.qrCodeBase64,
  ticketUrl: pix.dados.ticketUrl
});

No HTML, se qrCodeBase64 vier preenchido:

<img src="data:image/png;base64,SEU_QR_CODE_BASE64" alt="QR Code Pix">

E para copia e cola:

<textarea readonly>SEU_CODIGO_PIX_COPIA_E_COLA</textarea>

Depois de criar o Pix, você pode consultar uma vez:

const pagamento = await mp.consultarCobranca(pix.dados.id);

Ou acompanhar por loop:

await mp.acompanharCobranca(pix.dados.id, {
  intervaloMs: 5000,
  duracaoMs: 30 * 60 * 1000
});

Erros comuns:

{
  ok: false,
  erro: 'EMAIL_PAGADOR_AUSENTE',
  mensagem: 'Informe o email do pagador para gerar Pix'
}

Arquivo de teste local:

node testePIX.js

O arquivo testePIX.js cria um Pix, imprime copiaECola, qrCodeBase64, ticketUrl e inicia o loop usando pix.dados.id.

Observações:

  • em modo teste, use credenciais de teste e comprador de teste do Mercado Pago;
  • sandbox: true marca a resposta como teste na lib, mas não troca suas credenciais automaticamente;
  • o Pix começa normalmente como pendente;
  • use webhook ou loop para saber quando virou pago;
  • não confunda pix.dados.id com ID de preferência: no Pix direto, pix.dados.id já é ID real de pagamento.

criarPixRecorrente / createRecurringPix

Cria uma assinatura recorrente no Mercado Pago usando o endpoint oficial de assinaturas (POST /preapproval) e retorna o link para o cliente concluir o pagamento inicial.

const assinatura = await mp.criarPixRecorrente({
  produto: 'Clube do Cafe - Plano Mensal',
  id: 'assinatura-cafe-user-123',
  preco: 39.9,
  emailPagador: '[email protected]',
  urlRetorno: 'https://minhaloja.com/assinatura/ok',
  webhookUrl: 'https://minhaloja.com/webhook-assinaturas'
});

if (assinatura.ok) {
  console.log('Link da assinatura:', assinatura.dados.link);
}

Campos aceitos:

| Campo | Aliases | Obrigatorio | Enviado para o Mercado Pago | | --- | --- | --- | --- | | produto | product, productName, nome, title | Sim | reason | | id | produtoId, productId, externalReference, external_reference | Nao | external_reference. Se ausente, a lib gera um UUID. | | preco | price, valor, amount | Sim | auto_recurring.transaction_amount | | quantidade | quantity | Nao | Multiplica o valor final. Padrao: 1. | | emailPagador | payerEmail, payer_email | Sim | payer_email | | urlRetorno | returnUrl, backUrl, back_url, successUrl, urlSucesso | Sim | back_url | | frequencia | frequency | Nao | auto_recurring.frequency. Padrao: 1. | | tipoFrequencia | frequencyType, frequency_type | Nao | auto_recurring.frequency_type. Padrao: months. | | dataInicio | startDate, start_date | Nao | auto_recurring.start_date | | dataFim | endDate, end_date | Nao | auto_recurring.end_date | | webhookUrl | webhook, urlWebhook, notificationUrl, notification_url | Nao | notification_url. Sobrescreve o webhook do cliente. | | metadata | metadados | Nao | metadata |

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: 'PREAPPROVAL_ID',
    status: 'pending',
    situacao: 'pendente',
    descricao: 'Clube do Cafe - Plano Mensal',
    referencia: 'assinatura-cafe-user-123',
    link: 'https://www.mercadopago.com.br/subscriptions/checkout?...',
    valor: 39.9,
    moeda: 'BRL',
    frequencia: 1,
    tipoFrequencia: 'months',
    webhookUrl: 'https://minhaloja.com/webhook-assinaturas',
    modoTeste: false,
    bruto: {}
  }
}

Observacao: essa funcao cria uma assinatura com status: 'pending'. O comprador abre dados.link, escolhe/conclui o pagamento no Mercado Pago, e as proximas cobrancas ficam sob o fluxo de assinaturas do Mercado Pago.

consultarAssinatura / checkSubscription

Consulta uma assinatura recorrente pelo ID retornado em criarPixRecorrente.

const assinatura = await mp.consultarAssinatura('PREAPPROVAL_ID');

if (assinatura.ok && assinatura.dados.situacao === 'ativa') {
  console.log('Assinatura ativa:', assinatura.dados.referencia);
}

Essa funcao usa o endpoint de assinaturas (GET /preapproval/{id}). Para consultar um pagamento individual gerado pela assinatura, use consultarCobranca com o ID do pagamento.

consultarCobranca / checkCharge

Consulta um pagamento real pelo ID de pagamento do Mercado Pago.

const pagamento = await mp.consultarCobranca('123456789');

if (pagamento.ok && pagamento.dados.situacao === 'pago') {
  console.log('Pode liberar o pedido:', pagamento.dados.referencia);
}

De Onde Vem o paymentId?

O paymentId não nasce no momento em que você cria a preferência. Ele nasce quando o Mercado Pago cria um pagamento a partir daquele checkout.

Um fluxo comum com back_urls:

const cobranca = await mp.criarCobranca({
  produto: 'Clube do Café',
  id: 'pedido-123',
  preco: 39.9,
  urlSucesso: 'https://minhaloja.com/pagamento/volta',
  urlFalha: 'https://minhaloja.com/pagamento/volta',
  urlPendente: 'https://minhaloja.com/pagamento/volta'
});

Depois do checkout, o Mercado Pago pode redirecionar para uma URL parecida com:

https://minhaloja.com/pagamento/volta?payment_id=123456789&status=approved&external_reference=pedido-123

No backend, você pega esse ID e consulta a API:

app.get('/pagamento/volta', async (req, res) => {
  const paymentId = req.query.payment_id || req.query.collection_id;

  if (!paymentId) {
    return res.status(400).send('Pagamento sem ID.');
  }

  const pagamento = await mp.consultarCobranca(paymentId);

  if (pagamento.ok && pagamento.dados.situacao === 'pago') {
    await liberarPedido(pagamento.dados.referencia);
  }

  res.send('Pagamento recebido. Pode fechar esta página.');
});

O front pode capturar esse parâmetro e enviar ao backend, mas o backend sempre deve confirmar com consultarCobranca. Nunca libere pedido só porque o front disse que pagou.

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: 123456789,
    status: 'approved',
    statusDetalhado: 'accredited',
    situacao: 'pago',
    valor: 89.9,
    moeda: 'BRL',
    descricao: 'Workshop JavaScript Sem Drama',
    referencia: 'workshop-js-2026',
    metadados: {
      idUser: 'user-123'
    },
    metodoPagamento: 'pix',
    tipoPagamento: 'bank_transfer',
    aprovadoEm: '2026-05-15T10:05:00.000Z',
    criadoEm: '2026-05-15T10:01:00.000Z',
    atualizadoEm: '2026-05-15T10:05:00.000Z',
    modoTeste: false,
    bruto: {}
  }
}

Campos de dados:

| Campo | Significado | | --- | --- | | id | ID do pagamento no Mercado Pago. | | status | Status original da API, como approved, pending, rejected. | | statusDetalhado | Detalhe original, como accredited ou motivos de recusa. | | situacao | Status amigável da lib: pago, pendente, recusado ou desconhecido. | | valor | Valor da transação. | | moeda | Moeda retornada pela API. | | descricao | Descrição do pagamento, quando existir. | | referencia | Sua referência interna. | | metadados | Objeto enviado em metadata ao criar a cobrança. Útil para idUser, plano, campanha, etc. | | metodoPagamento | Método, como pix, visa, master. | | tipoPagamento | Tipo, como credit_card, debit_card, bank_transfer. | | aprovadoEm | Data de aprovação, quando existir. | | criadoEm | Data de criação. | | atualizadoEm | Data da última atualização. | | modoTeste | true se a instância está em modo teste. | | bruto | Resposta original da API. |

Mapeamento de situacao:

| Mercado Pago status | MP Facil situacao | | --- | --- | | approved | pago | | authorized | pendente | | in_process | pendente | | pending | pendente | | rejected | recusado | | cancelled | recusado | | refunded | recusado | | charged_back | recusado | | qualquer outro | desconhecido |

interpretarWebhook / parseWebhook

Valida e interpreta o corpo de um webhook sem consultar o pagamento na API.

const evento = mp.interpretarWebhook({
  body: req.body,
  query: req.query,
  headers: req.headers
});

if (!evento.ok) {
  return res.status(400).json(evento);
}

console.log('Pagamento citado no webhook:', evento.dados.id);

Entrada esperada:

{
  body: {
    type: 'payment',
    action: 'payment.updated',
    data: {
      id: '123456789'
    }
  },
  query: {
    'data.id': '123456789'
  },
  headers: {
    'x-request-id': '...',
    'x-signature': 'ts=...,v1=...'
  }
}

Retorno com sucesso:

{
  ok: true,
  dados: {
    id: '123456789',
    tipo: 'payment',
    acao: 'payment.updated',
    evento: 'payment.updated',
    headers: {},
    query: {},
    corpo: {}
  }
}

Campos de dados:

| Campo | Significado | | --- | --- | | id | ID do pagamento citado no webhook. | | tipo | Tipo do evento. A lib aceita apenas payment. | | acao | Ação enviada pelo Mercado Pago, quando existir. | | evento | acao quando existir, senão tipo. | | headers | Headers normalizados para minúsculas. | | query | Query recebida. | | corpo | Body recebido. |

Se webhookSecret estiver configurado, a assinatura é validada com x-signature, x-request-id e data.id. Se algo não bater, o retorno será:

{
  ok: false,
  erro: 'ASSINATURA_INVALIDA',
  mensagem: 'Assinatura do webhook invalida'
}

processarWebhook / handleWebhook

Interpreta o webhook, consulta o pagamento na API e dispara eventos com a resposta normalizada do pagamento.

mp.on('pago', (dados) => {
  console.log('Liberar acesso ao pedido:', dados.referencia);
  console.log('Usuario:', dados.metadados.idUser);
});

mp.on('recusado', (dados) => {
  console.log('Avisar cliente para tentar outro meio:', dados.id);
});

mp.on('pendente', (dados) => {
  console.log('Ainda aguardando:', dados.id);
});

mp.on('erro', (erro) => {
  console.error('Webhook não processado:', erro.mensagem);
});

app.post('/webhook', async (req, res) => {
  const resultado = await mp.processarWebhook({
    body: req.body,
    query: req.query,
    headers: req.headers
  });

  res.status(resultado.ok ? 200 : 400).json(resultado);
});

Retorno:

  • se o webhook for inválido, retorna o mesmo formato de erro de interpretarWebhook;
  • se a consulta do pagamento falhar, retorna o erro de consultarCobranca;
  • se der certo, retorna o mesmo formato de sucesso de consultarCobranca.

Eventos emitidos:

| Evento | Quando acontece | Payload | | --- | --- | --- | | pago | status vira approved | dados normalizados de pagamento. | | pendente | status vira authorized, in_process ou pending | dados normalizados de pagamento. | | recusado | status vira rejected, cancelled, refunded ou charged_back | dados normalizados de pagamento. | | erro | webhook inválido, falha na consulta ou status desconhecido | objeto de erro normalizado. |

Alternativa ao Webhook: Loop de Consulta

Às vezes webhook é chato no começo: ambiente local sem túnel, servidor que dorme, configuração ainda em teste. Para esses casos, a lib tem um loop que consulta o pagamento até ele ficar pago ou recusado.

mp.on('pago', (pagamento) => {
  console.log('Pedido liberado:', pagamento.referencia);
});

mp.on('recusado', (pagamento) => {
  console.log('Pagamento falhou:', pagamento.statusDetalhado);
});

mp.on('pendente', (pagamento) => {
  console.log('Ainda esperando:', pagamento.id);
});

await mp.acompanharCobranca('ID_DO_PAGAMENTO', {
  intervaloMs: 5000,
  duracaoMs: 10 * 60 * 1000
});

Esse recurso é útil quando você já tem o ID do pagamento. Em Checkout Pro, esse ID normalmente aparece no retorno para suas back_urls como payment_id ou collection_id, e também pode aparecer no webhook.

O loop não deve receber cobranca.dados.id, porque esse é o ID da preferência. Use:

await mp.acompanharCobranca(paymentId);

e não:

await mp.acompanharCobranca(cobranca.dados.id);

Como a memória funciona

Ao iniciar um acompanhamento, a lib cria um JSON local na pasta:

memory loop/

Exemplo de arquivo:

{
  "id": "123456789",
  "pagamentoId": "123456789",
  "status": "monitorando",
  "intervaloMs": 5000,
  "duracaoMs": 600000,
  "tentativas": 3,
  "modoTeste": false
}

Se a aplicação parar antes do pagamento resolver, esse arquivo continua ali. Quando você cria o cliente de novo, a lib tenta retomar automaticamente os loops salvos.

const mp = new MPFacil({
  apiKey: process.env.MP_ACCESS_TOKEN
});

Se preferir controlar isso manualmente:

const mp = new MPFacil({
  apiKey: process.env.MP_ACCESS_TOKEN,
  autoResumeLoops: false
});

await mp.retomarLoops();

Para trocar a pasta:

const mp = new MPFacil({
  apiKey: process.env.MP_ACCESS_TOKEN,
  memoryLoopPath: './dados/pagamentos-pendentes'
});

acompanharCobranca / watchCharge

Inicia um loop em segundo plano.

const loop = await mp.acompanharCobranca('123456789', {
  intervaloMs: 3000,
  duracaoMs: 15 * 60 * 1000
});

Opções:

| Campo | Aliases | Padrão | O que faz | | --- | --- | --- | --- | | intervaloMs | intervalMs, intervalo | 10000 | Tempo entre consultas. | | duracaoMs | durationMs, duracao, duration | null | Tempo máximo do loop. Se não informar, o loop só para quando pagar ou recusar. |

Retorno:

{
  ok: true,
  dados: {
    id: '123456789',
    status: 'monitorando',
    intervaloMs: 3000,
    duracaoMs: 900000,
    tentativas: 0,
    modoTeste: false,
    arquivo: '/caminho/do/projeto/memory loop/123456789.json'
  }
}

O loop emite:

| Evento | Quando | | --- | --- | | pendente | A consulta voltou pending, in_process ou authorized. O loop continua. | | pago | A consulta voltou approved. O loop para e remove o JSON. | | recusado | A consulta voltou rejected, cancelled, refunded ou charged_back. O loop para e remove o JSON. | | erro | A consulta falhou, a duração acabou ou veio status desconhecido. Erros de consulta não param o loop; duração expirada e status desconhecido param. |

retomarLoops / resumeLoops

Lê os JSONs da pasta memory loop e reinicia os acompanhamentos pendentes.

const retomados = await mp.retomarLoops();

Retorno:

{
  ok: true,
  dados: {
    retomados: 2,
    ids: ['123456789', '987654321']
  }
}

cancelarAcompanhamento / cancelWatch

Para um loop manualmente e remove o JSON.

const cancelado = await mp.cancelarAcompanhamento('123456789');

Retorno:

{
  ok: true,
  dados: {
    id: '123456789',
    cancelado: true
  }
}

Exemplo com back URL

Um uso comum é começar o loop quando o cliente volta do Mercado Pago:

app.get('/pagamento/volta', async (req, res) => {
  const pagamentoId = req.query.payment_id || req.query.collection_id;

  if (!pagamentoId) {
    return res.status(400).send('Pagamento sem ID para acompanhar.');
  }

  await mp.acompanharCobranca(pagamentoId, {
    intervaloMs: 5000,
    duracaoMs: 20 * 60 * 1000
  });

  res.send('Estamos confirmando seu pagamento. Pode fechar esta página.');
});

Nesse fluxo, o front só redireciona o comprador. A confirmação acontece no backend:

  1. front abre cobranca.dados.link;
  2. Mercado Pago redireciona para sua urlSucesso/urlFalha/urlPendente;
  3. backend lê payment_id ou collection_id;
  4. backend chama consultarCobranca ou inicia acompanharCobranca;
  5. backend atualiza seu banco.

Se você fizer uma SPA e o front receber o payment_id, envie esse ID ao backend:

await fetch('/api/confirmar-pagamento', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ paymentId })
});

E confirme no backend:

app.post('/api/confirmar-pagamento', async (req, res) => {
  const pagamento = await mp.consultarCobranca(req.body.paymentId);

  if (!pagamento.ok) {
    return res.status(400).json(pagamento);
  }

  if (pagamento.dados.situacao === 'pago') {
    await liberarPedido(pagamento.dados.referencia);
  }

  res.json(pagamento);
});

Formato de Erros

As funções públicas que fazem trabalho retornam objeto, não lançam exceção, exceto o construtor quando falta apiKey.

Formato:

{
  ok: false,
  erro: 'API_KEY_INVALIDA',
  mensagem: 'A api_key informada e invalida',
  status: 401,
  detalhes: {}
}

Campos:

| Campo | Quando aparece | Significado | | --- | --- | --- | | ok | sempre | false em erro. | | erro | sempre | Código interno estável da lib. | | mensagem | sempre | Mensagem curta para log ou resposta simples. | | status | quando existe | Status HTTP retornado pela API. | | detalhes | quando existe | Resposta original da API ou dado útil de depuração. |

Códigos internos:

| Código | Causa comum | | --- | --- | | API_KEY_AUSENTE | O cliente foi criado sem apiKey/api_key. | | API_KEY_INVALIDA | Mercado Pago respondeu 401. | | APP_ID_INVALIDO | appId foi informado vazio. | | PAYLOAD_INCOMPLETO | Payload ausente. | | PRODUTO_AUSENTE | Faltou produto/productName. | | PRECO_AUSENTE | Faltou preco/price. | | PRECO_INVALIDO | Preço menor ou igual a zero, ou não numérico. | | ID_AUSENTE | Consulta chamada sem ID. | | FALHA_DE_REDE | fetch falhou antes de receber resposta. | | RESPOSTA_INESPERADA | API retornou erro não mapeado ou JSON inválido. | | WEBHOOK_INVALIDO | Webhook sem data.id/id ou tipo diferente de payment. | | ASSINATURA_INVALIDA | Assinatura do webhook ausente ou inválida. | | STATUS_DESCONHECIDO | Pagamento retornou status fora do mapeamento da lib. | | LOOP_INTERVALO_INVALIDO | intervaloMs menor ou igual a zero, ou não numérico. | | LOOP_DURACAO_INVALIDA | duracaoMs menor ou igual a zero, ou não numérico. | | LOOP_TEMPO_ESGOTADO | O loop chegou ao tempo máximo sem pago ou recusado. | | LOOP_NAO_ENCONTRADO | Reservado para loops não encontrados em operações futuras. | | SPLIT_CONTA_A_INVALIDA | Faltou token OAuth da conta A/vendedor. | | SPLIT_CONTA_B_INVALIDA | Faltou percentual da conta B/marketplace. | | SPLIT_PERCENTUAL_INVALIDO | Percentuais não somam 100. | | SPLIT_VALOR_INVALIDO | Comissão calculada ficou fora do valor da cobrança. | | FREQUENCIA_ASSINATURA_INVALIDA | Frequencia menor ou igual a zero, ou nao numerica, ao criar Pix recorrente. | | EMAIL_PAGADOR_AUSENTE | Faltou emailPagador ao criar Pix. | | EMAIL_PAGADOR_INVALIDO | E-mail do pagador não parece válido. | | URL_RETORNO_AUSENTE | Faltou urlRetorno ao criar Pix recorrente. |

Exemplo Completo com Express

import express from 'express';
import { MPFacil } from 'mp-facil';

const app = express();
const mp = new MPFacil({
  apiKey: process.env.MP_ACCESS_TOKEN,
  webhookUrl: 'https://minhaloja.com/webhook',
  webhookSecret: process.env.MP_WEBHOOK_SECRET
});

app.use(express.json());

app.post('/checkout/cafe', async (req, res) => {
  const cobranca = await mp.criarCobranca({
    produto: 'Clube do Café - Degustação',
    id: `pedido-${Date.now()}`,
    preco: 29.9,
    emailPagador: req.body.email,
    urlSucesso: 'https://minhaloja.com/cafe/obrigado',
    urlFalha: 'https://minhaloja.com/cafe/erro',
    urlPendente: 'https://minhaloja.com/cafe/aguardando'
  });

  if (!cobranca.ok) {
    return res.status(400).json(cobranca);
  }

  res.json({
    checkout: cobranca.dados.link,
    referencia: cobranca.dados.referencia
  });
});

mp.on('pago', (pagamento) => {
  console.log('Separar café para o pedido:', pagamento.referencia);
});

mp.on('pendente', (pagamento) => {
  console.log('Aguardando confirmação:', pagamento.id);
});

mp.on('recusado', (pagamento) => {
  console.log('Pagamento não aprovado:', pagamento.statusDetalhado);
});

mp.on('erro', (erro) => {
  console.error('Falha no fluxo Mercado Pago:', erro);
});

app.post('/webhook', async (req, res) => {
  const resultado = await mp.processarWebhook({
    body: req.body,
    query: req.query,
    headers: req.headers
  });

  res.status(resultado.ok ? 200 : 400).json(resultado);
});

app.listen(3000);

O Que Esta Lib Não Tenta Fazer

Ela não é um SDK completo do Mercado Pago. Ela não cria pagamento transparente com cartão, não gerencia reembolso, carteira, disputa ou toda a gestao avancada de assinaturas. O foco é ser pequena e agradável para o fluxo básico:

  1. criar uma cobrança por checkout;
  2. criar Pix direto;
  3. criar assinatura recorrente com link de pagamento;
  4. consultar status;
  5. processar webhook ou acompanhar por loop;
  6. criar split marketplace 1:1;
  7. receber erros previsíveis.

Fontes Oficiais Consultadas

  • Criar preferência Checkout Pro: https://www.mercadopago.com.mx/developers/en/reference/online-payments/checkout-pro/preferences/create-preference/post
  • Criar pagamento Pix: https://docs02.mercadopago.com.br/developers/en/docs/checkout-api-payments/integration-configuration/integrate-pix
  • Criar assinatura: https://www.mercadopago.com.br/developers/pt/reference/online-payments/subscriptions/create-preapproval/post
  • Assinatura com pagamento pendente: https://www.mercadopago.com.br/developers/pt/docs/subscriptions/integration-configuration/subscription-no-associated-plan/pending-payments
  • Obter pagamento por ID: https://www.mercadopago.com.br/developers/pt/reference/online-payments/checkout-pro/get-payment/get
  • Validação de webhooks com x-signature: https://www.mercadopago.com.mx/developers/es/docs/your-integrations/notifications/webhooks
  • Split Payments 1:1: https://www.mercadopago.com.br/developers/en/docs/split-payments/split-1-1/overview
  • Integração de marketplace/split: https://www.mercadopago.com.ar/developers/en/docs/split-payments/split-1-1/integration-configuration/integrate-marketplace