Programação Por Equipe DevBras

Capa do artigo: Pagamento no site: o número do cartão não precisa passar pelo seu servidor

Pagamento no site: o número do cartão não precisa passar pelo seu servidor

Monte um checkout transparente com tokenização no provedor, sem número de cartão no seu banco ou log, com valor calculado no servidor e status pago vindo do webhook.

O checkout ficou bonito, dentro do layout da loja, e o formulário mandava número, validade e CVV para a sua API, que repassava ao gateway. Meses depois, alguém procurando um bug encontrou números de cartão completos no log de requisições, guardados há semanas. A resposta curta: o checkout pode ser transparente sem que o número do cartão toque no seu servidor. O campo do cartão fala direto com o provedor de pagamento, que devolve um token. A sua API recebe só esse token, cria a cobrança e guarda o id dela. O pedido vira pago quando o webhook confirmado do provedor chega, não quando a tela de obrigado aparece.

Guardar número e CVV no seu banco não é um detalhe de implementação. É outro produto, com outro nível de exigência de segurança e de auditoria. Para quase toda loja, a resposta certa é não guardar.

Por que o cartão não deve passar por você

O setor de cartões segue o padrão PCI DSS, que define o que precisa ser feito por quem armazena, processa ou transmite dados de cartão. Quanto mais o dado passa pelos seus sistemas, maior o escopo: servidores, logs, backups, rede, pessoas com acesso. O código de segurança (CVV) tem uma regra ainda mais dura: não pode ser armazenado depois da autorização, nem criptografado.

Na prática, o vazamento raramente vem de alguém decidir guardar o cartão. Vem do caminho. O corpo da requisição vai para o log de acesso detalhado, para a ferramenta de rastreamento de erro, para o backup do log, para o dump que alguém copiou para investigar um problema. Cada lugar desses passa a ser um repositório de cartões, sem que ninguém tenha escrito uma linha de código para isso. O assunto aparece também em log sem senha.

A tokenização corta o problema na origem. O provedor fornece um script ou um campo embutido (normalmente um iframe) que coleta os dados do cartão no navegador e os envia direto para os servidores dele. O que volta para a sua página é um token de uso único que representa aquele cartão. Esse token não serve para nada fora da sua conta no provedor.

Isso reduz bastante o seu escopo de PCI DSS, mas não o elimina: a página que carrega o campo ainda é sua, e um script malicioso nela poderia capturar o que é digitado. O tipo de integração, iframe hospedado pelo provedor ou script na sua página, muda o que é exigido de você. Confirme com o provedor e com o adquirente qual se aplica ao seu caso. Este texto não substitui a orientação de um profissional habilitado em conformidade PCI.

Como montar o fluxo com token

  1. Crie o pedido no servidor com itens, frete e total calculados por você, em centavos, como em dinheiro em centavos. O navegador nunca informa o valor.
  2. Carregue o componente de cartão do provedor na página de pagamento, seguindo a documentação dele. Os campos de número, validade e CVV pertencem ao componente, não ao seu formulário.
  3. Receba o token que o componente devolve e envie para a sua API junto com o id do pedido.
  4. Crie a cobrança no servidor com o token, o valor do pedido e uma chave de idempotência, se o provedor aceitar.
  5. Guarde o id da cobrança e o status inicial retornados. Mostre à pessoa que o pagamento está em processamento.
  6. Marque o pedido como pago só no webhook assinado do provedor, conferindo id da cobrança, valor e status.

Exemplo do passo 4 em Express. O endereço e os nomes de campos do provedor são fictícios: troque pelos da documentação do seu.

app.post('/pedidos/:id/pagar', exigirLogin, express.json(), async (req, res) => {
  const pedido = await buscarPedido(req.params.id, req.usuario.sub);
  if (!pedido || pedido.status !== 'aguardando_pagamento') {
    return res.status(409).json({ erro: 'Pedido não pode ser pago agora.' });
  }

  const resposta = await fetch('https://api.provedor-pagamento.example/v1/cobrancas', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.PAGAMENTO_CHAVE,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'pedido-' + pedido.id + '-' + pedido.tentativa
    },
    body: JSON.stringify({
      valor_centavos: pedido.totalCentavos,
      referencia: String(pedido.id),
      token_cartao: req.body.tokenCartao
    })
  });

  const cobranca = await resposta.json();
  if (!resposta.ok) {
    return res.status(402).json({ erro: 'Pagamento não autorizado.' });
  }

  await salvarCobranca(pedido.id, cobranca.id, cobranca.status);
  return res.status(202).json({ status: 'processando' });
});

Repare no que não aparece: número, validade ou CVV. O valor vem de pedido.totalCentavos, lido do banco, e não do corpo da requisição. A chave de idempotência inclui um contador de tentativas do pedido, que você incrementa quando a pessoa tenta com outro cartão; assim, um clique duplo reaproveita a mesma cobrança, e uma nova tentativa legítima gera outra. O porquê disso está em idempotência no pagamento. As funções buscarPedido e salvarCobranca são da sua aplicação.

O status pago vem do webhook

A resposta da criação pode dizer aprovado, em análise ou recusado, e análises antifraude mudam o status depois. O retorno do navegador para a página de obrigado pode ser repetido, forjado ou simplesmente não acontecer, se a pessoa fechar a aba. O único sinal confiável é o aviso do provedor para a sua rota de webhook, com assinatura conferida e tratado de forma idempotente, como em webhook com segredo. Ao recebê-lo, confira se o valor da cobrança bate com o do pedido antes de liberar.

O que guardar e o que não guardar

Guarde o id da cobrança no provedor, o status, o valor, a data e, se quiser mostrar na tela "cartão final 4242", a bandeira e os quatro últimos dígitos que o provedor devolver. Não guarde número completo, validade nem CVV, em nenhuma tabela, log ou campo de observação. Estorno é uma chamada ao provedor usando o id da cobrança, não um DELETE no pedido. Se a loja precisa de compra recorrente ou "comprar com um clique", use o recurso de cartão salvo do próprio provedor, que devolve um identificador reutilizável.

Erros comuns

  • Formulário próprio que manda o cartão para a sua API. O número passa a existir em logs, rastreadores de erro e backups.
  • Valor enviado pelo navegador. Qualquer pessoa edita a requisição e paga um centavo por um pedido de mil reais.
  • Marcar como pago na página de obrigado. Um pagamento recusado em análise libera o pedido do mesmo jeito.
  • Webhook sem conferir valor e assinatura. Um aviso falso ou de outra cobrança marca o pedido como pago.
  • Sem idempotência na criação da cobrança. O clique duplo cobra duas vezes do cartão da pessoa.
  • Guardar CVV "para facilitar a próxima compra". É proibido pelo padrão do setor e transforma o banco num alvo valioso.

Checklist

  • Os campos do cartão pertencem ao componente do provedor, não ao seu formulário.
  • A sua API recebe só o token e o id do pedido.
  • O valor da cobrança vem do banco.
  • A criação da cobrança usa chave de idempotência.
  • O status pago é definido apenas pelo webhook assinado.
  • O banco guarda id da cobrança, status, valor e, no máximo, bandeira e últimos quatro dígitos.
  • Uma busca nos logs por sequências longas de dígitos não encontra números de cartão.

Rode grep -rEn '[0-9]{13,19}' /var/log/sua-app/ no servidor. Vai aparecer falso positivo, como carimbos de tempo em milissegundos, mas se algum resultado começar com 4 ou 5 e tiver 16 dígitos, olhe com atenção: o cartão pode ter passado por você. E a pergunta para o código: o status pago do seu pedido nasce do webhook ou da tela de obrigado?

Avalie este artigo

Referência: https://dev.pagseguro.uol.com.br/v1/page/quickstart-api-checkout-transparente