Почему пет-проекты ломаются на первой же оплате
Самый частый косяк в сервисах с онлайн-оплатой: фронт шлет юзера на платежный шлюз, а после редиректа на страницу «Успешно» бэк сразу открывает доступ к фиче.
В реале это гарантированные проблемы:
• Юзер закрыл вкладку до редиректа: списание прошло, а заказ повис «в обработке»
• Моргает сеть, шлюз шлет вебхук дважды: баланс улетает клиенту в double-размере (классический race condition).
• Каталог обновился, пока клиент тупил на чекауте: спишется старая цена или все упадет на валидации.
Когда собирал модуль эквайринга на базе Stripe, заложил три простых правила:
1. Доверяем только криптографическим вебхукам Статус заказа меняем строго по входящему вебхуку от шлюза с проверкой подписи заголовка (signature verification). Редирект в браузере вообще ничего не значит и доверия к нему ноль.
2. Строгая идемпотентность У каждого платежного события есть уникальный ID транзакции. Перед обработкой чекаем статус в PostgreSQL через SELECT FOR UPDATE или берем распределенный лок в Redis. Повторный вебхук просто получает 200 OK и идет дальше, ничего повторно не начисляя.
3. Снапшот цен на старте чекаута Цена товара или подписки жестко фиксируется в сущности Order в момент создания платежной сессии. Если прайс в каталоге изменится через секунду, на открытую сессию это уже не повлияет.
Обложил логику 16 тестами на pytest: прогнал эмуляцию таймаутов, дубли вебхуков и фейковые сигнатуры. Никаких задвоений и потерянных оплат.
Архитектуру и готовый код выложил на GitHub: https://github.com/map1772/stripe-shop Стек: Python, FastAPI / Django, Stripe API, Docker Compose, PostgreSQL, Pytest. Коллеги, сталкивались с дабл-списаниями на вебхуках или сразу закладываете локи в базу?