Vad en granskning av vårt eget betalsystem visade
Två kritiska fynd, och inget av dem låg där vi skulle ha letat. En öppen redovisning av misstag som sedan dess har åtgärdats.

I augusti 2026 granskade vi vårt betalsystem från grunden. Inte för att något hade hänt, utan för att det hade gått live kort dessförinnan och vi ville veta vad vi hade missat.
Vi hade missat en hel del. Den här artikeln redovisar fynden. Allt som beskrivs här är åtgärdat, och det mesta stod ändå redan i den offentliga ändringsloggen.
Modelleringsfelet som båda de kritiska fynden kom från
När någon köper Premium hos oss skapas en kundpost hos vår betalleverantör. Vi sparade den posten under server-ID:t, eftersom Premium ju hamnar på en server.
Det är precis fel modell, och det var roten till de två allvarligaste fynden.
En kundpost tillhör en köpare. Den bär köparens betalningsmetod, fakturahistorik och, så snart någon betalar för två servrar, flera prenumerationer. Att nyckla den efter server-ID innebär att flera personer pekar på samma rad.
Fynd 1: Någon annans kunddata, möjlig att skriva över utan att betala
Att köpa är öppet för alla medlemmar på en server, och funktionen som hämtar eller skapar kundposten körs före betalningen. Den hittade raden via server-ID:t och skrev tyst om den till den nya spekulanten, inklusive dennes e-postadress hos betalleverantören.
I praktiken innebar det: ett klick på en server där någon någon gång hade köpt räckte. Därefter skulle fakturorna för varje prenumeration hos den ursprungliga köparen ha gått till främlingens e-postadress. Ingen betalning, ingen bekräftelse.
Åtgärdat genom att vi före varje återanvändning kontrollerar om posten verkligen tillhör det här kontot. Om den inte gör det skapas en separat post och den andra lämnas orörd.
Fynd 2: Faktureringsportalen visade andras prenumerationer
Samma modelleringsfel, en nivå längre bort. Ägarkontrollen gällde serverns prenumeration, men portalsessionen öppnades för kundposten, som avsiktligt kan bära flera servrar.
Den som köpte legitimt på en server kunde alltså se och hantera de prenumerationer som samma post bar för andra servrar.
Åtgärdat genom att kundposten hämtas utifrån det specifika köpet i stället för från serverraden.
Två fynd där koden motsade våra egna villkor
Det här är kategorin som störde oss mest, eftersom den inte är teknisk.
Svartlistan tog bort betald tid. Våra villkor säger att en prenumeration upphör vid slutet av den aktuella perioden. Men när en server blockerades begärde koden en omedelbar uppsägning: ingen återbetalning, och ingen tjänst från det ögonblicket under en period som redan var betald. Koden gjorde något annat än vi hade lovat skriftligt. Nu löper den betalda perioden ut.
Avståendet från ångerrätten gick inte att bevisa. Före ett köp måste du bekräfta att tjänsten börjar direkt. Den bekräftelsen fanns bara i webbläsaren, skickades aldrig med och sparades aldrig någonstans. Bevisbördan ligger hos oss, och vi hade helt enkelt inget bevis. Nu är samtycket ett obligatoriskt fält i checkout-anropet, sparat med tidsstämpel och en versionerad formulering, och själva formuleringen är versionerad: en befintlig text redigeras aldrig, det skapas alltid en ny version, annars skulle gamla poster inte längre gå att återskapa.
Den andra omgången var värre än den första
Efter de första 21 fynden tittade vi igen några veckor senare. Det var rätt beslut, för det enskilt allvarligaste fyndet kom fram först då.
En återbetalning på server B raderade den betalda livstidslicensen på server A. Aviseringar om återbetalningar och återkrav nämner bara en betalning, inte en server. Vi kopplade dem till en server via kundposten. Eftersom vi i första omgången korrekt hade slagit fast att en post tillhör en köpare över flera servrar var just den kopplingen inte längre entydig. Någon som hade betalat för två servrar och fick återbetalning för den ena kunde förlora livstidslicensen för den andra.
Kopplingen sker nu i fyra exakta steg, och allt som inte kan kopplas entydigt rapporteras i stället för att gissas.
Ett vunnet återkrav återställde ingenting. Vid ett återkrav raderade koden köpraden, det enda beviset för att den här servern någonsin hade haft en livstidslicens. Om ärendet senare vanns gick det inte att göra ogjort. Nu markeras raden i stället för att raderas.
Datalagringen raderade betalda servrar. När boten lämnar en server tas dess data bort efter 30 dagar. Den rensningen frågade aldrig om en löpande prenumeration var kopplad till servern.
Vad vi tog med oss
Buggarna låg inte i betalningskoden. Inte ett enda kritiskt fynd gällde ett belopp, en valuta eller en beräkning. Alla låg i modellen bakom: vem som äger en kundpost, vem som äger en betalning, vem som äger en server. Den som granskar ett betalsystem genom att titta på belopp tittar bort från den farliga delen.
En granskning behöver en andra omgång. Det allvarligaste fyndet var en direkt följd av en fix från den första. Det är ingen slump: att korrigera en modell gör antaganden ogiltiga som fortfarande gäller på andra ställen.
"Det står i våra villkor" är en mening som går att kontrollera. Två fynd var avvikelser mellan det vi hade lovat skriftligt och det som koden gjorde. Det går att läsa, och ingen gör det, eftersom det ser ut som juridik snarare än teknik.
Köpvillkoren i sin helhet finns i våra användarvillkor, ångerrätten under ångerrätt.
Fler artiklar
Lagen i vår kod
En uppsägningsknapp som måste fungera utan inloggning. Ett formulär som aldrig får avvisa dig. Varför rättsliga skyldigheter är arkitektur för oss, inte standardtext.
Bakom kulissernaVarför nästan allt i AlphaCloud förblir gratis
De flesta Discord-botar låser in sina mest användbara funktioner bakom en prenumeration. Det gör vi medvetet inte, och det har mindre med generositet att göra än det låter.
Bakom kulissernaKoll på 4 000 Discord-servrar: Grafana, PostHog och vad de visade oss
En loop som i tysthet stod för halva vår trafik, tre miljoner meningslösa förfrågningar om dagen och en attack som var över efter en minut. Vad vår övervakning ser, och vad den medvetet inte ser.