Garu

2026-09-09

v0.21.1 — Venda no cartão depois das 21h aprova na hora (e não vira cobrança repetida)

cartaobugfixcheckoutapiseguranca

O que mudou

Venda no cartão à noite aprova na hora

Quem vendia no cartão depois das 21h tinha um problema invisível: a cobrança saía daqui carimbada com a data do dia seguinte. A adquirente não manda uma cobrança para a operadora do cartão antes da data marcada — ela guarda e processa no lote da madrugada. Então a venda das 21h só era autorizada por volta das 5h da manhã.

Para o comprador isso parecia falha. Ele finalizava a compra, não via nada na fatura, concluía que não tinha dado certo e pagava de novo. No caso que revelou o problema, uma compradora pagou três vezes a mesma mensalidade de R$ 288,00 — as três cobranças caíram juntas na fatura dela às 5h do dia seguinte.

A causa era de fuso: a data vinha de uma função que sempre devolve UTC. Como o Brasil é UTC−3, a partir das 21h o UTC já virou o dia. A data agora é calculada em horário de Brasília, como o resto do sistema já fazia nas cobranças agendadas e no carnê.

O bug existia desde o primeiro dia da plataforma e atingia toda venda no cartão entre 21h e meia-noite.

O botão de pagar trava enquanto processa

O botão "Pagar agora" do checkout continuava clicável durante o processamento da cobrança. Quem tocasse duas vezes gerava duas cobranças reais. Agora ele desabilita enquanto a cobrança está em andamento.

A API recusa cobrança repetida

Para quem integra via API: uma segunda cobrança com o mesmo comprador, produto, forma de pagamento, valor e número de parcelas dentro de 60 segundos devolve a cobrança original, em vez de criar outra. Isso protege quem tem retry automático e cujo cliente HTTP desiste antes da nossa resposta.

A trava vale para chamadas autenticadas por chave de API. Tentativas negadas ou canceladas não entram na conta — nesses casos o comprador precisa mesmo poder tentar de novo.

Cobranças voltaram a ser rastreáveis até o produto

As cobranças enviadas à adquirente carregavam a referência do produto como undefined, o que quebrava a reconciliação entre o webhook recebido e o produto vendido. A referência agora vai correta.

Para quem integra

Se você usa o SDK @garuhq/node, atualize para a 5.0.0. Até a 4.1.0 o SDK inventava uma chave de idempotência aleatória a cada chamada quando você não passava uma — o que não protegia nada, porque uma chave de idempotência só funciona se a mesma chave voltar na repetição. A 5.0.0 não inventa mais: passe idempotencyKey derivado de algo estável do seu domínio (o id do pedido, o id do agendamento) para ter proteção de verdade.

A versão é major porque o comportamento na rede muda: sem idempotencyKey, nenhum header vai. Nenhum endpoint exige o header, então a maioria das integrações não precisa mudar código.

await garu.charges.create({
  productId,
  paymentMethod: 'creditCard',
  customer,
  idempotencyKey: `pedido:${pedido.id}:cobranca`
});