Skip to main content
Conteúdo do guia

Conteúdo do guia

Tempo de leitura: 20 min
#monobank#acquiring#vibecoding#ai_tools#payments
NOVOIniciante20 min

Como integrar o acquiring do Monobank sem programador via vibe coding

Guia passo a passo para integrar pagamentos online do Monobank ao seu site: conformidade para MEI, criação de terminal, verificação de webhooks ECDSA, UX robusta e testes de segurança.

Publicado:
PARA AGENTE DE IAChatGPTClaude

O vibe coding transformou radicalmente o desenvolvimento de produtos digitais. Hoje, para adicionar ao seu site a aceitação de pagamentos via Apple Pay, Google Pay ou cartões bancários, não é mais necessário contratar um desenvolvedor backend nem mergulhar em protocolos criptográficos por conta própria. Basta saber formular com clareza a lógica de negócio para o seu agente de IA (Codex, Antigravity, Cursor ou Claude Code) e fornecer-lhe o contexto técnico adequado da documentação.

Neste guia, analisamos o ciclo de vida completo da integração do acquiring online Monobank no seu projeto: desde a conformidade bancária e configuração do painel do empreendedor até a geração de um gateway de pagamento seguro com verificação criptográfica de assinaturas ECDSA nos webhooks, tratamento de condições de corrida na interface e um conjunto obrigatório de testes automatizados.

Dica

Versão em vídeo do guia: Se você prefere um formato visual, assista ao vídeo prático detalhado no YouTube →, onde todo o processo é demonstrado em tela viva, desde a primeira solicitação até a cobrança real dos fundos.

Nota

Pacote de skills agênticas: Para máxima precisão na geração de código, baixe o pacote oficial de conhecimento para o seu assistente de IA:
Baixar arquivo completo monobank-acquiring.zip (38 KB) →


1. Arquitetura Hosted Checkout e ciclo de vida do pagamento

O Monobank Acquiring opera sob o esquema Hosted Checkout (pagamento em página segura do banco). Isso significa que seu site não precisa de certificações de segurança PCI DSS complexas e caras, pois os dados do cartão são inseridos pelo usuário diretamente no domínio protegido do banco.

A documentação oficial do Monobank para ferramentas de IA está disponível em monobank.ua/api-docs/acquiring/dev/ai-tools/docs--ai-prompts →. Salve este link como fonte canônica dos requisitos técnicos do banco.

1.1. Fluxo básico de pagamento

terminal
[ Cliente no site ] │ ├─ 1. Clica em «Pagar» ▼ [ Seu Servidor ] ──────────────► [ API Monobank: /invoice/create ] ▲ │ │ recebe pageUrl, invoiceId │ └──────────────────────────────┘ │ ├─ 2. Redireciona o cliente para pageUrl (Hosted Checkout) ▼ [ Página de pagamento Monobank ] ───► Pagamento (Apple Pay / Google Pay / Cartão) │ ├─ 3. POST assíncrono do webhook com assinatura x-sign ▼ [ Seu Endpoint de Webhook ] ─────────► Verificação ECDSA SHA-256 → Status "success" │ ├─ 4. O cliente retorna para /payment-result ▼ [ Página de sucesso ] ───────────────► Exibição do acesso / recibo
  • 1. Criação da fatura (invoice/create): O cliente escolhe um produto ou plano no seu site. O handler do servidor envia uma requisição POST para a API do Monobank com o valor, a descrição do pagamento e o endereço de retorno. O banco retorna um invoiceId único e o link pageUrl.
  • 2. Redirecionamento para a página de pagamento: O cliente é redirecionado para o pageUrl, onde paga o pedido via Apple Pay, Google Pay, aplicativo Monobank ou inserindo os dados do cartão bancário.
  • 3. Recebimento do Webhook: Após a conclusão do pagamento, o Monobank envia automaticamente uma requisição POST para o seu webHookUrl previamente configurado, contendo o status da transação e uma assinatura digital criptográfica.
  • 4. Fixação do status (Terminal States): Seu servidor deve reagir a dois estados finais: success (pagamento bem-sucedido; libera-se o acesso ou envia-se o pedido) e failure (erro ou recusa do pagamento pelo banco).
Importante

O status expired (expiração do link) não gera webhook. Se o cliente fechar a página de pagamento sem concluir a transação, o estado do pedido deve ser verificado via polling de backup na API.


2. Pré-requisitos legais e checklist de conformidade do site para autônomos (FOP)

Mesmo que o código de pagamento esteja impecável, o departamento de monitoramento financeiro e segurança do Monobank não ativará a aceitação de pagamentos reais se o site não estiver em conformidade com a legislação ucraniana e as regras dos sistemas de pagamento Visa/Mastercard.

2.1. Pré-requisitos bancários

  • Conta FOP ou PJ aberta no Monobank: O acquiring é conectado exclusivamente a contas empresariais. É proibido por lei receber pagamentos comerciais em cartões pessoais (preto ou branco) de pessoa física.
  • Códigos NACE/CAE adequados: Os dados de registro do FOP devem conter códigos de atividade para comércio eletrônico ou serviços (por exemplo, 47.91 — Varejo pela internet, 62.01/62.02 — Programação e consultoria de computadores, 85.59 — Outros tipos de educação).

2.2. Checklist obrigatório de páginas antes da submissão para moderação

Antes de solicitar a ativação do terminal de internet, certifique-se de que seu site possui as seguintes seções (geralmente localizadas no rodapé):

  • Contrato Público de Oferta (Public Offer Agreement): Descrição do objeto da transação, momento da celebração do contrato, direitos e obrigações das partes.
  • Política de Privacidade (Privacy Policy): Formulação clara sobre quais dados dos clientes são coletados e como são protegidos, conforme a Lei da Ucrânia "Sobre a Proteção de Dados Pessoais".
  • Condições de Reembolso e Entrega: Procedimento de devolução de mercadorias ou reembolso dentro de 14 dias, conforme a Lei da Ucrânia "Sobre a Proteção dos Direitos do Consumidor", e, para produtos/serviços digitais ou assinaturas, as regras de desistência do serviço.
  • Dados completos do empreendedor no rodapé do site: Nome do FOP ou LTDA, CNPJ/IE (equivalente local), endereço jurídico, número de telefone de contato e e-mail funcional do suporte.
  • Preços transparentes e descrições: Cada botão de pagamento deve ter um valor fixo em Hryvnias (UAH) e uma descrição clara do que exatamente o cliente está pagando.
Aviso

Se o site não tiver oferta pública, dados do FOP ou se, em vez de preços, houver placeholders como "sob consulta", a equipe de segurança do Monobank rejeitará o registro do terminal na etapa de verificação.


3. Criação do terminal web no painel Monobank Business

Para interagir com a API de pagamentos, você precisa de uma chave de acesso pessoal — o X-Token. Ele é gerado gratuitamente dentro do painel pessoal do empreendedor.

3.1. Algoritmo passo a passo para abrir a caixa registradora

  1. Autenticação: Acesse web.monobank.ua → e faça login usando QR code através do aplicativo Monobank no smartphone.
  2. Acesso à Caixa: No menu de navegação esquerdo, selecione a seção «Caixa».
  3. Adicionar ferramenta: Clique no botão «+ Adicionar ferramenta» e escolha a opção «Pagamento no site (desenvolvimento próprio)».
  4. Registro do terminal: Digite o nome do projeto (por exemplo, Meu Site ou Terminal Principal) e confirme a operação clicando em «Conectar».
  5. Geração do token de API: Abra o terminal criado, vá para a aba «Integrações / Chaves de API», clique em «Criar token» e copie a chave gerada.

📍 Navegação no painel: web.monobank.ua → Caixa → + Adicionar ferramenta → Pagamento no site (desenvolvimento próprio)

Criação de ferramenta de pagamento no painel Monobank Kasa ЗбільшитиCriação de ferramenta de pagamento no painel Monobank KasaCriação de ferramenta de pagamento no painel Monobank Kasa

📍 Obtenção da chave de API: Caixa → Seu Terminal → Integração → Criar X-Token

Janela modal de criação e cópia do token de acesso X-Token ЗбільшитиJanela modal de criação e cópia do token de acesso X-TokenJanela modal de criação e cópia do token de acesso X-Token

3.2. Regras inegociáveis de segurança de tokens

  • Nenhum hardcode no código cliente: O X-Token dá acesso direto ao gerenciamento das suas finanças e reembolsos. É estritamente proibido armazená-lo em JavaScript/HTML aberto ou publicá-lo em repositórios GitHub públicos.
  • Uso de variáveis de ambiente: Armazene o token exclusivamente no arquivo .env no servidor, sob o nome MONOBANK_TOKEN, ou na seção Secrets da sua plataforma de hosting (Vercel, Render, Railway, Replit, Lovable).
  • Token de teste para desenvolvimento: Para experimentos iniciais, o banco fornece um token de teste separado na página api.monobank.ua →, que permite simular transações sem cobrar valores reais.

4. Prompts oficiais de IA do Monobank para vibe coders

A equipe do Monobank desenvolveu um conjunto de prompts de sistema oficiais para agentes de IA. Sua principal vantagem é a correspondência exata com os endpoints atuais do banco.

Página da documentação oficial do Monobank para ferramentas de IA ЗбільшитиPágina da documentação oficial do Monobank para ferramentas de IAPágina da documentação oficial do Monobank para ferramentas de IA
Aviso

A principal armadilha para iniciantes — valor em centavos: A API do Monobank aceita todos os valores exclusivamente nas unidades mínimas da moeda (centavos/kopecks). $100\text{ UAH} = 10,000\text{ kopecks}$. Se você passar amount: 100, o cliente pagará apenas 1 hryvnia.

4.1. Prompt base para criação de pagamento

Copie este prompt e envie-o no chat do seu assistente de IA:

markdown
Quero adicionar ao meu site a funcionalidade de aceitar pagamentos via Monobank. TAREFA: Escreva um código completo e pronto para uso que crie um pagamento e redirecione o usuário para a página de pagamento. O QUE DEVE ACONTECER: 1. No meu site, há um botão "Ir para o pagamento". 2. O usuário clica no botão → um pagamento é criado no Monobank. 3. O usuário é redirecionado para a página de pagamento do Monobank. 4. Após efetuar o pagamento, ele retorna ao meu site (https://mysite.com/payment-result). 5. Eu verifico o status do pagamento e exibo o resultado. DADOS DE TESTE: - Valor: 100 UAH (10000 kopecks) - Descrição do pagamento: "Pagamento do pedido" - URL de retorno do usuário: https://mysite.com/payment-result MEU TOKEN DO MONOBANK: Variável de ambiente MONOBANK_TOKEN definida no arquivo .env DOCUMENTAÇÃO: - Criação de pagamento: https://monobank.ua/api-docs/acquiring/methods/ia/post--api--merchant--invoice--create - Verificação de status: https://monobank.ua/api-docs/acquiring/methods/ia/get--api--merchant--invoice--status Escreva todo o código necessário para esse fluxo.

4.2. Prompt para configurar o handler de Webhook

Sem o webhook, o servidor não saberá do fato do pagamento bem-sucedido se o cliente fechar o navegador imediatamente após a cobrança:

markdown
Preciso que meu servidor detecte automaticamente quando um usuário efetuar o pagamento de um pedido. TAREFA: Configure a recepção automática de informações de pagamentos do Monobank (webhook). O QUE DEVE ACONTECER: 1. O usuário realiza o pagamento na página do Monobank. 2. O Monobank envia automaticamente uma requisição POST para o meu servidor. 3. Meu servidor recebe os dados: invoiceId, status (success/failure), valor. 4. O servidor valida a assinatura criptográfica (ECDSA SHA-256 via header x-sign). 5. Atualiza o status do pedido no banco de dados e registra o resultado em log. IMPORTANTE: - É obrigatório validar o header x-sign utilizando a chave pública do banco. - Processe o corpo bruto da requisição (raw body Buffer / raw string), não o JSON já parseado. - Trate os status: success, failure, processing, hold, expired. - Garanta idempotência: se o mesmo webhook for recebido duas vezes, não duplique a entrega do produto. Escreva todo o código para um handler de webhook robusto.

5. Pacote de skills monobank-acquiring: potencializando o agente

Se limitar apenas a um prompt curto, o assistente de IA escreverá um código básico (aproximadamente 6.8 de 10): o botão funcionará, mas o código não conterá verificação de assinaturas criptográficas, proteção contra adulteração de preço nem tratamento de erros raros de rede.

Para obter nível de produção (9.8–10 pontos), adiciona-se à raiz do projeto um pacote especializado de skills monobank-acquiring.

Auditoria de integração de pagamento: comparação de segurança antes e depois do uso da skill ЗбільшитиAuditoria de integração de pagamento: comparação de segurança antes e depois do uso da skillAuditoria de integração de pagamento: comparação de segurança antes e depois do uso da skill
bash
# Розпакування пакета скілів у проєкт curl -L -o monobank-acquiring.zip https://gotburnout.io/downloads/monobank-acquiring.zip unzip monobank-acquiring.zip -d monobank-acquiring/

5.1. Anatomia e estrutura do pacote de skills

Arquivo da SkillConteúdo e responsabilidades
SKILL.mdManifesto central: fluxo base, autenticação X-Token, tipos de dados e tratamento de erros 400, 403, 429, 500.
quickstart.mdInício rápido: tutorial passo a passo para criar invoice e fallback-polling com comandos curl prontos.
invoice.mdCiclo de vida das faturas: endpoints de criação de invoice, verificação de status, cancelamento e invalidação de links.
webhook.mdProteção criptográfica: verificação matematicamente precisa da assinatura ECDSA SHA-256 do header x-sign.
payment.mdPagamentos diretos: cobrança com token de cartão salvo, transações síncronas e verificação 3DS.
wallet.mdTokenização de cartões (Wallet): armazenamento seguro do meio de pagamento do cliente no cofre do banco para pagamentos em um clique.
fiscal.mdRecibos fiscais e pRRo: descrição da estrutura do carrinho basketOrder, cálculo de impostos, descontos e exportação de recibos em PDF.
statement.mdExtratos e analytics: obtenção do registro de operações bem-sucedidas no período selecionado, com cálculo de taxas bancárias.
merchant.mdDados do merchant: obtenção da chave pública do banco, gerenciamento de submerchants e caixas.
examples/Servidores prontos: amostras funcionais de servidores em 6 linguagens populares (Node.js, Python, Go, PHP, C#, Java).

6. Implementação prática: fluxo dinâmico de pagamento

Em cada projeto há uma estrutura diferente: alguns vendem consultas digitais com planos fixos, outros têm lojas online com carrinhos dinâmicos, e outros ainda possuem landing pages simples com um único botão de doação ou assinatura.

O erro principal dos iniciantes é "codificar rigidamente" o valor (por exemplo, 1000 UAH) diretamente no código cliente do botão ou no corpo da requisição POST vinda do navegador. Essa abordagem cria uma vulnerabilidade crítica de segurança.

6.1. Princípio de segurança: preço dinâmico em vez de hardcode

  • Nunca confie no valor vindo do navegador: Se o JavaScript do cliente enviar { price: 1000 } para o servidor, um atacante pode, via DevTools ou Postman, alterar esse valor para { price: 1 } e adquirir o acesso por 1 hryvnia.

  • Servidor como única fonte da verdade (Single Source of Truth): O frontend envia para o backend apenas o identificador do produto (productId), o plano escolhido (planId: "pro") ou o array de identificadores do carrinho (items: [{ id: "book_1", qty: 2 }]).

  • Conversão automática para centavos: O servidor obtém o valor atual do arquivo de configuração ou banco de dados e multiplica por 100 por conta própria:

    $$\text{amount} = \text{Math.round}(\text{realPrice} \times 100)$$ Se você alterar o preço no banco ou anunciar um desconto, a fatura do Monobank será gerada com o novo valor correto sem intervenção no gateway de pagamento.

6.2. Prompt universal de IA para adaptação a qualquer projeto

Copie este prompt e envie-o no chat do seu agente de IA (Codex, Antigravity, Cursor ou Claude Code). O agente escaneará os arquivos do seu site, encontrará botões e preços existentes e construirá uma integração robusta:

markdown
No diretório raiz do nosso projeto, foi adicionado o pacote oficial de conhecimento Monobank Acquiring na pasta /monobank-acquiring. TAREFA: 1. Analise a arquitetura do nosso projeto: identifique onde estão descritos os produtos, serviços, planos tarifários ou botões de checkout/pagamento. 2. Crie ou integre um endpoint seguro no servidor para criação de pagamentos via Monobank: - O cliente envia APENAS o identificador do produto/plano tarifário (por exemplo, tariffId ou productId), mas NÃO o valor. - O servidor busca o preço real e atualizado da nossa configuração ou banco de dados e converte-o em centavos (preço * 100). - O token é obtido com segurança a partir de process.env.MONOBANK_TOKEN. - É feita uma requisição POST para https://api.monobank.ua/api/merchant/invoice/create. - Retorna-se ao cliente o link pageUrl para redirecionamento ao checkout do banco. 3. Atualize os nossos botões de pagamento existentes no frontend: - Adicione um estado de carregamento (indicador Loader e bloqueio do botão contra cliques repetidos). - Em caso de resposta bem-sucedida, realize um redirecionamento suave do usuário para o link recebido do Monobank. - Adicione tratamento de erros com notificações intuitivas para o usuário. 4. Crie uma página de resultado do pagamento (/payment-result) que verifique o status do pedido e informe o usuário sobre o sucesso. 5. Escreva testes unitários para validar o cálculo dinâmico dos valores e a criação da fatura (invoice). Todas as exigências técnicas, estruturas de requisições e códigos de moeda devem ser retiradas dos arquivos na pasta /monobank-acquiring (especialmente SKILL.md e invoice.md).
Trabalho do agente de IA na IDE com análise de estrutura e criação de endpoint dinâmico ЗбільшитиTrabalho do agente de IA na IDE com análise de estrutura e criação de endpoint dinâmicoTrabalho do agente de IA na IDE com análise de estrutura e criação de endpoint dinâmico

6.3. Template arquitetural do backend para criação de invoice

Aqui estão implementações server-side testadas para os dois stacks web mais populares:

typescript
// app/api/checkout/create-invoice/route.ts import { NextResponse } from "next/server"; const PRODUCTS_CATALOG: Record<string, { title: string; priceUah: number }> = { plan_starter: { title: "Тариф Starter", priceUah: 490 }, plan_pro: { title: "Тариф Pro", priceUah: 990 }, plan_vip: { title: "Тариф VIP", priceUah: 2490 }, }; export async function POST(req: Request) { try { const { productId } = await req.json(); // 1. Валідація: ціна формується виключно на бекенді const product = PRODUCTS_CATALOG[productId]; if (!product) { return NextResponse.json({ error: "Обраний товар або тариф не знайдено" }, { status: 400 }); } const amountInKopecks = Math.round(product.priceUah * 100); const orderReference = `order_${productId}_${Date.now()}`; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mysite.com"; // 2. Запит до Monobank API const response = await fetch("https://api.monobank.ua/api/merchant/invoice/create", { method: "POST", headers: { "X-Token": process.env.MONOBANK_TOKEN!, "Content-Type": "application/json", }, body: JSON.stringify({ amount: amountInKopecks, ccy: 980, // Гривня (ISO 4217) merchantPaymInfo: { reference: orderReference, destination: `Оплата: ${product.title}`, comment: `Замовлення ${orderReference}`, }, redirectUrl: `${siteUrl}/payment-result?ref=${orderReference}`, webHookUrl: `${siteUrl}/api/payment/webhook`, validity: 3600, // Рахунок діє 1 годину }), }); const data = await response.json(); if (!response.ok) { return NextResponse.json({ error: data.errText || "Помилка створення рахунку в банку" }, { status: response.status }); } return NextResponse.json({ checkoutUrl: data.pageUrl, invoiceId: data.invoiceId }); } catch (error) { return NextResponse.json({ error: "Внутрішня помилка ініціалізації платежу" }, { status: 500 }); } }

7. Tratamento seguro de webhooks e assinatura criptográfica ECDSA

A parte mais crítica de qualquer integração financeira é a verificação das notificações de pagamento. Um atacante pode enviar uma requisição HTTP forjada para seu endereço /api/payment/webhook com uma mensagem falsa de sucesso.

Para prevenir isso, o Monobank assina cada webhook usando o algoritmo assimétrico ECDSA (curva secp256r1 / SHA-256) e transmite a assinatura no header HTTP x-sign.

7.1. Por que JSON.stringify quebra a verificação da assinatura

Aviso

Armadilha crítica do rawBody: Para verificar a assinatura ECDSA, é necessário o fluxo de bytes original rigorosamente enviado pelo servidor do Monobank. Se você tentar parsear o JSON e chamar novamente JSON.stringify(req.body), a ordem das chaves, espaços ou quebras de linha mudarão. Isso resultará em um hash SHA-256 diferente, e a verificação da assinatura retornará erro garantidamente!

7.2. Implementação da verificação do webhook

Abaixo está a implementação correta para obter o corpo bruto e verificar a assinatura para Next.js App Router e Express:

typescript
// app/api/payment/webhook/route.ts import { NextResponse } from "next/server"; import crypto from "crypto"; let cachedPubKey: string | null = null; async function getMonobankPubKey(token: string): Promise<string> { if (cachedPubKey) return cachedPubKey; const res = await fetch("https://api.monobank.ua/api/merchant/pubkey", { headers: { "X-Token": token }, next: { revalidate: 86400 }, // Кешуємо публічний ключ на 24 години }); const data = await res.json(); cachedPubKey = `-----BEGIN PUBLIC KEY-----\n${data.key}\n-----END PUBLIC KEY-----`; return cachedPubKey; } export async function POST(req: Request) { const signature = req.headers.get("x-sign"); if (!signature) { return new NextResponse("Missing x-sign header", { status: 400 }); } // 1. Отримуємо сире незмінене тіло запиту у вигляді тексту const rawBody = await req.text(); try { const pubKey = await getMonobankPubKey(process.env.MONOBANK_TOKEN!); // 2. Верифікуємо підпис ECDSA SHA-256 const verifier = crypto.createVerify("SHA256"); verifier.update(rawBody); const isValid = verifier.verify(pubKey, Buffer.from(signature, "base64")); if (!isValid) { console.error("Вебхук відхилено: недійсний підпис x-sign"); return new NextResponse("Invalid signature", { status: 400 }); } // 3. Парсимо дані тільки після успішної перевірки криптографії const payload = JSON.parse(rawBody); const { invoiceId, status, amount, reference } = payload; if (status === "success") { // Тут активуємо замовлення в базі даних з перевіркою на дублікати (ідемпотентність) console.log(`Замовлення ${reference} (${invoiceId}) оплачено на суму ${amount / 100} грн`); } return new NextResponse("OK", { status: 200 }); } catch (error) { console.error("Помилка обробки вебхука:", error); return new NextResponse("Internal verification error", { status: 500 }); } }

8. Teste local de webhooks (Localhost & Cloudflare Tunnels)

Problema comum entre vibe coders: ao iniciar o servidor em http://localhost:3000, o banco não consegue entregar o webhook, pois a máquina local não possui IP público na internet.

O Monobank envia webhooks exclusivamente para endereços públicos com protocolo HTTPS válido. Para testar o ciclo completo no seu computador, configure um túnel seguro.

8.1. Início rápido do túnel (sem cadastro e gratuito)

Use o Cloudflare Tunnel (cloudflared) ou untun:

bash
# Миттєвий HTTPS-тунель на 3000 порт без встановлення утиліт npx untun@latest tunnel --port 3000

Após a execução, você receberá um endereço HTTPS público temporário, por exemplo: https://your-tunnel-name.trycloudflare.com

8.2. Configuração do webhook para teste local

No código de criação da invoice, durante o desenvolvimento, especifique o endereço obtido:

typescript
webHookUrl: "https://your-tunnel-name.trycloudflare.com/api/payment/webhook"

Agora, ao realizar um pagamento de teste, o banco enviará um webhook real diretamente para seu servidor local no terminal, permitindo confirmar que a assinatura x-sign passa na verificação.


9. UX da página de retorno e resolução de Race Condition

Quando o cliente paga a fatura via aplicativo Monobank ou Apple Pay, o banco o redireciona imediatamente para o redirectUrl (/payment-result?ref=...).

No entanto, o webhook de rede do servidor do banco para o seu servidor pode sofrer atrasos de 1–2 segundos devido às rotas de rede. Se a página de resultado verificar o status no banco de dados imediatamente, correrá o risco de mostrar: «Pedido não pago», causando pânico no cliente (os fundos foram debitados do cartão, mas o site informa ausência de pagamento).

9.1. Padrão de engenharia para resolver a condição de corrida

  1. Estado inicial de carregamento: A página abre com status neutro: «Confirmando pagamento no banco...» e indicador animado.
  2. Polling curto (Short Polling): O frontend faz até 5 requisições rápidas à sua própria API a cada 1.5 segundos (/api/orders/check-status?ref=...), aguardando o webhook alterar o status do pedido no banco para success.
  3. Fallback: Se em 8 segundos o webhook não chegar, o cliente vê a mensagem: «Pagamento recebido para processamento. Assim que o banco confirmar a transação, o acesso será liberado automaticamente».

9.2. Componente React pronto para a página de resultado

tsx
// app/payment-result/page.tsx "use client"; import { useEffect, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; export default function PaymentResultPage() { const searchParams = useSearchParams(); const router = useRouter(); const ref = searchParams.get("ref"); const [status, setStatus] = useState<"checking" | "success" | "pending" | "failed">("checking"); useEffect(() => { if (!ref) { setStatus("failed"); return; } let attempts = 0; const maxAttempts = 5; const interval = setInterval(async () => { attempts++; try { const res = await fetch(`/api/orders/status?ref=${encodeURIComponent(ref)}`); const data = await res.json(); if (data.status === "success") { clearInterval(interval); setStatus("success"); } else if (attempts >= maxAttempts) { clearInterval(interval); // Якщо вебхук затримався, переводимо в статус очікування, а не помилки setStatus(data.status === "processing" ? "pending" : "pending"); } } catch (err) { if (attempts >= maxAttempts) { clearInterval(interval); setStatus("pending"); } } }, 1500); return () => clearInterval(interval); }, [ref]); return ( <div className="max-w-md mx-auto my-16 p-8 rounded-2xl bg-neutral-900 border border-neutral-800 text-center text-white"> {status === "checking" && ( <div> <div className="w-12 h-12 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" /> <h2 className="text-xl font-semibold mb-2">Підтверджуємо оплату...</h2> <p className="text-sm text-neutral-400">Отримуємо статус транзакції від банку. Зачекайте кілька секунд.</p> </div> )} {status === "success" && ( <div> <div className="w-12 h-12 bg-emerald-500/20 text-emerald-400 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl font-bold">✓</div> <h2 className="text-xl font-semibold mb-2">Оплата успішна!</h2> <p className="text-sm text-neutral-400 mb-6">Ваше замовлення #{ref} успішно підтверджено.</p> <button onClick={() => router.push("/dashboard")} className="px-6 py-2.5 rounded-xl bg-amber-500 hover:bg-amber-400 text-black font-semibold transition"> Перейти до кабінету </button> </div> )} {status === "pending" && ( <div> <div className="w-12 h-12 bg-amber-500/20 text-amber-400 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl font-bold">⏳</div> <h2 className="text-xl font-semibold mb-2">Платіж в обробці</h2> <p className="text-sm text-neutral-400 mb-6">Кошти зарезервовано. Підтвердження надійде протягом 1–2 хвилин.</p> <button onClick={() => router.push("/")} className="px-6 py-2.5 rounded-xl bg-neutral-800 hover:bg-neutral-700 text-white font-medium transition"> На головну </button> </div> )} </div> ); }

10. Recursos avançados: pRRo embutido, hold e Wallet

O Monobank Acquiring oferece um espectro completo de ferramentas para modelos de negócios mais complexos:

Página final de pagamento Monobank Hosted Checkout com Apple Pay e cartões ЗбільшитиPágina final de pagamento Monobank Hosted Checkout com Apple Pay e cartõesPágina final de pagamento Monobank Hosted Checkout com Apple Pay e cartões

10.1. Registradora Fiscal Eletrônica (pRRo): Checkbox integrado gratuito na Caixa Monobank

Para a maioria dos FOPs ucranianos dos grupos 2 e 3, a fiscalização de vendas online é requisito legal obrigatório.

A grande vantagem do Monobank para empreendedores é a integração gratuita embutida com o serviço de pRRo Checkbox:

  • Ativação em um clique: No painel web.monobank.ua, vá às configurações do terminal criado e ative o switch «Fiscalização via Checkbox». O Monobank cria a caixa registradora e assina os recibos com sua chave KEPP gratuitamente.
  • Fiscalização automática: Se você tem um catálogo padrão de produtos com alíquota única, nem precisa alterar o código de criação do pagamento — o recibo é formado automaticamente pelo campo destination na descrição do pagamento.
  • Fiscalização avançada via API: Se você vende produtos com diferentes alíquotas de IVA ou itens sujeitos a impostos específicos com códigos UKTZED, passe o array basketOrder dentro do objeto merchantPaymInfo:
json
"merchantPaymInfo": { "reference": "order_1001", "destination": "Оплата онлайн-курсу", "customerEmails": ["client@example.com"], "basketOrder": [ { "name": "Доступ до курсу з вайбкодингу", "qty": 1, "sum": 99000, "code": "SKU-COURSE-01", "unit": "шт.", "total": 99000 } ] }

10.2. Pagamento em duas etapas (Hold)

Se você vende produtos físicos que podem estar indisponíveis no estoque, use o modo de hold:

  • Bloqueio: Passe paymentType: "hold" ao criar a invoice. Os fundos ficam congelados no cartão do comprador por até 9 dias.
  • Captura (Finalize): Após verificar a disponibilidade do produto, chame /api/merchant/invoice/finalize. Pode-se capturar tanto o valor total quanto menor (por exemplo, se um item não estava disponível).
  • Cancelamento: Se o produto não estiver disponível, chame /api/merchant/invoice/cancel — o pagamento é cancelado sem taxas bancárias para o comprador.

10.3. Salvamento de cartões e assinaturas (Wallet)

Se você lança um serviço SaaS com assinatura mensal recorrente, passe saveCardData: true ao criar a primeira invoice. Após o pagamento bem-sucedido, o webhook retornará o walletId. Use esse token para cobranças automáticas subsequentes sem que o cliente precise reinserir os dados do cartão.


11. Matriz de segurança e testes automatizados (Vitest / Jest)

Pagamento é zona de máxima responsabilidade financeira. Um erro em um botão comum causa irritação, mas um erro no gateway de pagamento leva a perdas materiais diretas ou bloqueio da caixa pelo banco.

Antes de migrar para produção, execute a matriz de testes de segurança:

Teste de SegurançaO que é verificadoComportamento esperado do sistema
1. Proteção contra adulteração de preço (Price Tampering)Cliente envia productId: "vip", mas tenta injetar na requisição amount: 100 (1 UAH)Servidor ignora o campo amount do cliente, pega o preço real da config (2490 UAH = 249 000 kopecks). Se productId for falsificado — retorna HTTP 400.
2. Bloqueio sem assinaturaRequisição POST chega ao endpoint /api/payment/webhook sem header x-signRequisição é bloqueada imediatamente com resposta HTTP 400 Bad Request. Nenhuma processamento do pedido ocorre.
3. Bloqueio de assinaturas falsasAtacante enviou assinatura fake em x-sign com status: "success"Verificação crypto.verify(SHA256, ...) retorna false. Servidor responde HTTP 400/401, acesso ou produto não são liberados.
4. Idempotência do webhook (Duplicate Delivery)Monobank enviou o mesmo webhook success duas ou três vezes seguidas por lag de redeAcesso ou produto ativam apenas uma vez. Webhook repetido não duplica entrega, respondendo ao banco HTTP 200 OK.
5. Resiliência a Race ConditionWebhook do banco chegou antes de finalizar a requisição HTTP inicial de criação da invoice no banco de dadosHandler do webhook é resiliente à ausência do registro no BD (usa UPSERT ou cria pedido on-the-fly).
6. Conformidade com Rate Limits (Buffer)Em falha do webhook, inicia-se polling de status /api/merchant/invoice/statusRequisições executadas com pausa mínima de 15 segundos, protegendo o terminal de bloqueio HTTP 429 Too Many Requests.

11.1. Conjunto pronto de testes automatizados para seu projeto

Peça ao seu agente de IA para adicionar o seguinte suite de testes ao projeto (monobank-acquiring.test.ts):

typescript
import { describe, it, expect } from "vitest"; import crypto from "crypto"; // Генерація тестової пари ECDSA secp256r1 для симуляції підписів Monobank const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1", publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); describe("Monobank Acquiring Security Tests", () => { it("Тест 1: Не дозволяє клієнту маніпулювати ціною", () => { const CATALOG: Record<string, { priceUah: number }> = { plan_pro: { priceUah: 990 } }; const clientPayload = { productId: "plan_pro", amount: 100 }; // Спроба підсунути 1 грн const safeAmount = Math.round(CATALOG[clientPayload.productId].priceUah * 100); expect(safeAmount).toBe(99000); // 990.00 грн, а не 1.00 грн }); it("Тест 2: Відхиляє вебхук без обов'язкового заголовка x-sign", () => { const headers: Record<string, string> = {}; const hasSignature = Boolean(headers["x-sign"]); expect(hasSignature).toBe(false); }); it("Тест 3: Успішно верифікує валідний цифровий підпис банку", () => { const rawPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 99000 }); const signer = crypto.createSign("SHA256"); signer.update(rawPayload); const validSignatureBase64 = signer.sign(privateKey, "base64"); const verifier = crypto.createVerify("SHA256"); verifier.update(rawPayload); const isValid = verifier.verify(publicKey, Buffer.from(validSignatureBase64, "base64")); expect(isValid).toBe(true); }); it("Тест 4: Блокує підроблений підпис або модифіковане тіло запиту", () => { const originalPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 99000 }); const tamperedPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 1000 }); const signer = crypto.createSign("SHA256"); signer.update(originalPayload); const signature = signer.sign(privateKey, "base64"); const verifier = crypto.createVerify("SHA256"); verifier.update(tamperedPayload); const isValid = verifier.verify(publicKey, Buffer.from(signature, "base64")); expect(isValid).toBe(false); }); it("Тест 5: Ідемпотентність — повторний вебхук не видає товар двічі", () => { const processedOrders = new Set<string>(); function handleOrder(invoiceId: string): { processed: boolean } { if (processedOrders.has(invoiceId)) { return { processed: false }; // Дубль відхилено } processedOrders.add(invoiceId); return { processed: true }; } expect(handleOrder("inv_001").processed).toBe(true); // Перший вебхук expect(handleOrder("inv_001").processed).toBe(false); // Повторний прихід дубліката expect(processedOrders.size).toBe(1); }); });

12. Checklist final de engenharia antes do lançamento

Verifique seu módulo de pagamento nestes 10 pontos antes de alternar para pagamentos reais:

  • Conformidade do site: Rodapé contém links para Oferta Pública, Política de Privacidade, Condições de Reembolso e dados completos do FOP com CNPJ/IE.
  • Valores em centavos: Todos os campos amount multiplicados por 100 ($1\text{ UAH} = 100\text{ kopecks}$) via Math.round.
  • Segurança da chave: Token movido para .env sob o nome MONOBANK_TOKEN e adicionado ao .gitignore.
  • Preços do backend (SSOT): Cliente envia apenas ID do produto; valor vem exclusivamente do banco ou config.
  • Tratamento rawBody: Webhook verifica buffer textual ou binário original (req.text() ou req.rawBody), evitando double parsing via JSON.stringify.
  • Proteção criptográfica: Webhook valida assinatura x-sign via chave pública do banco com algoritmo SHA256 ECDSA.
  • URL pública para webhook: Servidor acessível externamente via certificado SSL HTTPS válido (para testes locais, Cloudflare Tunnel ou ngrok servem).
  • Idempotência: Chegada repetida do mesmo webhook não causa dupla entrega de produto ou assinatura.
  • UX da página de resultado: Em /payment-result, configurado loader de espera e polling curto de status para proteger usuário de race condition.
  • Pagamento real de 1–5 UAH: Transação de teste realizada com cartão real, verificando débito e exibição no painel do banco.

Recursos úteis e arquivos para download

Este guia é totalmente gratuito. Se ele te poupou uma noite, você pode apoiar o crescimento do projeto.
Apoiar o autor