Como integrar Mercado Pago Pix no Next.js 15 (App Router)
Se voce ja tentou integrar pagamento Pix num projeto Next.js, sabe que a documentacao oficial do Mercado Pago e voltada pra PHP e Express. Next.js 15 com App Router? Quase nada.
Esse guia cobre tudo: do setup ate o webhook de confirmacao.
Pre-requisitos
- Next.js 15 com App Router
- Node.js 18+
- Conta no Mercado Pago Developers
- Access Token de teste (sandbox)
1. Instalar o SDK
npm install mercadopago
O SDK oficial do Mercado Pago pra Node.js (mercadopago v2+) funciona com ESModules e TypeScript.
2. Configurar variaveis de ambiente
Crie um .env.local na raiz do projeto:
MERCADOPAGO_ACCESS_TOKEN=TEST-sua-chave-aqui
NEXT_PUBLIC_MERCADOPAGO_PUBLIC_KEY=TEST-sua-public-key-aqui
Importante: o Access Token e secreto. Nunca exponha no frontend. O NEXT_PUBLIC_ prefix so serve pra Public Key.
Pra pegar essas chaves:
- Acesse Suas Aplicacoes
- Crie uma aplicacao (ou use a padrao)
- Va em Credenciais de Teste
- Copie o Access Token e a Public Key
3. Criar a Route Handler para gerar pagamento Pix
No Next.js 15 com App Router, APIs ficam em app/api/. Crie o arquivo:
// app/api/pix/route.ts
import { MercadoPagoConfig, Payment } from "mercadopago";
import { NextRequest, NextResponse } from "next/server";
const client = new MercadoPagoConfig({
accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN!,
});
const payment = new Payment(client);
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const result = await payment.create({
body: {
transaction_amount: body.amount,
description: body.description || "Pagamento via Pix",
payment_method_id: "pix",
payer: {
email: body.email,
first_name: body.name?.split(" ")[0] || "",
last_name: body.name?.split(" ").slice(1).join(" ") || "",
},
},
requestOptions: {
idempotencyKey: crypto.randomUUID(),
},
});
// O resultado traz o QR code e o codigo copia-e-cola
return NextResponse.json({
id: result.id,
status: result.status,
qr_code: result.point_of_interaction?.transaction_data?.qr_code,
qr_code_base64:
result.point_of_interaction?.transaction_data?.qr_code_base64,
ticket_url: result.point_of_interaction?.transaction_data?.ticket_url,
});
} catch (error: any) {
console.error("Erro ao criar pagamento Pix:", error);
return NextResponse.json(
{ error: error.message || "Erro ao processar pagamento" },
{ status: 500 }
);
}
}
O que esta acontecendo:
MercadoPagoConfiginicializa o SDK com seu Access Tokenpayment.create()cria um pagamento com metodopix- O retorno inclui
qr_code(texto para copia-e-cola) eqr_code_base64(imagem do QR code) idempotencyKeyevita pagamentos duplicados se a requisicao for reenviada
4. Componente de pagamento no frontend
Crie um componente React que chama a API e exibe o QR Code:
// app/components/PixPayment.tsx
"use client";
import { useState } from "react";
interface PixData {
id: string;
status: string;
qr_code: string;
qr_code_base64: string;
ticket_url: string;
}
export function PixPayment({
amount,
description,
}: {
amount: number;
description: string;
}) {
const [pixData, setPixData] = useState<PixData | null>(null);
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);
async function handlePay() {
setLoading(true);
try {
const res = await fetch("/api/pix", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
amount,
description,
email: "[email protected]", // Em producao, pegue do formulario
name: "Nome do Cliente",
}),
});
const data = await res.json();
if (data.error) throw new Error(data.error);
setPixData(data);
} catch (err: any) {
alert("Erro: " + err.message);
} finally {
setLoading(false);
}
}
async function copyCode() {
if (pixData?.qr_code) {
await navigator.clipboard.writeText(pixData.qr_code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
}
if (!pixData) {
return (
<button
onClick={handlePay}
disabled={loading}
className="bg-green-600 text-white px-6 py-3 rounded-lg
hover:bg-green-700 disabled:opacity-50"
>
{loading ? "Gerando Pix..." : `Pagar R$${amount.toFixed(2)} com Pix`}
</button>
);
}
return (
<div className="flex flex-col items-center gap-4 p-6 border rounded-lg">
<h3 className="text-lg font-bold">Escaneie o QR Code</h3>
{pixData.qr_code_base64 && (
<img
src={`data:image/png;base64,${pixData.qr_code_base64}`}
alt="QR Code Pix"
className="w-64 h-64"
/>
)}
<p className="text-sm text-gray-500">Ou copie o codigo:</p>
<button
onClick={copyCode}
className="bg-gray-100 px-4 py-2 rounded text-sm
hover:bg-gray-200 transition"
>
{copied ? "Copiado!" : "Copiar codigo Pix"}
</button>
<p className="text-xs text-gray-400">
O pagamento expira em 30 minutos.
</p>
</div>
);
}
5. Webhook para confirmar pagamento
O Mercado Pago envia notificacoes quando o pagamento e aprovado. Crie outro Route Handler:
// app/api/webhook/mercadopago/route.ts
import { MercadoPagoConfig, Payment } from "mercadopago";
import { NextRequest, NextResponse } from "next/server";
const client = new MercadoPagoConfig({
accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN!,
});
const payment = new Payment(client);
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Mercado Pago envia diferentes tipos de notificacao
if (body.type === "payment" || body.action === "payment.updated") {
const paymentId =
body.data?.id || body.data_id;
if (!paymentId) {
return NextResponse.json({ received: true });
}
// Busca detalhes do pagamento
const paymentInfo = await payment.get({ id: paymentId });
if (paymentInfo.status === "approved") {
// Pagamento aprovado! Aqui voce:
// 1. Atualiza o status no banco de dados
// 2. Libera acesso ao produto
// 3. Envia email de confirmacao
console.log(
`Pagamento ${paymentId} aprovado!`,
`Valor: R$${paymentInfo.transaction_amount}`
);
// await db.orders.update({
// where: { paymentId: String(paymentId) },
// data: { status: "paid" },
// });
}
}
return NextResponse.json({ received: true });
} catch (error: any) {
console.error("Erro no webhook:", error);
// Sempre retorne 200 para o Mercado Pago nao reenviar
return NextResponse.json({ received: true });
}
}
Pontos importantes:
- Sempre retorne status 200, mesmo em caso de erro. Se voce retornar 4xx/5xx, o Mercado Pago vai reenviar a notificacao repetidamente.
- Valide o
paymentIdbuscando diretamente na API (nao confie so no corpo do webhook). - Em producao, adicione verificacao de assinatura HMAC pra garantir que a notificacao veio do Mercado Pago.
6. Configurar o webhook no painel
- Acesse Suas Aplicacoes
- Clique na sua aplicacao
- Va em "Webhooks" (ou "Notificacoes IPN")
- Adicione a URL:
https://seudominio.com.br/api/webhook/mercadopago - Selecione o evento:
Payments
Para testes locais, use ngrok ou Cloudflare Tunnel pra expor sua aplicacao.
7. Testando no sandbox
O Mercado Pago tem um ambiente de teste com contas sandbox. Com credenciais de teste (TEST-*), voce pode simular pagamentos sem dinheiro real.
Pra testar Pix no sandbox:
- Gere o pagamento normalmente
- O QR code gerado nao funciona em apps de banco reais (e sandbox)
- Use o simulador de pagamento pra aprovar/rejeitar o pagamento manualmente
- O webhook sera disparado normalmente
Erros comuns
"invalid_access_token"
Verifique se voce esta usando o Access Token correto (teste vs producao).
QR Code nao aparece
O campo qr_code_base64 pode vir vazio em algumas respostas. Use qr_code (texto) como fallback e gere o QR code no frontend com uma lib como qrcode.react.
Webhook nao chega
Em ambiente local, voce precisa expor a porta com ngrok ou similar. O Mercado Pago nao consegue acessar localhost.
"unsupported_payment_method"
Pix precisa que sua conta do Mercado Pago tenha Pix habilitado. Verifique nas configuracoes da conta.
Proximos passos
Essa integracao cobre o basico. Pra um SaaS em producao, voce vai querer:
- Assinatura HMAC no webhook (verificar autenticidade)
- Polling de status no frontend (verificar pagamento sem depender 100% do webhook)
- Expiracao customizada (o padrao e 24h, mas pra checkout voce quer 30min)
- Reembolso via API (
payment.refund()) - Logs e monitoramento (Sentry, LogTail)
Pix e o metodo de pagamento que mais cresce no Brasil. Se voce esta construindo SaaS pro mercado BR, integrar Pix corretamente desde o comeco economiza muita dor de cabeca depois.
Esse post foi publicado originalmente no blog do Zilvo. Estamos construindo ferramentas para devs que criam SaaS no Brasil.