Pix do lado de quem integra: checkout, estados e eventos
Todo mundo no time já pagou com Pix. Integrar é outra história: a confirmação chega depois, o código expira e o cliente sai da sua página. Veja como desenhar esse fluxo, quais eventos tratar e como testar sem mexer em dinheiro.
Escrito por Time KrokoPayPublicado em 7 min de leitura
Seu time não precisa que ninguém explique o que é Pix: todo mundo já leu um QR Code na fila do caixa ou colou um código no app do banco para fechar uma compra online. O problema é que conhecer o Pix como usuário não ensina muita coisa sobre recebê-lo dentro de um produto. Do lado de quem integra, o Pix é um pagamento assíncrono, que acontece fora da sua página e tem prazo de validade.
Este artigo é para quem vai colocar o Pix no checkout de um SaaS, loja ou produto digital e quer acertar de primeira: o que muda na arquitetura, quais estados a cobrança atravessa na KrokoPay, quais eventos ouvir e os detalhes de interface que fazem a pessoa terminar de pagar.
Do ponto de vista técnico, o Pix tem uma característica que define tudo: quem inicia o pagamento é o pagador, no app da instituição onde ele tem conta. O seu sistema só publica um pedido de pagamento, em forma de QR Code ou de código copia e cola, e espera. Você nunca recebe dados de conta, senha ou cartão.
Se alguém do time já integrou cartão, vale deixar a diferença explícita. No cartão, você puxa o dinheiro: recebe os dados e pede autorização, e a resposta volta na mesma requisição. No Pix, o cliente empurra o dinheiro: você mostra o pedido e fica aguardando uma confirmação que acontece num lugar que você não enxerga.
- Não existe formulário de pagamento. Além dos dados básicos de quem paga, a etapa de pagamento é um QR Code, um código para copiar e instruções claras.
- O cliente sai da sua página. No celular, ele troca para o app do banco; no computador, pega o celular. A página precisa aguentar ficar em segundo plano, ser recarregada ou reaberta.
- A confirmação é assíncrona. Criar a cobrança não diz nada sobre o pagamento. O resultado chega depois, por evento, e a interface precisa saber esperar.
- O código expira. Cada pedido vale por um tempo limitado. Depois disso, a cobrança não pode mais ser paga e é preciso gerar outra.
Na KrokoPay, você não precisa montar nada disso na sua interface. Seu servidor cria a cobrança, você redireciona o cliente para o checkout_url e o checkout hospedado cuida do QR Code, do copia e cola, da contagem regressiva e da tela de espera. O que continua com você é o lado do servidor: saber quando a cobrança foi paga e agir uma única vez.
Toda cobrança na KrokoPay nasce pending e termina em um único desfecho. No Pix, os caminhos são poucos: o cliente paga dentro do prazo ou não paga.
| Status | O que significa | Evento |
|---|---|---|
pending | A cobrança existe. Quando o cliente escolhe Pix, ela ganha um expires_at. | charge.created |
paid | O pagador confirmou o Pix no app e o pagamento foi recebido. | charge.paid |
expired | Ninguém pagou antes do expires_at. A cobrança está encerrada. | charge.expired |
refunded / partially_refunded | Você devolveu todo ou parte do valor de uma cobrança paga. | charge.refunded |
Repare que o Pix não tem o caminho failed, comum no cartão recusado. Ou o dinheiro é enviado ou não é, então o desfecho que pede cuidado é o expired. A tabela completa, com os casos de cartão, está em Criando cobranças.
Inscreva seu endpoint de webhook em charge.paid, charge.expired e charge.refunded. Libere o pedido no charge.paid, devolva o estoque reservado no charge.expired e ajuste acesso ou financeiro no charge.refunded. Um handler enxuto fica assim:
// Chamado depois de validar a assinatura e deduplicar pelo id do evento.async function handleChargeEvent(event: { id: string; type: string; data: { object: Charge } }) { const charge = event.data.object; const order = await orders.findByChargeId(charge.id); if (!order) return; switch (event.type) { case "charge.paid": await orders.markPaid(order.id, charge.paid_at); await fulfil(order.id); // idempotente: pode rodar duas vezes break; case "charge.expired": await orders.markExpired(order.id); await stock.release(order.id); break; case "charge.refunded": await orders.recordRefund(order.id, charge.refunded_amount, charge.status); break; }}Atenção: Não confie no redirecionamento
GET /v1/charges/{id} feito pelo seu servidor), nunca o navegador.Como o trabalho de pagar acontece em outro app, o papel do checkout é orientar e tranquilizar. São estes os detalhes que cuidamos no checkout da KrokoPay, e os que vale revisar em qualquer tela que você construir em volta dele.
Ninguém consegue ler um QR Code mostrado no próprio celular. Em telas pequenas, o botão de copiar o código é a ação principal, com uma confirmação visível de que o código foi copiado e um lembrete de onde colar. Em telas maiores, o QR Code vem primeiro, porque o celular vira o leitor. As duas opções continuam disponíveis em qualquer tela: tem gente que prefere a outra.
- Mostre quanto tempo o código ainda vale, calculado a partir do
expires_at, e não da hora em que a página carregou. - Depois de mostrar o código, exiba um estado de espera tranquilo, avisando que a página se atualiza sozinha quando o pagamento chegar. Anuncie as mudanças também para leitores de tela.
- Não use um botão "Já paguei" como mecanismo de confirmação. No máximo, ofereça uma forma de verificar de novo.
- Garanta que recarregar a página mostre a mesma cobrança pendente e o tempo restante, sem criar uma cobrança nova.
Quando a contagem chegar a zero, diga isso com clareza e mostre o próximo passo. Uma cobrança expirada não aceita nova tentativa, então o caminho é uma cobrança nova: mande o cliente de volta para o link de pagamento de origem ou crie outra cobrança no servidor, com uma nova chave de idempotência. Mantenha o carrinho intacto para que tentar de novo custe um toque.
Dá para percorrer o fluxo inteiro sem app de banco e sem dinheiro de verdade. No ambiente de teste, o checkout mostra um QR Code e um código copia e cola fictícios, junto com um controle que simula a confirmação do pagador. No modo de teste, o código Pix expira 15 minutos depois de gerado; o prazo em produção ainda será definido.
- Crie uma cobrança com a chave de teste (
kp_test_sk_…) emethods: ["pix"], como em Criando cobranças. - Abra o
checkout_url, escolha Pix e simule o pagamento. Confira se o seu endpoint recebe ocharge.paide libera o pedido uma única vez. - Crie outra cobrança e deixe a contagem terminar. Confira se o
charge.expireddevolve o que estava reservado e se o cliente consegue recomeçar. - Faça o reembolso de uma cobrança de teste paga e veja se os seus registros acompanham o
charge.refunded.
Depois, teste os caminhos desconfortáveis: feche a aba logo após pagar, recarregue durante a espera, abra o mesmo checkout em dois aparelhos. Se no fim os seus registros estiverem certos, a integração está pronta para o jeito como as pessoas realmente pagam.
- No Pix, quem inicia o pagamento é o cliente, no app do banco, por QR Code ou copia e cola.
- O checkout vira uma passagem guiada mais uma sala de espera; a confirmação chega depois.
- Trate
pending → paidepending → expired, e use o webhook como fonte da verdade. - Priorize o copia e cola no celular e o QR Code no computador, mostre uma contagem real e faça a expiração ter saída.
Quer ver funcionando antes de escrever código? A página do Pix tem uma demonstração ao vivo do checkout.
