Ce qu'un audit de notre propre système de paiement a révélé
Deux failles critiques, et aucune là où nous aurions cherché. Un compte rendu ouvert sur des erreurs corrigées depuis.

En août 2026, nous avons passé notre système de paiement au crible de fond en comble. Pas parce que quelque chose s'était produit, mais parce qu'il venait d'être mis en ligne et que nous voulions savoir ce qui nous avait échappé.
Il nous avait échappé pas mal de choses. Cet article nomme les failles trouvées. Tout ce qui est décrit ici est corrigé, et la plupart de ces points figuraient de toute façon déjà dans le changelog public.
L'erreur de modélisation à l'origine des deux failles critiques
Quand quelqu'un achète Premium chez nous, une fiche client est créée chez notre prestataire de paiement. Nous enregistrions cette fiche sous l'ID du serveur, puisque Premium atterrit après tout sur un serveur.
C'est exactement le mauvais modèle, et c'était la racine des deux failles les plus graves.
Une fiche client appartient à un acheteur. Elle porte son moyen de paiement, son historique de factures et, dès que quelqu'un paie pour deux serveurs, plusieurs abonnements. La rattacher à l'ID du serveur signifie que plusieurs personnes pointent vers la même ligne.
Faille 1 : les données client d'un autre, modifiables sans payer
L'achat est ouvert à n'importe quel membre d'un serveur, et la fonction qui récupère ou crée la fiche client s'exécute avant le paiement. Elle trouvait la ligne par l'ID du serveur et la réécrivait silencieusement au nom du nouvel intéressé, adresse e-mail chez le prestataire de paiement comprise.
En pratique, cela voulait dire : un clic sur un serveur où quelqu'un avait acheté un jour suffisait. Ensuite, les factures de tous les abonnements de cet acheteur d'origine seraient parties vers l'adresse e-mail de l'inconnu. Sans paiement, sans confirmation.
Corrigé en vérifiant, avant toute réutilisation, si la fiche appartient vraiment à ce compte. En cas de différence, une fiche distincte est créée et l'autre reste intacte.
Faille 2 : le portail de facturation exposait les abonnements des autres
La même erreur de modélisation, un niveau plus loin. Le contrôle de propriété portait sur l'abonnement du serveur, mais la session du portail était ouverte pour la fiche client, qui peut par conception porter plusieurs serveurs.
Ainsi, quiconque avait acheté légitimement sur un serveur pouvait voir et gérer les abonnements que la même fiche portait pour d'autres serveurs.
Corrigé en déterminant la fiche client à partir de l'achat concret plutôt qu'à partir de la ligne du serveur.
Deux failles où le code contredisait nos propres conditions
C'est la catégorie qui nous a le plus dérangés, parce qu'elle n'est pas technique.
La liste noire retirait du temps payé. Nos conditions disent qu'un abonnement prend fin à la fin de la période en cours. Mais lors du blocage d'un serveur, le code demandait une résiliation immédiate : aucun remboursement, et plus aucun service à partir de ce moment pour une période déjà payée. Le code faisait autre chose que ce que nous avions promis par écrit. Désormais, la période payée va jusqu'à son terme.
La renonciation au droit de rétractation n'était pas prouvable. Avant un achat, tu dois confirmer que le service commence immédiatement. Cette confirmation n'existait que dans le navigateur, n'était jamais transmise et n'était enregistrée nulle part. La charge de la preuve nous incombe, et nous n'avions tout simplement aucune preuve. Désormais, le consentement est un champ obligatoire de la route de paiement, enregistré avec un horodatage et une formulation versionnée, et la formulation elle-même est versionnée : un texte existant n'est jamais modifié, une nouvelle version est toujours créée, sinon les anciens enregistrements ne seraient plus reproductibles.
Le deuxième passage était pire que le premier
Après les 21 premières failles, nous avons regardé à nouveau quelques semaines plus tard. C'était la bonne décision, parce que la faille la plus grave de toutes n'est apparue qu'à ce moment-là.
Un remboursement sur le serveur B supprimait la licence à vie payée du serveur A. Les notifications de remboursement et de rétrofacturation ne nomment qu'un paiement, pas un serveur. Nous les rattachions via la fiche client. Après avoir correctement établi au premier passage qu'une fiche appartient à un acheteur sur plusieurs serveurs, ce rattachement précis n'était plus univoque. Quelqu'un qui avait payé pour deux serveurs et obtenu un remboursement pour l'un pouvait perdre la licence à vie de l'autre.
Le rattachement passe désormais par quatre étapes exactes, et tout ce qui ne peut pas être attribué sans ambiguïté est signalé plutôt que deviné.
Une rétrofacturation gagnée ne restaurait rien. Lors d'une rétrofacturation, le code supprimait la ligne d'achat, la seule preuve que ce serveur avait jamais eu une licence à vie. Gagner le litige plus tard ne pouvait pas l'annuler. Désormais, il marque au lieu de supprimer.
La conservation des données supprimait des serveurs payants. Quand le bot quitte un serveur, ses données sont supprimées au bout de 30 jours. Ce nettoyage ne demandait jamais si un abonnement en cours y était rattaché.
Ce que nous en avons retenu
Les bugs n'étaient pas dans le code de paiement. Pas une seule faille critique ne concernait un montant, une devise ou un calcul. Elles étaient toutes dans le modèle derrière : à qui appartient une fiche client, à qui appartient un paiement, à qui appartient un serveur. Quiconque audite un système de paiement en regardant les montants détourne le regard de la partie dangereuse.
Un audit a besoin d'un deuxième passage. La faille la plus grave était une conséquence directe d'une correction du premier. Ce n'est pas un hasard : corriger un modèle invalide des hypothèses qui tiennent encore ailleurs.
« Nos conditions le disent » est une phrase vérifiable. Deux failles étaient des écarts entre ce que nous avions promis par écrit et ce que faisait le code. Cela se lit, et personne ne le fait, parce que cela ressemble à du droit plutôt qu'à de l'ingénierie.
Les conditions d'achat complètes se trouvent dans nos conditions d'utilisation, le droit de rétractation sous rétractation.
Plus d'articles
Le droit dans notre code
Un bouton de résiliation qui doit fonctionner sans connexion. Un formulaire qui ne doit jamais te refuser. Pourquoi les obligations légales sont pour nous de l'architecture, pas du texte standard.
CoulissesPourquoi presque tout reste gratuit dans AlphaCloud
La plupart des bots Discord enferment leurs fonctionnalités les plus utiles derrière un abonnement. Nous ne le faisons volontairement pas, et c'est moins une question de générosité qu'il n'y paraît.
CoulissesGarder un œil sur 4 000 serveurs Discord : Grafana, PostHog et ce qu'ils nous ont montré
Une boucle qui causait discrètement la moitié de notre trafic, trois millions de requêtes inutiles par jour et une attaque terminée au bout d'une minute. Ce que notre monitoring voit, et ce qu'il ne voit volontairement pas.