Co wykazał audyt naszego własnego systemu płatności
Dwa krytyczne znaleziska, i żadne nie tam, gdzie byśmy szukali. Otwarty raport o błędach, które zostały już naprawione.

W sierpniu 2026 przejrzeliśmy nasz system płatności od podstaw. Nie dlatego, że coś się stało, tylko dlatego, że niedługo wcześniej wszedł do użytku, a my chcieliśmy wiedzieć, co przeoczyliśmy.
Przeoczyliśmy całkiem sporo. Ten artykuł wymienia znaleziska. Wszystko, co tu opisano, jest naprawione, a większość i tak była już w publicznym changelogu.
Błąd modelowania, z którego wzięły się oba krytyczne znaleziska
Gdy ktoś kupuje u nas Premium, u naszego dostawcy płatności powstaje rekord klienta. Zapisywaliśmy ten rekord pod ID serwera, bo przecież Premium trafia na serwer.
To dokładnie zły model i był on źródłem dwóch najpoważniejszych znalezisk.
Rekord klienta należy do kupującego. Zawiera jego metodę płatności, historię faktur i, gdy tylko ktoś zapłaci za dwa serwery, kilka subskrypcji. Kluczowanie go po ID serwera oznacza, że kilka osób wskazuje na ten sam wiersz.
Znalezisko 1: Cudze dane klienta do nadpisania bez płacenia
Zakup jest dostępny dla każdego członka serwera, a funkcja, która pobiera albo tworzy rekord klienta, działa przed płatnością. Znajdowała wiersz po ID serwera i po cichu przepisywała go na nowego zainteresowanego, łącznie z jego adresem e-mail u dostawcy płatności.
W praktyce oznaczało to: wystarczyło jedno kliknięcie na serwerze, na którym ktoś kiedyś coś kupił. Potem faktury za każdą subskrypcję pierwotnego kupującego trafiałyby na adres e-mail obcej osoby. Bez płatności, bez potwierdzenia.
Naprawione przez sprawdzenie przed każdym ponownym użyciem, czy rekord naprawdę należy do tego konta. Przy niezgodności tworzony jest osobny, a tamten pozostaje nietknięty.
Znalezisko 2: Portal rozliczeń pokazywał cudze subskrypcje
Ten sam błąd modelowania, poziom dalej. Kontrola własności dotyczyła subskrypcji serwera, ale sesja portalu była otwierana dla rekordu klienta, który z założenia może obejmować kilka serwerów.
Każdy, kto legalnie kupił na jednym serwerze, mógł więc zobaczyć i zarządzać subskrypcjami, które ten sam rekord miał dla innych serwerów.
Naprawione przez ustalanie rekordu klienta na podstawie konkretnego zakupu, a nie wiersza serwera.
Dwa znaleziska, w których kod przeczył naszemu regulaminowi
To kategoria, która niepokoiła nas najbardziej, bo nie jest techniczna.
Czarna lista odbierała opłacony czas. Nasz regulamin mówi, że subskrypcja kończy się z końcem bieżącego okresu. Ale przy blokowaniu serwera kod wywoływał natychmiastowe anulowanie: bez zwrotu i bez usługi od tej chwili za okres już opłacony. Kod robił coś innego, niż obiecaliśmy na piśmie. Teraz opłacony okres dobiega końca.
Zrzeczenia się prawa odstąpienia nie dało się udowodnić. Przed zakupem trzeba potwierdzić, że usługa zaczyna się natychmiast. To potwierdzenie żyło wyłącznie w przeglądarce, nigdy nie było wysyłane ani nigdzie zapisywane. Ciężar dowodu spoczywa na nas, a my po prostu nie mieliśmy dowodu. Teraz zgoda jest wymaganym polem trasy zakupu, zapisywanym ze znacznikiem czasu i wersjonowanym brzmieniem, a samo brzmienie jest wersjonowane: istniejącego tekstu nigdy się nie edytuje, zawsze tworzy się nową wersję, inaczej stare zapisy nie byłyby już odtwarzalne.
Druga runda była gorsza od pierwszej
Po pierwszych 21 znaleziskach przyjrzeliśmy się jeszcze raz kilka tygodni później. To była dobra decyzja, bo najpoważniejsze pojedyncze znalezisko wyszło dopiero wtedy.
Zwrot na serwerze B usuwał opłaconą licencję dożywotnią serwera A. Powiadomienia o zwrotach i obciążeniach zwrotnych podają tylko płatność, a nie serwer. Ustalaliśmy je przez rekord klienta. Skoro w pierwszej rundzie słusznie ustaliliśmy, że rekord należy do kupującego na wielu serwerach, właśnie to ustalenie przestało być jednoznaczne. Ktoś, kto zapłacił za dwa serwery i dostał zwrot za jeden, mógł stracić licencję dożywotnią drugiego.
Ustalanie przebiega teraz w czterech dokładnych krokach, a wszystko, czego nie da się jednoznacznie ustalić, jest zgłaszane, a nie zgadywane.
Wygrany spór o obciążenie zwrotne niczego nie przywracał. Przy obciążeniu zwrotnym kod usuwał wiersz zakupu, jedyny dowód, że ten serwer w ogóle miał licencję dożywotnią. Późniejszej wygranej nie dało się cofnąć. Teraz wiersz jest oznaczany, a nie usuwany.
Retencja danych usuwała opłacone serwery. Gdy bot opuszcza serwer, jego dane są usuwane po 30 dniach. To sprzątanie nigdy nie pytało, czy była z nim związana trwająca subskrypcja.
Co z tego wynieśliśmy
Błędy nie były w kodzie płatności. Ani jedno krytyczne znalezisko nie dotyczyło kwoty, waluty ani obliczenia. Wszystkie tkwiły w modelu za tym: do kogo należy rekord klienta, do kogo płatność, do kogo serwer. Kto audytuje system płatności, patrząc na kwoty, patrzy w przeciwną stronę niż niebezpieczna część.
Audyt potrzebuje drugiej rundy. Najpoważniejsze znalezisko było bezpośrednim skutkiem poprawki z pierwszej. To nie przypadek: poprawienie modelu unieważnia założenia, które gdzie indziej wciąż obowiązują.
"Tak mówi nasz regulamin" to zdanie, które da się sprawdzić. Dwa znaleziska były rozbieżnościami między tym, co obiecaliśmy na piśmie, a tym, co robił kod. Da się to przeczytać, a nikt tego nie robi, bo wygląda to na prawo, a nie na inżynierię.
Pełne warunki zakupu są w naszym regulaminie, prawo odstąpienia w odstąpieniu.
Więcej artykułów
Dlaczego prawie wszystko w AlphaCloud pozostaje darmowe
Większość botów Discord zamyka najprzydatniejsze funkcje za subskrypcją. My celowo tego nie robimy, i ma to mniej wspólnego z hojnością, niż się wydaje.
Za kulisamiPrawo w naszym kodzie
Przycisk wypowiedzenia, który musi działać bez logowania. Formularz, który nigdy nie może cię odrzucić. Dlaczego obowiązki prawne to dla nas architektura, a nie formułki.
Za kulisamiPod okiem 4000 serwerów Discord: Grafana, PostHog i to, co nam pokazały
Pętla, która po cichu generowała połowę naszego ruchu, trzy miliony bezsensownych zapytań dziennie i atak, który skończył się po minucie. Co widzi nasz monitoring, a czego celowo nie widzi.