Wat een audit van ons eigen betaalsysteem opleverde
Twee kritieke bevindingen, en geen van beide zat waar wij gezocht zouden hebben. Een open verslag over fouten die inmiddels zijn opgelost.

In augustus 2026 hebben we ons betaalsysteem van de grond af doorgelicht. Niet omdat er iets was gebeurd, maar omdat het kort daarvoor live was gegaan en we wilden weten wat we over het hoofd hadden gezien.
Dat was nogal wat. Dit artikel noemt de bevindingen. Alles wat hier beschreven staat, is opgelost, en het meeste stond sowieso al in de openbare changelog.
De modelfout waar beide kritieke bevindingen uit voortkwamen
Als iemand bij ons Premium koopt, wordt er bij onze betaalprovider een klantrecord aangemaakt. Dat record sloegen we op onder de server ID, omdat Premium nu eenmaal op een server terechtkomt.
Dat is precies het verkeerde model, en het was de wortel van de twee ernstigste bevindingen.
Een klantrecord hoort bij een koper. Het draagt zijn betaalmethode, zijn factuurgeschiedenis en, zodra iemand voor twee servers betaalt, meerdere abonnementen. Het onder een server ID opslaan betekent dat meerdere mensen naar dezelfde rij wijzen.
Bevinding 1: klantgegevens van een ander, te overschrijven zonder te betalen
Kopen staat open voor elk lid van een server, en de functie die het klantrecord ophaalt of aanmaakt, draait voor de betaling. Ze vond de rij via de server ID en schreef die stilletjes om naar de nieuwe geïnteresseerde, inclusief zijn e-mailadres bij de betaalprovider.
In de praktijk betekende dat: één klik op een server waar ooit iemand had gekocht, was genoeg. Daarna zouden de facturen voor elk abonnement van die oorspronkelijke koper naar het e-mailadres van de vreemde zijn gegaan. Geen betaling, geen bevestiging.
Opgelost door voor elk hergebruik te controleren of het record echt bij dit account hoort. Klopt dat niet, dan wordt er een apart record aangemaakt en blijft het andere onaangeroerd.
Bevinding 2: het factureringsportaal toonde abonnementen van anderen
Dezelfde modelfout, één niveau verder. De eigendomscontrole gold voor het abonnement van de server, maar de portalsessie werd geopend voor het klantrecord, dat bewust meerdere servers kan dragen.
Iedereen die op één server legitiem had gekocht, kon dus de abonnementen zien en beheren die hetzelfde record voor andere servers droeg.
Opgelost door het klantrecord af te leiden uit de specifieke aankoop in plaats van uit de serverrij.
Twee bevindingen waarbij de code onze eigen voorwaarden tegensprak
Dit is de categorie die ons het meest dwarszat, omdat ze niet technisch is.
De blacklist nam betaalde tijd af. Onze voorwaarden zeggen dat een abonnement eindigt aan het einde van de lopende termijn. Maar bij het blokkeren van een server vroeg de code om directe opzegging: geen terugbetaling, en vanaf dat moment geen dienst meer voor een periode die al betaald was. De code deed iets anders dan wat we schriftelijk hadden beloofd. Nu loopt de betaalde periode gewoon af.
De afstand van het herroepingsrecht was niet te bewijzen. Voor een aankoop moet je bevestigen dat de dienst direct begint. Die bevestiging leefde puur in de browser, werd nooit meegestuurd en nergens opgeslagen. De bewijslast ligt bij ons, en we hadden simpelweg geen bewijs. Nu is de toestemming een verplicht veld van de checkoutroute, opgeslagen met een tijdstempel en een versie van de formulering, en die formulering zelf heeft versies: een bestaande tekst wordt nooit bewerkt, er komt altijd een nieuwe versie bij, anders zouden oude vastleggingen niet meer te reproduceren zijn.
De tweede ronde was erger dan de eerste
Na de eerste 21 bevindingen keken we een paar weken later opnieuw. Dat was de juiste beslissing, want de allerernstigste bevinding kwam pas toen naar boven.
Een terugbetaling op server B verwijderde de betaalde lifetime licentie van server A. Meldingen over terugbetalingen en chargebacks noemen alleen een betaling, geen server. Die herleidden we via het klantrecord. Nadat we in ronde één terecht hadden vastgesteld dat een record bij een koper over meerdere servers hoort, was precies die herleiding niet meer eenduidig. Iemand die voor twee servers had betaald en voor één een terugbetaling kreeg, kon de lifetime licentie van de andere kwijtraken.
De herleiding loopt nu via vier exacte stappen, en alles wat niet eenduidig te herleiden is, wordt gemeld in plaats van geraden.
Een gewonnen chargeback herstelde niets. Bij een chargeback verwijderde de code de aankooprij, het enige bewijs dat deze server ooit een lifetime licentie had. Als we de zaak later wonnen, was dat niet terug te draaien. Nu markeert de code in plaats van te verwijderen.
De gegevensbewaring verwijderde betaalde servers. Als de bot een server verlaat, worden de gegevens na 30 dagen verwijderd. Die opruiming vroeg nooit of er een lopend abonnement aan vastzat.
Wat we eruit hebben meegenomen
De bugs zaten niet in de betaalcode. Geen enkele kritieke bevinding ging over een bedrag, een valuta of een berekening. Ze zaten allemaal in het model erachter: van wie is een klantrecord, van wie is een betaling, van wie is een server. Wie een betaalsysteem controleert door naar bedragen te kijken, kijkt weg van het gevaarlijke deel.
Een audit heeft een tweede ronde nodig. De ernstigste bevinding was een direct gevolg van een fix uit de eerste. Dat is geen toeval: een model corrigeren maakt aannames ongeldig die elders nog gelden.
"Dat staat in onze voorwaarden" is een controleerbare zin. Twee bevindingen waren verschillen tussen wat we schriftelijk hadden beloofd en wat de code deed. Dat kun je lezen, en niemand doet het, omdat het eruitziet als recht in plaats van techniek.
De aankoopvoorwaarden staan volledig in onze algemene voorwaarden, het herroepingsrecht onder herroeping.
Meer artikelen
De wet in onze code
Een opzegknop die zonder login moet werken. Een formulier dat je nooit mag weigeren. Waarom wettelijke plichten voor ons architectuur zijn, geen standaardtekst.
Achter de schermenWaarom bijna alles in AlphaCloud gratis blijft
De meeste Discord bots zetten hun nuttigste functies achter een abonnement. Wij doen dat bewust niet, en dat heeft minder met vrijgevigheid te maken dan het klinkt.
Achter de schermen4.000 Discord servers in de gaten houden: Grafana, PostHog en wat ze ons lieten zien
Een loop die stilletjes de helft van ons verkeer veroorzaakte, drie miljoen zinloze verzoeken per dag en een aanval die na een minuut voorbij was. Wat onze monitoring ziet, en wat bewust niet.