Executando verificação de segurança...
7

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

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:

  1. Acesse Suas Aplicacoes
  2. Crie uma aplicacao (ou use a padrao)
  3. Va em Credenciais de Teste
  4. 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:

  • MercadoPagoConfig inicializa o SDK com seu Access Token
  • payment.create() cria um pagamento com metodo pix
  • O retorno inclui qr_code (texto para copia-e-cola) e qr_code_base64 (imagem do QR code)
  • idempotencyKey evita 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 paymentId buscando 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

  1. Acesse Suas Aplicacoes
  2. Clique na sua aplicacao
  3. Va em "Webhooks" (ou "Notificacoes IPN")
  4. Adicione a URL: https://seudominio.com.br/api/webhook/mercadopago
  5. 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:

  1. Gere o pagamento normalmente
  2. O QR code gerado nao funciona em apps de banco reais (e sandbox)
  3. Use o simulador de pagamento pra aprovar/rejeitar o pagamento manualmente
  4. 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.

Carregando publicação patrocinada...