Programação Por Equipe DevBras

Capa do artigo: TEF no caixa: como não cobrar duas vezes quando o PDV trava no aprovado

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

  1. Antes de chamar a TEF, grave a linha em iniciado com venda e valor.
  2. Chame a TEF. Se aprovar, grave aprovado com NSU, autorização e rede imediatamente.
  3. Conclua a venda: grave itens, pagamento e documento fiscal.
  4. Só depois da venda gravada, envie a confirmação e marque confirmado.
  5. Se a venda falhar, desfaça a transação e marque desfeito.
  6. Ao abrir o PDV, antes da primeira venda, resolva toda linha em iniciado ou aprovado.

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:

  • aprovado com 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.
  • aprovado sem 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?

Avalie este artigo

Referência: http://www.linhadecodigo.com.br/artigo/242/automacao-comercial-com-vb-vbnet-e-csharp-ecf-e-tef-parte-i-de-iv.aspx