When user clicks /start ref_CODE, the referral code was stored only in
FSM state. If the user opened miniapp/cabinet before completing bot
registration, the referral was lost.
Now /start immediately saves pending_referral:{telegram_id} to Redis
(7-day TTL). The referral is consumed by whichever path creates the
user first — bot create_user(), cabinet auth (initdata/widget/oidc).
Redis key is cleared after consumption to prevent double-referral.
- referral_service: save/get/clear_pending_referral Redis helpers
- start.py: save pending referral for new users only
- crud/user.py: create_user checks Redis if no referred_by_id
- cabinet/auth.py: initdata/widget/oidc routes check + cleanup Redis
Bot handlers (H1-H5):
- confirm_extend_subscription: error alert instead of wrong sub fallback
- open_subscription_link/subscription_connect: startswith registration
- handle_subscription_settings: multi-tariff guard
- confirm_reset_traffic: FSM state check in multi-tariff
Services (H6-H12):
- subscription_service: 5 UUID fallback fixes — no user.remnawave_uuid in
multi-tariff, return None if subscription.remnawave_uuid missing
- auto_purchase: use cart subscription_id for tariff match
- remnawave_service: migrate_squad_users checks subscription.remnawave_uuid
- campaign_service: extend existing sub or create new in multi-tariff
- broadcast_service: check ALL subs for paid-subscription guard
- blocked_users_service: remnawave_uuids list, iterate in cleanup
- user_service: log sub.remnawave_uuid in multi-tariff
Admin (H13-H16):
- grant_trial/paid_subscription: allow in multi-tariff mode
- promo_offers: pick sub with URL, aggregate squads from all subs
CRUD/Frontend (H17-H18):
- get_users_list: .unique() for outerjoin dedup
- refreshTraffic: withSubId in params instead of body
get_user_by_remnawave_uuid: fallback query searches Subscription table
when User-level UUID not found (multi-tariff stores UUID per-subscription).
Webhook _resolve_user_and_subscription: direct Subscription lookup before
returning None when user not found by telegram_id or User.remnawave_uuid.
Webhook user.deleted: refresh user.subscriptions before iterating to
ensure relationship is loaded from DB.
Account merge: clear subscription-level remnawave_uuid/short_uuid on
secondary user's subscriptions to prevent orphaned panel users.
- Fix orphaned subscriptions/GuestPurchase when phantom claim fails with
IntegrityError — now merges phantom into existing user across all 3 call sites
- Add explicit db.commit() after merge in both active-user and registration paths
- Fix remnawave_uuid transfer ordering (clear→flush→assign) to prevent unique
constraint violation during flush
- Clear phantom.referral_code on soft-delete to prevent unique constraint issues
- Add status != DELETED filter to find_phantom_user_by_username (defense in depth)
- Add WARNING-level logging on phantom claims for admin audit trail
- Add functional index on lower(username) for phantom lookup performance (migration 0048)
- Add ON DELETE CASCADE to subscription_servers.subscription_id (migration 0047)
- Add admin endpoint POST /users/{id}/assign-referrer with recursive CTE cycle
detection, self-enrichment prevention, and audit logging
- Harden account_merge_service: add SubscriptionServer, RioPayPayment,
SeverPayPayment, SavedPaymentMethod, GuestPurchase, NewsArticle handling
- Fix logger key typo get= → error= in promocode activation
lock_user_for_pricing не загружал User.subscription eagerly,
что вызывало lazy load в async контексте при обращении к db_user.subscription
в execute_change_devices.
Updated email queries in authentication routes and user CRUD operations to be case-insensitive. This change ensures that email comparisons ignore case, improving user experience and preventing potential registration/login issues with differently cased emails.
Added selectinload(UserPromoGroup.promo_group) nested under
user_promo_groups to prevent lazy-load in get_primary_promo_group().
Added selectinload(User.referrer) for format_referrer_info().
Broadened except clause in format_referrer_info as safety net.
lock_user_for_update, subtract_user_balance, and add_user_balance use
select(User).with_for_update().populate_existing which expires loaded
relationships. Added selectinload for subscription, user_promo_groups
and promo_group to prevent lazy-load in async context.
All payment providers now use lock_user_for_update before balance mutations
and commit=False pattern for atomic payment status + fulfillment.
Tribute service refund also uses proper locking.
- subtract_user_balance: only rollback when commit=True, re-raise when
commit=False so caller controls transaction lifecycle
- log_promo_offer_action: add db.flush() when commit=False to surface
constraint errors immediately instead of deferring to caller's commit
- Stars: ceil → round в rubles_to_stars, нормализация kopeks в cabinet invoice
- Устройства/трафик: добавлен transaction_type в subtract_user_balance,
покупки устройств и трафика теперь создают SUBSCRIPTION_PAYMENT (не WITHDRAWAL),
что исправляет отображение в статистике продаж
- Add try_resume_disabled_daily_after_topup() for instant resume when balance is topped up
- Fix all 5 resume paths to charge daily fee BEFORE activating subscription
- Remove unsafe inline auto-resume from add_user_balance() that bypassed fee charging
- Add NULL-safe is_daily_paused filter in subscription queries
- Use create_remnawave_user() instead of enable_remnawave_user() for full VPN panel sync
- Add DAILY_SUBSCRIPTION_RESUMED_AFTER_TOPUP localization key (ru, en, fa, zh, ua)
- New gift_activation handler for gift_activate:{id} callback buttons
- Send Telegram notification to gift recipients with activate button
- Add skip_notification param to activate_purchase to prevent duplicates
- Fix telegram username regex minimum length (4→5 chars) in landing routes
- Add BOT_TOKEN guard in telegram gift notification sender
- Pre-resolve notification params before commit to avoid DetachedInstanceError
- Убран WITHDRAWAL из автонегации в create_transaction (ломал profit,
expenses и display flip в admin_users)
- Добавлен func.abs() в by_type агрегацию (transaction.py)
- Добавлен func.abs() в total_spent user.py (_build_spending_stats_select)
- Исправлен all_time_stats в боте и webapi: передаём явный диапазон дат
вместо дефолтного текущего месяца
Referral links from cabinet (?ref=CODE) were only tracked for email registration.
Now referral_code is accepted and processed in Telegram initData, Telegram Widget,
and OAuth authentication endpoints. Includes self-referral protection by email
for OAuth, proper error logging, and the missing email_templates table migration.
Add .selectinload(Subscription.tariff) chain to all User queries that
load subscriptions, preventing lazy loading of the tariff relationship
in async context. Also replace unsafe getattr(subscription, 'tariff')
with explicit async get_tariff_by_id() in handle_extend_subscription.
- 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
- YooKassa: SELECT FOR UPDATE on payment row to prevent concurrent double-processing
- subtract_user_balance: row locking to prevent concurrent balance race conditions
- subtract_user_balance: transaction creation before commit for atomicity
- subscription renewal: compensating refund if extend_subscription fails after charge
- StaleDataError: use savepoint instead of full rollback to protect parent transaction
- Add OAuth provider config vars and helpers to config.py
- Add google_id, yandex_id, discord_id, vk_id columns to User model
- Create OAuth provider service with state management and 4 providers
- Add CRUD functions for OAuth user lookup, linking, and creation
- Add 3 API endpoints: providers list, authorize URL, callback
- Add alembic migration and universal_migration support
- Fix trial disable logic to cover OAuth auth_types
Исправленные файлы:
1. app/services/traffic_monitoring_service.py — удалены неиспользуемые импорты Decimal, aiohttp
2. app/services/blacklist_service.py — удалён неиспользуемый импорт re
3. app/database/crud/user.py:998 — создана отсутствующая функция get_users_with_active_subscriptions:
async def get_users_with_active_subscriptions(db: AsyncSession) -> List[User]:
3. Функция:
- Возвращает пользователей с активными подписками
- Фильтрует по remnawave_uuid IS NOT NULL (нужен для API Remnawave)
- Проверяет end_date > now и status == ACTIVE
Добавлена поддержка указания способа оплаты при пополнении баланса:
- add_user_balance(): новый параметр payment_method для передачи в транзакцию
- add_user_balance_by_id(): поддержка payment_method
- UserService: ручные пополнения админом пом
2) У промогрупп появится приоритет
3) У пользователя может быть несколько промогрупп, но влиять будет только с наивысшим приоритетом
4) К промокодам можно будет добавить промогруппу. Все активировавшие промокод получат её
5) При выводе пользователей с промогруппой будет также выводиться ссылка на каждого. Можно будет отследить сливы промокодов "для своих". Я в целом это добавлю во все места, где пользователь выводится в админке
6) Исправить баг исчезновения триалки при пополнении
7) Исправить падающие тесты и добавить новых
8) Трафик: 0 ГБ в тестовой подписке исправить на Трафик: Безлимит