Total affiché contre total soumis : lire un formulaire de paiement
Le défi
Une page de paiement affiche un récapitulatif net et rassurant : un abonnement, un prix, un total à payer aujourd'hui. Le nombre que lit le client et le nombre que facture réellement le service de facturation sont calculés à deux endroits totalement différents, et sur cette page ils ont divergé. Regardez d'abord la page rendue et notez le total annoncé, puis passez en affichage du code source et lisez tous les champs que le formulaire va réellement soumettre. Un commentaire dans le balisage explique comment le serveur additionne le montant. Déterminez ce que ce formulaire facture vraiment lorsque le client appuie sur Confirmer et payer, et soumettez ce nombre.
Ce que tu vas apprendre
- Comparer ce qu'une page affiche à ce que son formulaire soumet réellement
- Identifier les champs cachés d'un formulaire et les valeurs qu'ils transportent
- Appliquer une règle de sommation côté serveur énoncée aux données d'un formulaire
- Distinguer les champs facturés des champs de métadonnées dans un formulaire
- Expliquer pourquoi les prix ne doivent jamais être calculés à partir de valeurs soumises par le client
Compétences testées
Prérequis
- Lire un balisage HTML de formulaire simple
- Comprendre que les champs cachés sont soumis
Comment ça marche
Une page de paiement, c'est deux calculs qui portent une seule interface. Le gabarit affiche un total pour qu'un humain le lise, et le formulaire assemble un ensemble de champs pour qu'un service de facturation facture. Rien n'oblige ces deux nombres à concorder, et quand ils divergent l'interface continue de paraître correcte, parce que la partie que le client peut voir est la partie qui n'a jamais fait autorité.
L'onglet Aperçu affiche un seul abonnement et Total due today: 290 EUR. Cette chaîne vit dans le corps de la page. Ce n'est pas un champ de saisie, donc elle n'est jamais soumise et le serveur ne la voit jamais.
L'affichage du code source montre ce qu'il en est réellement. Un commentaire énonce la règle de facturation sans détour : le service facture plan_price plus chaque champ addon_* plus promo_credit. Quatre champs correspondent à cette règle : plan_price à 290, addon_priority_support à 120, addon_onboarding à 450 et promo_credit à -60. Les additionner en conservant le signe donne 800. Trois autres champs cachés, plan_id, currency et csrf, ne portent aucun prix et sortent du cadre de la règle.
La page annonce donc 290 et le formulaire facture 800. Aucun des deux composants ne dysfonctionne : ils sont simplement en désaccord, et ce désaccord est invisible depuis la page affichée. La même architecture peut tout aussi bien échouer en faveur du client, car un formulaire qui transporte ses propres prix est un formulaire que le client peut modifier avant de le soumettre. Afficher un prix relève de la présentation, décider un prix relève du serveur, et cette page a confondu les deux.
Erreurs fréquentes
- Répondre 290. C'est la chaîne affichée. Ce n'est pas un champ de formulaire et elle n'est jamais soumise.
- Répondre 860. Cela revient à ignorer le signe de
promo_credit, qui vaut-60et réduit le montant facturé. - Répondre 570. Cela additionne le plan et un seul addon en oubliant
addon_onboarding. Les deux champsaddon_*comptent. - Inclure plan_id, currency ou csrf. Ces champs sont cachés mais ne portent aucun prix, et la règle énoncée ne les couvre pas.
- S'arrêter à l'onglet Aperçu. Chaque champ facturé de cette page est caché, donc la vue affichée seule ne permet pas de répondre à la question.
- Ajouter la TVA. Le pied de page précise que les prix l'incluent déjà, et aucun champ de taxe n'est soumis.
Comment s'en protéger
La règle est simple et absolue : le navigateur peut afficher un prix, mais il ne doit jamais en fournir un.
- Envoyer des identifiants, pas des montants. Le formulaire doit soumettre
plan_idet une liste d'identifiants d'addons sélectionnés, et le serveur doit chercher chaque prix dans son propre catalogue. - Calculer le total côté serveur et afficher le chiffre à partir de ce même calcul, de sorte qu'un nombre ne puisse jamais s'écarter de l'autre.
- Traiter tout champ facturé provenant d'un client comme une entrée non fiable, et rejeter toute requête qui en porte un plutôt que d'essayer de le valider.
- Lier le devis. Émettre côté serveur un devis ou un identifiant de panier auquel le montant est attaché, et facturer sur cet identifiant.
- Réconcilier en continu en alertant lorsque le montant facturé diffère du montant indiqué par le devis pour le même panier, ce qui permet de détecter cette classe de bug en production.
- Couvrir le cas par des tests, en vérifiant que le montant facturé par un paiement est égal au total affiché par la page.