Cosa ha portato alla luce un audit del nostro sistema di pagamento
Due problemi critici, e nessuno dei due era dove avremmo guardato. Un resoconto aperto su errori che nel frattempo sono stati corretti.

Ad agosto 2026 abbiamo ricontrollato il nostro sistema di pagamento da cima a fondo. Non perché fosse successo qualcosa, ma perché era andato online poco prima e volevamo sapere cosa ci era sfuggito.
Ci era sfuggito parecchio. Questo articolo elenca ciò che abbiamo trovato. Tutto ciò che è descritto qui è stato corretto, e la maggior parte era comunque già nel changelog pubblico.
L'errore di modello da cui sono nati entrambi i problemi critici
Quando qualcuno acquista Premium da noi, presso il nostro fornitore di pagamenti viene creato un record cliente. Noi salvavamo quel record sotto l'ID del server, perché in fondo Premium finisce su un server.
È esattamente il modello sbagliato, ed è stato la radice dei due problemi più gravi.
Un record cliente appartiene a un acquirente. Contiene il suo metodo di pagamento, lo storico delle sue fatture e, non appena qualcuno paga per due server, più abbonamenti. Indicizzarlo per ID del server significa che più persone puntano alla stessa riga.
Problema 1: i dati cliente di qualcun altro, sovrascrivibili senza pagare
L'acquisto è aperto a qualsiasi membro di un server, e la funzione che recupera o crea il record cliente viene eseguita prima del pagamento. Trovava la riga tramite l'ID del server e la riscriveva in silenzio a favore del nuovo potenziale acquirente, compreso il suo indirizzo email presso il fornitore di pagamenti.
In pratica bastava un clic su un server dove qualcuno a un certo punto aveva acquistato. Da quel momento le fatture di ogni abbonamento di quell'acquirente originale sarebbero andate all'indirizzo email dell'estraneo. Nessun pagamento, nessuna conferma.
Corretto verificando, prima di qualsiasi riutilizzo, se il record appartiene davvero a questo account. Se non corrisponde ne viene creato uno separato e l'altro resta intatto.
Problema 2: il portale di fatturazione mostrava gli abbonamenti di altre persone
Lo stesso errore di modello, un livello più in là. Il controllo di proprietà valeva per l'abbonamento del server, ma la sessione del portale veniva aperta per il record cliente, che per come è fatto può portare con sé più server.
Quindi chiunque avesse acquistato legittimamente su un server poteva vedere e gestire gli abbonamenti che lo stesso record portava per altri server.
Corretto risolvendo il record cliente a partire dall'acquisto specifico invece che dalla riga del server.
Due problemi in cui il codice contraddiceva i nostri stessi termini
È la categoria che ci ha infastidito di più, perché non è tecnica.
La blacklist toglieva tempo già pagato. I nostri termini dicono che un abbonamento termina alla fine del periodo in corso. Ma quando un server veniva bloccato, il codice chiedeva la disdetta immediata: nessun rimborso, e da quel momento nessun servizio per un periodo già pagato. Il codice faceva qualcosa di diverso da ciò che avevamo promesso per iscritto. Ora il periodo pagato arriva a scadenza.
La rinuncia al recesso non era dimostrabile. Prima di un acquisto devi confermare che il servizio inizia subito. Quella conferma viveva solo nel browser, non veniva mai inviata e non veniva mai salvata da nessuna parte. L'onere della prova è nostro, e semplicemente non avevamo alcuna prova. Ora il consenso è un campo obbligatorio della route di checkout, salvato con data e ora e con un testo versionato, e il testo stesso è versionato: un testo esistente non viene mai modificato, si crea sempre una nuova versione, altrimenti i vecchi record non sarebbero più riproducibili.
Il secondo giro è stato peggiore del primo
Dopo i primi 21 problemi abbiamo guardato di nuovo qualche settimana dopo. È stata la scelta giusta, perché il problema in assoluto più grave è emerso solo allora.
Un rimborso sul server B cancellava la licenza lifetime pagata del server A. Le notifiche di rimborso e di chargeback indicano solo un pagamento, non un server. Le risolvevamo tramite il record cliente. Dopo aver stabilito correttamente nel primo giro che un record appartiene a un acquirente su più server, proprio quella risoluzione non era più univoca. Chi aveva pagato per due server e aveva ricevuto un rimborso per uno poteva perdere la licenza lifetime dell'altro.
Ora la risoluzione passa da quattro passaggi esatti, e tutto ciò che non si può risolvere in modo univoco viene segnalato invece che indovinato.
Un chargeback vinto non ripristinava nulla. In caso di chargeback il codice cancellava la riga dell'acquisto, l'unica prova che quel server avesse mai avuto una licenza lifetime. Vincere la contestazione in seguito non poteva più annullare l'effetto. Ora contrassegna invece di cancellare.
La conservazione dei dati cancellava server paganti. Quando il bot lascia un server, i suoi dati vengono rimossi dopo 30 giorni. Quella pulizia non chiedeva mai se ci fosse un abbonamento attivo collegato.
Cosa ne abbiamo ricavato
I bug non erano nel codice dei pagamenti. Nemmeno un problema critico riguardava un importo, una valuta o un calcolo. Erano tutti nel modello che ci sta dietro: a chi appartiene un record cliente, a chi appartiene un pagamento, a chi appartiene un server. Chi controlla un sistema di pagamento guardando gli importi sta guardando lontano dalla parte pericolosa.
Un audit ha bisogno di un secondo giro. Il problema più grave era una conseguenza diretta di una correzione del primo. Non è un caso: correggere un modello invalida ipotesi che altrove valgono ancora.
"Lo dicono i nostri termini" è una frase verificabile. Due problemi erano divergenze tra ciò che avevamo promesso per iscritto e ciò che faceva il codice. Si può leggere, e nessuno lo fa, perché sembra diritto e non ingegneria.
Le condizioni di acquisto complete sono nei nostri termini di servizio, il diritto di recesso alla pagina recesso.
Altri articoli
La legge dentro il nostro codice
Un pulsante di disdetta che deve funzionare senza login. Un modulo che non deve mai respingerti. Perché per noi gli obblighi legali sono architettura, non testo di contorno.
Dietro le quintePerché quasi tutto in AlphaCloud resta gratuito
La maggior parte dei bot Discord chiude le funzionalità più utili dietro un abbonamento. Noi volutamente no, e ha meno a che fare con la generosità di quanto sembri.
Dietro le quinteTenere d'occhio 4.000 server Discord: Grafana, PostHog e cosa ci hanno mostrato
Un loop che causava in silenzio metà del nostro traffico, tre milioni di richieste inutili al giorno e un attacco finito dopo un minuto. Cosa vede il nostro monitoraggio, e cosa volutamente non vede.