What an Audit of Our Own Payment System Turned Up
Two critical findings, and neither was where we would have looked. An open report on mistakes that have since been fixed.

In August 2026 we reviewed our payment system from the ground up. Not because something had happened, but because it had gone live shortly before and we wanted to know what we had missed.
We had missed quite a lot. This article names the findings. Everything described here is fixed, and most of it was already in the public changelog anyway.
The modelling error both critical findings came from
When somebody buys Premium from us, a customer record is created at our payment provider. We stored that record under the server ID, because Premium lands on a server after all.
That is exactly the wrong model, and it was the root of the two most serious findings.
A customer record belongs to a buyer. It carries their payment method, their invoice history and, as soon as somebody pays for two servers, several subscriptions. Keying it by server ID means several people point at the same row.
Finding 1: Somebody else's customer data, overwritable without paying
Buying is open to any member of a server, and the function that fetches or creates the customer record runs before payment. It found the row by server ID and silently rewrote it to the new prospect, including their email address at the payment provider.
In practice that meant: one click on a server where somebody had bought at some point was enough. After that the invoices for every subscription of that original buyer would have gone to the stranger's email address. No payment, no confirmation.
Fixed by checking, before any reuse, whether the record really belongs to this account. On a mismatch a separate one is created and the other is left untouched.
Finding 2: The billing portal exposed other people's subscriptions
The same modelling error, one level further. The ownership check applied to the server's subscription, but the portal session was opened for the customer record, which by design can carry several servers.
So anybody who bought legitimately on one server could see and manage the subscriptions that the same record carried for other servers.
Fixed by resolving the customer record from the specific purchase rather than from the server row.
Two findings where the code contradicted our own terms
This is the category that bothered us most, because it is not technical.
The blacklist took paid time away. Our terms say a subscription ends at the end of the current term. But when blocking a server, the code called for immediate cancellation: no refund, and no service from that moment on for a period already paid for. The code did something other than what we had promised in writing. Now the paid period runs out.
The withdrawal waiver was not provable. Before a purchase you have to confirm that the service starts immediately. That confirmation lived purely in the browser, was never sent along and never stored anywhere. The burden of proof is on us, and we simply had no proof. Now the consent is a required field of the checkout route, stored with a timestamp and a versioned wording, and the wording itself is versioned: an existing text is never edited, a new version is always created, otherwise old records would no longer be reproducible.
The second round was worse than the first
After the first 21 findings we looked again a few weeks later. That was the right call, because the single most serious finding only came out then.
A refund on server B deleted the paid lifetime licence of server A. Refund and chargeback notifications name only a payment, not a server. We resolved them through the customer record. Having correctly established in round one that a record belongs to a buyer across several servers, that exact resolution was no longer unambiguous. Somebody who had paid for two servers and received a refund for one could lose the lifetime licence of the other.
Resolution now runs through four exact steps, and anything that cannot be resolved unambiguously is reported rather than guessed.
A won chargeback restored nothing. On a chargeback the code deleted the purchase row, the only evidence that this server ever had a lifetime licence. Winning the case later could not be undone. Now it marks rather than deletes.
Data retention deleted paid servers. When the bot leaves a server, its data is removed after 30 days. That cleanup never asked whether a running subscription was attached.
What we took from it
The bugs were not in the payment code. Not one critical finding concerned an amount, a currency or a calculation. They were all in the model behind it: who owns a customer record, who owns a payment, who owns a server. Anyone auditing a payment system by looking at amounts is looking away from the dangerous part.
An audit needs a second round. The most serious finding was a direct consequence of a fix from the first. That is no coincidence: correcting a model invalidates assumptions that still hold elsewhere.
"Our terms say so" is a checkable sentence. Two findings were divergences between what we had promised in writing and what the code did. That can be read, and nobody does it, because it looks like law rather than engineering.
The purchase terms in full are in our terms of service, the right of withdrawal under withdrawal.
More articles
The Law Inside Our Code
A cancellation button that must work without a login. A form that must never reject you. Why legal duties are architecture for us, not boilerplate.
Behind the ScenesWhy Almost Everything in AlphaCloud Stays Free
Most Discord bots lock their most useful features behind a subscription. We deliberately do not, and that has less to do with generosity than it sounds.
Behind the ScenesKeeping an Eye on 4,000 Discord Servers: Grafana, PostHog and What They Showed Us
A loop that quietly caused half of our traffic, three million pointless requests a day and an attack that was over after one minute. What our monitoring sees, and what it deliberately does not.