Lo que reveló una auditoría de nuestro propio sistema de pagos
Dos hallazgos críticos, y ninguno estaba donde habríamos mirado. Un informe abierto sobre errores que ya están corregidos.

En agosto de 2026 revisamos nuestro sistema de pagos desde cero. No porque hubiera pasado algo, sino porque había entrado en producción poco antes y queríamos saber qué se nos había escapado.
Se nos había escapado bastante. Este artículo nombra los hallazgos. Todo lo descrito aquí está corregido, y la mayor parte ya figuraba de todos modos en el changelog público.
El error de modelado del que salieron los dos hallazgos críticos
Cuando alguien nos compra Premium, se crea un registro de cliente en nuestro proveedor de pagos. Guardábamos ese registro bajo el ID del servidor, porque al fin y al cabo Premium acaba en un servidor.
Ese es exactamente el modelo equivocado, y fue la raíz de los dos hallazgos más graves.
Un registro de cliente pertenece a un comprador. Lleva su método de pago, su historial de facturas y, en cuanto alguien paga por dos servidores, varias suscripciones. Indexarlo por ID de servidor significa que varias personas apuntan a la misma fila.
Hallazgo 1: datos de cliente ajenos, sobrescribibles sin pagar
Comprar está abierto a cualquier miembro de un servidor, y la función que obtiene o crea el registro de cliente se ejecuta antes del pago. Encontraba la fila por ID de servidor y la reescribía en silencio para el nuevo interesado, incluida su dirección de correo en el proveedor de pagos.
En la práctica eso significaba: bastaba un clic en un servidor donde alguien hubiera comprado en algún momento. A partir de ahí, las facturas de todas las suscripciones de ese comprador original habrían ido a la dirección de correo del desconocido. Sin pago, sin confirmación.
Corregido comprobando, antes de cualquier reutilización, si el registro pertenece de verdad a esta cuenta. Si no coincide, se crea uno aparte y el otro no se toca.
Hallazgo 2: el portal de facturación mostraba suscripciones ajenas
El mismo error de modelado, un nivel más allá. La comprobación de propiedad se aplicaba a la suscripción del servidor, pero la sesión del portal se abría para el registro de cliente, que por diseño puede llevar varios servidores.
Así, cualquiera que hubiera comprado legítimamente en un servidor podía ver y gestionar las suscripciones que ese mismo registro llevaba para otros servidores.
Corregido resolviendo el registro de cliente a partir de la compra concreta y no de la fila del servidor.
Dos hallazgos en los que el código contradecía nuestras propias condiciones
Esta es la categoría que más nos molestó, porque no es técnica.
La lista negra quitaba tiempo pagado. Nuestras condiciones dicen que una suscripción termina al final del periodo en curso. Pero al bloquear un servidor, el código pedía la cancelación inmediata: sin reembolso y sin servicio desde ese momento durante un periodo ya pagado. El código hacía algo distinto de lo que habíamos prometido por escrito. Ahora el periodo pagado se agota.
La renuncia al desistimiento no se podía demostrar. Antes de una compra tienes que confirmar que el servicio empieza de inmediato. Esa confirmación vivía solo en el navegador, nunca se enviaba y nunca se guardaba en ningún sitio. La carga de la prueba es nuestra, y sencillamente no teníamos prueba. Ahora el consentimiento es un campo obligatorio de la ruta de pago, guardado con fecha y hora y con una redacción versionada, y la propia redacción está versionada: un texto existente nunca se edita, siempre se crea una versión nueva; si no, los registros antiguos dejarían de ser reproducibles.
La segunda ronda fue peor que la primera
Tras los primeros 21 hallazgos volvimos a mirar unas semanas después. Fue la decisión correcta, porque el hallazgo más grave de todos solo salió entonces.
Un reembolso en el servidor B borraba la licencia de por vida pagada del servidor A. Las notificaciones de reembolso y de contracargo solo nombran un pago, no un servidor. Las resolvíamos a través del registro de cliente. Después de haber establecido correctamente en la primera ronda que un registro pertenece a un comprador en varios servidores, esa misma resolución ya no era inequívoca. Alguien que había pagado por dos servidores y recibía un reembolso por uno podía perder la licencia de por vida del otro.
La resolución pasa ahora por cuatro pasos exactos, y lo que no se puede resolver de forma inequívoca se notifica en lugar de adivinarse.
Un contracargo ganado no restauraba nada. Ante un contracargo, el código borraba la fila de la compra, la única prueba de que ese servidor había tenido alguna vez una licencia de por vida. Ganar el caso después no se podía deshacer. Ahora marca en lugar de borrar.
La retención de datos borraba servidores de pago. Cuando el bot sale de un servidor, sus datos se eliminan a los 30 días. Esa limpieza nunca preguntaba si había una suscripción en curso vinculada.
Lo que nos llevamos
Los bugs no estaban en el código de pagos. Ni un solo hallazgo crítico tenía que ver con un importe, una moneda o un cálculo. Todos estaban en el modelo de detrás: a quién pertenece un registro de cliente, a quién un pago, a quién un servidor. Quien audita un sistema de pagos mirando importes está mirando lejos de la parte peligrosa.
Una auditoría necesita una segunda ronda. El hallazgo más grave fue consecuencia directa de una corrección de la primera. No es casualidad: corregir un modelo invalida suposiciones que siguen vigentes en otros sitios.
«Lo dicen nuestras condiciones» es una frase verificable. Dos hallazgos eran divergencias entre lo que habíamos prometido por escrito y lo que hacía el código. Eso se puede leer, y nadie lo hace, porque parece derecho y no ingeniería.
Las condiciones de compra completas están en nuestros términos del servicio, y el derecho de desistimiento en desistimiento.
Más artículos
La ley dentro de nuestro código
Un botón de cancelación que tiene que funcionar sin iniciar sesión. Un formulario que nunca puede rechazarte. Por qué para nosotros las obligaciones legales son arquitectura, no texto de relleno.
Entre bastidoresPor qué casi todo en AlphaCloud sigue siendo gratis
La mayoría de los bots de Discord esconden sus funciones más útiles tras una suscripción. Nosotros deliberadamente no, y eso tiene menos que ver con la generosidad de lo que parece.
Entre bastidoresVigilar 4000 servidores de Discord: Grafana, PostHog y lo que nos enseñaron
Un bucle que causaba en silencio la mitad de nuestro tráfico, tres millones de peticiones inútiles al día y un ataque que terminó al cabo de un minuto. Lo que ve nuestra monitorización y lo que deliberadamente no ve.