- Remove `locked.is_paid = True` — is_paid is a read-only @property computed from status
- Change status from 'completed' to 'paid' (matching is_paid property check)
- Use db.commit() instead of db.flush() for guest payment persistence
Мультиязычность:
- Миграция 0021: текстовые поля лендингов → JSON с ключами локалей
- Утилита resolve_locale_text с fallback-цепочкой (lang → ru → en → first)
- Админ API: dict[str, str] для title/subtitle/footer/meta/features
- Публичный API: ?lang= параметр, резолвит в плоские строки
- Обратная совместимость: plain strings → {"ru": value}
Гостевые платежи:
- Миграция 0022: user_id nullable во всех платёжных таблицах
- Поддержка всех провайдеров кроме Stars
- Общий хелпер try_fulfill_guest_purchase в common.py
- YooKassa переведена на общий хелпер
Исправления по ревью:
- CryptoBot: guest fulfillment после FOR UPDATE lock
- _patch_guest_metadata: commit вместо flush
- Freekassa/KassaAI: metadata как dict вместо JSON-строки
- purchase_token маскирован в логах
- None → "guest" в order_id
- Rate limit на GET /landing/{slug}
- Маскирование contact_value
- SELECT FOR UPDATE блокировка во всех 9 провайдерах (кроме YooKassa — свой паттерн)
- create_transaction(commit=False) + единый db.commit() для атомарности
- emit_transaction_side_effects() для отложенных событий после коммита
- Все link_*_payment_to_transaction используют db.flush() вместо db.commit()
- Freekassa/KassaAI: прямое присвоение transaction_id + flush вместо update_status
- MulenPay: прямая мутация balance_kopeks вместо add_user_balance
- Platega: блокировка перед чтением metadata, инлайн обновления полей
- CloudPayments: int(round(amount * 100)) для корректного округления
- Heleket добавлен в SUPPORTED_AUTO_CHECK_METHODS
- Удалены PII из логов yookassa webhook (заголовки, IP)
- UniqueConstraint(external_id, payment_method) на транзакциях + миграция 0017
- Cabinet: PaymentService(bot=bot) внутри try блока
- verify_payment_amount утилита для проверки суммы webhook
- Remove misleading "Важно" and "При наличии корзины" warnings from all
payment success notifications
- Fix cart total bug: show actual cart price from Redis instead of top-up
amount, and suppress "insufficient funds" when balance is enough
- Extract shared send_cart_notification_after_topup() in common.py to
replace duplicated code across all 10 payment providers
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
inline.py, and admin/payments.py
Cabinet was calling CryptoBotService.create_invoice() directly without
saving CryptoBotPayment to DB. When webhook arrived, payment lookup
failed and returned HTTP 400, causing infinite retries.
Now cabinet uses PaymentService.create_cryptobot_payment() (same as
miniapp) with proper USD conversion via currency_converter.
Also return HTTP 200 for unknown invoice_ids to stop retry spam.
Transaction created_at and completed_at showed identical timestamps
because webhook handlers created transactions with is_completed=True
in a single step. Now all 10 payment providers pass payment.created_at
to the transaction so created_at reflects when the user initiated
the payment, not when the webhook processed it.
Also: remove duplicate datetime import in inline.py, upgrade button
stats DB error logging from debug to warning, add index on
button_click_logs.button_type for analytics queries.
Remove AUTO_ACTIVATE_AFTER_TOPUP and SHOW_ACTIVATION_PROMPT_AFTER_TOPUP
features from all payment providers, config, system settings, and tests.
Cart auto-purchase (AUTO_PURCHASE_AFTER_TOPUP) is preserved.
Bug fixes:
- fix KeyError 'months' in devices.py for custom locale overrides
- fix IntegrityError on trial subscription retry (update existing PENDING instead of INSERT)
- fix PendingRollbackError cascade by adding db.rollback() before recovery
- fix TelegramForbiddenError not caught in photo_message.py
- fix "query is too old" spam in required_sub_channel_check
- add missing trial locale keys (TRIAL_PAYMENT_DESCRIPTION, TRIAL_REFUND_DESCRIPTION, TRIAL_ACTIVATION_ERROR)
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
Новый функционал:
- Быстрая проверка (TRAFFIC_FAST_CHECK_*) — отслеживает дельту трафика за интервал через snapshot
- Суточная проверка (TRAFFIC_DAILY_CHECK_*) — анализирует трафик за 24 часа через bandwidth API
- Фильтрация по нодам (TRAFFIC_MONIT
- Ежедневная ротация в 00:00 с архивацией в tar.gz
- Разделение по уровням: info.log, warning.log, error.log
- Отдельный payments.log для платежных операций
- Отправка архивов в Telegram-канал бекапов
- Автоочистка архивов старше 7 дней (настраивается)
- Переключатель LOG_ROTATION_ENABLED (по умолчанию выключен)
Добавлена функция умной автоактивации подписки после пополнения баланса:
- Новая настройка AUTO_ACTIVATE_AFTER_TOPUP_ENABLED в .env
- Функция auto_activate_subscription_after_topup() в subscription_auto_purchase_service.py:
- Автоматически продлевает истёкшую подписку с теми же параметрами
- Создаёт новую подписку с дефолтными параметрами если подписки нет
- Проверяет достаточность баланса перед активацией
- Интеграция с RemnaWave API
- Уведомления пользователю и админам
- Интеграция во все 9 платёжных провайдеров:
- Stars, CryptoBot, YooKassa, CloudPayments
- WATA, Platega, Pal24, MulenPay, Tribute
- Исправлен handle_activate_button в menu.py:
- Полная переработка с интеграцией RemnaWave
- Корректная работа с балансом и транзакциями
- Использование SubscriptionRenewalService
Добавлено округление цен при отображении:
- Новая настройка PRICE_ROUNDING_ENABLED в .env
- Логика: ≤50 коп → вниз, >50 коп → вверх
- Применяется везде: пополнения, партнёрки, скидки, промогруппы
2) У промогрупп появится приоритет
3) У пользователя может быть несколько промогрупп, но влиять будет только с наивысшим приоритетом
4) Промокоды с промогруппой
5) При выводе пользователей с промогруппой будет также выводиться ссылка на каждого. Можно будет отследить сливы промокодов "для своих". Я в целом это добавлю во все места, где пользователь выводится в админке
6) Исправить баг исчезновения триалки при пополнении
7) Исправить падающие тесты и добавить новых
8) Трафик: 0 ГБ в тестовой подписке исправить на Трафик: Безлимит
9) При попытке изменить промогруппу "Пользователь не найден" - исправил