TEF no caixa: como não cobrar duas vezes quando o PDV trava no aprovado
Ligue a venda e a transação de cartão pelo identificador da TEF: grave antes e depois de cobrar, confirme só com a venda gravada e resolva pendências antes de cobrar de novo.
Caixa 3 do mercadinho, sábado, 11h40. A cliente passa o cartão para pagar R$ 187,40, o pinpad mostra "aprovada" e o PDV congela. A operadora reinicia o programa, a venda não aparece concluída, e ela cobra de novo. A cliente sai com duas cobranças no extrato. A resposta curta: a venda e a transação de cartão precisam estar ligadas por um registro gravado antes de cobrar e atualizado com o identificador da transação logo depois da aprovação. Ao reabrir, o PDV olha esse registro e resolve a pendência antes de iniciar qualquer cobrança nova.
Este guia monta esse controle com o caso do caixa 3, em C#, usando um adaptador seu sobre a biblioteca do fornecedor de TEF.
Como a transação de TEF funciona por baixo
TEF é a conversa entre o seu PDV e a rede de cartão, intermediada por um software do fornecedor. Na maioria das soluções, essa conversa tem mais de um passo: o PDV pede o pagamento, a rede aprova e devolve dados como o NSU e o código de autorização, o PDV conclui a venda e só então manda a confirmação. Enquanto não chega a confirmação, a transação fica pendente. Muitas soluções desfazem a pendência sozinhas se a confirmação não vier, por exemplo na próxima transação ou ao reiniciar. Confira no manual do seu fornecedor como ele trata esse caso, porque o seu sistema precisa concordar com ele.
Esse desenho existe justamente para o PDV que trava. Se a venda não foi gravada, a transação não deveria ser confirmada. O problema é que muita automação comercial ignora o passo de confirmação, não guarda nada antes de cobrar e trata o retorno da biblioteca como o fim da história. Aí o travamento entre "aprovada" e "venda gravada" vira cobrança órfã.
Os estados de um pagamento
Modele o pagamento como uma máquina de estados numa tabela sua, com uma linha por tentativa:
iniciado: gravado antes de chamar a TEF. Se o PDV cair aqui, o resultado é desconhecido.aprovado: a rede aprovou e o NSU está gravado, mas a venda ainda não foi concluída.confirmado: a venda foi gravada e a confirmação foi enviada.desfeito: a venda não foi concluída e a transação foi desfeita.negado: a rede recusou. Nada a resolver.
CREATE TABLE pagamento_tef (
id INT AUTO_INCREMENT PRIMARY KEY,
venda_id INT NOT NULL,
valor_centavos INT NOT NULL,
status VARCHAR(20) NOT NULL,
nsu VARCHAR(20) NULL,
autorizacao VARCHAR(20) NULL,
rede VARCHAR(40) NULL,
criado_em DATETIME NOT NULL,
atualizado_em DATETIME NOT NULL
);
Valor em centavos, inteiro, para não somar R$ 187,40 com erro de arredondamento; o guia de dinheiro em centavos explica por quê. Não existe coluna para número do cartão nem trilha: o PDV não precisa disso, e guardar é um risco que você não quer.
Como cobrar sem duplicar
- Antes de chamar a TEF, grave a linha em
iniciadocom venda e valor. - Chame a TEF. Se aprovar, grave
aprovadocom NSU, autorização e rede imediatamente. - Conclua a venda: grave itens, pagamento e documento fiscal.
- Só depois da venda gravada, envie a confirmação e marque
confirmado. - Se a venda falhar, desfaça a transação e marque
desfeito. - Ao abrir o PDV, antes da primeira venda, resolva toda linha em
iniciadoouaprovado.
O fluxo em C#
A interface ITef é sua. Ela esconde a biblioteca do fornecedor, que tem nomes e parâmetros próprios descritos no manual dele.
public interface ITef
{
Task<RespostaTef> PagarAsync(int pagamentoId, long valorCentavos);
Task ConfirmarAsync(RespostaTef resposta);
Task DesfazerAsync(RespostaTef resposta);
}
public record RespostaTef(bool Aprovada, string? Nsu, string? Autorizacao, string? Rede);
public async Task<bool> CobrarAsync(Venda venda)
{
var pagamentoId = await pagamentos.CriarAsync(venda.Id, venda.TotalCentavos, "iniciado");
var resposta = await tef.PagarAsync(pagamentoId, venda.TotalCentavos);
if (!resposta.Aprovada)
{
await pagamentos.MarcarAsync(pagamentoId, "negado");
return false;
}
await pagamentos.MarcarAprovadoAsync(pagamentoId, resposta.Nsu, resposta.Autorizacao, resposta.Rede);
try
{
await vendas.ConcluirAsync(venda);
}
catch
{
await tef.DesfazerAsync(resposta);
await pagamentos.MarcarAsync(pagamentoId, "desfeito");
throw;
}
await tef.ConfirmarAsync(resposta);
await pagamentos.MarcarAsync(pagamentoId, "confirmado");
return true;
}
Repare na ordem: a linha aprovado com o NSU é gravada antes de qualquer outra coisa. Se o caixa 3 congelar um milissegundo depois do "aprovada", o banco já sabe que existe uma transação de R$ 187,40 com aquele NSU.
O que o PDV faz ao reabrir
Na inicialização, o PDV procura linhas pendentes e não deixa vender até resolver cada uma:
aprovadocom a venda concluída: o travamento foi entre gravar a venda e confirmar. Confirme, se o manual da sua TEF permite confirmar depois de reiniciar, ou registre o que a TEF já fez sozinha.aprovadosem venda concluída: desfaça (ou registre que a TEF desfez) e avise a operadora que a cobrança anterior não valeu.iniciado: o resultado é desconhecido. Mostre na tela "Existe uma cobrança de R$ 187,40 sem resultado. Consulte antes de cobrar de novo", e use a função administrativa da TEF ou o comprovante para descobrir o que houve.
No sábado do caixa 3, a tela teria mostrado a pendência com o NSU, e a operadora teria concluído a venda existente em vez de iniciar outra cobrança. É a mesma ideia de idempotência no pagamento do comércio eletrônico, aplicada ao balcão.
Cancelamento é estorno, não delete
Quando a cliente desiste depois de confirmado, o caminho é a função de cancelamento da TEF, que normalmente pede o NSU e a data da transação original. Apagar a linha da venda no banco não devolve dinheiro a ninguém e ainda apaga a prova da cobrança. Grave o cancelamento como um novo registro ligado ao pagamento original.
Erros comuns
- Chamar a TEF sem gravar nada antes. Se o PDV cai durante a transação, não sobra nem a informação de que houve uma tentativa.
- Gravar o NSU só no fim da venda. O travamento entre aprovar e gravar apaga o único dado que liga a cobrança à venda.
- Confirmar antes de gravar a venda. A cobrança fica definitiva para uma venda que não existe no sistema.
- Permitir nova cobrança com pendência aberta. A operadora cobra de novo por precaução, e a cliente paga duas vezes.
- Cancelar apagando a venda. O dinheiro continua cobrado e o registro que provaria isso desaparece.
- Logar a resposta inteira da biblioteca. Dados do cartão podem acabar em arquivo de texto; veja o guia de log sem dado sensível.
Checklist
- Toda cobrança começa com uma linha gravada em
iniciado. - NSU e autorização são gravados imediatamente após a aprovação.
- A confirmação só é enviada depois que a venda está gravada.
- Falha ao concluir a venda desfaz a transação.
- O PDV não vende enquanto houver pagamento pendente sem resolver.
- Cancelamento usa a função da TEF e gera registro próprio.
- Nenhum dado de cartão vai para banco ou log.
- Você sabe, pelo manual do fornecedor, o que a TEF faz com transação não confirmada.
Se a sua TEF é chamada por uma DLL, a forma de declarar as funções também importa; veja chamar uma DLL nativa sem adivinhar o nome. Faça agora o teste no ambiente de homologação da sua TEF: aprove uma transação e mate o processo do PDV antes de concluir a venda. Ao reabrir, o seu sistema sabe qual cobrança já existia?