Total Exibido Versus Total Enviado: Lendo um Formulário de Checkout
O desafio
Uma página de checkout mostra um resumo limpo e confiante: um plano, um preço, um total a pagar hoje. O número que o cliente lê e o número que o serviço de cobrança realmente cobra são calculados em dois lugares completamente diferentes, e nesta página eles divergiram. Olhe primeiro a página renderizada e anote o total anunciado, depois mude para o código-fonte e leia todos os campos que o formulário vai de fato enviar. Um comentário na marcação explica como o servidor soma a cobrança. Descubra quanto este formulário realmente cobra quando o cliente aperta Confirmar e pagar, e envie esse número.
O que você vai aprender
- Comparar o que uma página renderiza com o que seu formulário realmente envia
- Identificar campos de input ocultos e os valores que eles carregam
- Aplicar uma regra de soma do lado do servidor, explicitamente informada, aos dados do formulário
- Diferenciar campos com preço de campos de metadados em um formulário
- Explicar por que preços nunca devem ser calculados a partir de valores enviados pelo cliente
Habilidades testadas
Pré-requisitos
- Leitura básica de marcação HTML de formulários
- Entender que inputs ocultos também são enviados
Como funciona
Uma página de checkout é dois cálculos vestindo uma única interface. O template renderiza um total para um humano ler, e o formulário monta um conjunto de campos para um serviço de cobrança processar. Nada obriga esses dois números a coincidir, e quando eles se afastam a interface continua parecendo correta, porque a parte que o cliente consegue ver é a parte que nunca foi autoritativa.
A pré-visualização mostra uma única assinatura e Total due today: 290 EUR. Essa string vive no corpo da página. Não é um input, então nunca é enviada e o servidor nunca a vê.
O código-fonte mostra o que de fato vai acontecer. Um comentário enuncia a regra de cobrança sem rodeios: o serviço cobra plan_price mais cada campo addon_* mais promo_credit. Quatro campos batem com essa regra: plan_price em 290, addon_priority_support em 120, addon_onboarding em 450 e promo_credit em -60. Somando-os mantendo o sinal, o resultado é 800. Três outros campos ocultos, plan_id, currency e csrf, não carregam preço e ficam fora da regra.
Ou seja, a página anuncia 290 e o formulário cobra 800. Nenhum dos dois componentes está com defeito; eles simplesmente discordam, e essa discordância é invisível a partir da página renderizada. A mesma arquitetura falha a favor do cliente com a mesma facilidade, porque um formulário que carrega os próprios preços é um formulário que o cliente pode editar antes de enviar. Exibir um preço é uma questão de apresentação; decidir um preço é uma questão do servidor, e esta página confundiu as duas coisas.
Erros comuns
- Responder 290. Essa é a string renderizada. Não é um campo de formulário e nunca é enviada.
- Responder 860. Isso descarta o sinal de
promo_credit, que é-60e reduz a cobrança. - Responder 570. Isso soma o plano e apenas um addon, deixando de fora
addon_onboarding. Os dois camposaddon_*contam. - Incluir plan_id, currency ou csrf. Eles são ocultos, mas não carregam preço, e a regra enunciada não os cobre.
- Parar na pré-visualização. Todo campo com preço nesta página está oculto, então a visualização renderizada sozinha não responde à pergunta.
- Somar o IVA. O rodapé afirma que os preços já o incluem, e nenhum campo de imposto é enviado.
Como se proteger
A regra é simples e absoluta: o navegador pode exibir um preço, mas nunca pode fornecer um.
- Envie identificadores, não valores. O formulário deve enviar
plan_ide uma lista de ids de addons selecionados, e o servidor deve buscar cada preço no próprio catálogo. - Calcule o total no servidor e renderize o valor exibido a partir desse mesmo cálculo, para que um número nunca possa se afastar do outro.
- Trate qualquer campo com preço vindo do cliente como entrada não confiável, e rejeite de vez uma requisição que carregue um, em vez de tentar validá-lo.
- Amarre a cotação. Emita uma cotação ou id de carrinho do lado do servidor com o valor já anexado, e cobre com base nesse id.
- Reconcilie continuamente, alertando quando o valor cobrado difere do valor cotado para o mesmo carrinho, o que pega essa classe de bug em produção.
- Cubra isso em testes, garantindo que a cobrança gerada por um checkout seja igual ao total que a página exibiu.