Commit Graph

119 Commits

Author SHA1 Message Date
Fringg 7dd67e36b3 feat: add tariff identification to all notifications for multi-tariff mode
When MULTI_TARIFF_ENABLED=true users can have multiple subscriptions,
so notifications must identify which tariff they relate to.

- Webhook notifications: _notify_user auto-injects tariff_label from
  subscription.tariff.name into all 16 webhook notification strings
- Monitoring service: tariff labels in expired, expiring, trial ending,
  follow-up waves, autopay success/failed notifications
- Daily subscription: tariff in insufficient balance notification
- Recurrent payments: tariff in card autopay success/failed
- Auto-purchase: tariff in all 4 auto-purchase notification paths,
  with pre-captured names to avoid MissingGreenlet after db.commit()
- Channel checker: plural form for multi-subscription deactivation
- Payment providers: tariff in YooKassa and Stars activation messages
- Admin: tariff in bulk expiry reminder
- Localization: {tariff_label} in 20 notification keys across all 5
  locales (ru, en, ua, zh, fa) + _MULTI channel keys
- Fix: selectinload(Subscription.tariff) in trial expiring query
- Fix: capture tariff_name before expire_subscription to prevent
  MissingGreenlet from db.refresh() expiring ORM relationships
2026-03-28 19:12:34 +03:00
Fringg 94ed282381 fix: multi-tariff MEDIUM/LOW batch — 20 issues across 17 files
MEDIUM fixes:
- config: MAX_ACTIVE_SUBSCRIPTIONS=10 limit + check in tariff purchase
- tariff_purchase: add_user_balance return check → _persist_failed_refund
- tariff_purchase: promo restoration atomic with refund (commit=False)
- purchase: handle_toggle_daily_subscription_pause multi-tariff guard
- my_subscriptions: respect HIDE_SUBSCRIPTION_LINK setting
- multi_tariff: delete_subscription uses actual_status
- tariff_switch: guard against switching to already-owned tariff
- monitoring: i18n button texts via texts.t() + per-subscription dedup
- promo_offer_service: multi-tariff subscription resolution
- bot.py: register DisplayNameRestrictionMiddleware + SubscriptionStatusMiddleware on pre_checkout_query
- crud/user: add_user_balance accepts commit=False
- crud/subscription: update_daily_charge_time refresh only on commit, autopay days_before preserved
- daily_subscription: remove redundant outer commit in process_traffic_resets

LOW fixes:
- inline.py: remove dead get_subscription_expiring_keyboard function
- promocode_service: current_uses +1 in response (post-increment)
- yookassa: use already-resolved subscription for admin notification
- subscription_utils: remove dead ensure_single_subscription + update_or_create_subscription
- devices: merge identical daily/non-daily branches in confirm_change_devices
2026-03-26 11:00:15 +03:00
Fringg fec374edba chore: ruff format 2026-03-26 10:34:14 +03:00
Fringg 948e4791f4 fix: multi-tariff Stage 5 fixes — auth sync, notifications, cart, race guard
HIGH fixes:
- auth.py: profile description sync now iterates all per-subscription
  remnawave_uuids in multi-tariff mode
- admin_users: sync_from_panel uses subscription UUIDs for panel lookup,
  does not overwrite user.remnawave_uuid in multi-tariff

MEDIUM fixes:
- monitoring_service: _send_subscription_expired_notification now takes
  subscription param, uses se:{sub_id} in multi-tariff
- remnawave_webhook_service: _get_renew_keyboard accepts subscription_id,
  all 7 callers pass it
- recurrent_payment_service: _build_extend_keyboard with subscription_id
- user_service: balance notification keyboards use menu_subscription in
  multi-tariff instead of bare subscription_extend
- autopay.py + purchase.py: per-subscription cart deletion instead of
  global delete_user_cart where subscription context available
- subscription_auto_purchase_service: 60-sec race guard changed from
  per-user to per-subscription (checks subscription.updated_at)
2026-03-26 09:15:50 +03:00
Fringg a49e52cc92 fix: multi-tariff Stage 4 critical fixes — keyboards, guest purchase, monitoring, tariff deletion
- inline.py: open_subscription_link/subscription_connect callbacks now include
  :{subscription_id} suffix in multi-tariff mode. Main menu uses subscription_connect
  (picker) instead of bare open_subscription_link.
- guest_purchase_service: activate_purchase non-tariff path uses proper ordering
  (non-daily, max days_left) instead of arbitrary _active[0]
- monitoring_service: _send_expired_day1_notification and discount notification
  keyboards use se:{subscription.id} in multi-tariff (2 more hardcoded callbacks fixed)
- admin/tariffs: delete_tariff_confirmed now checks active subscription count
  before deletion (RESTRICT FK). Prompt shows blocking message when active subs exist.
  New CRUD function get_active_subscriptions_count_by_tariff_id.
2026-03-26 08:46:00 +03:00
Fringg 5724906517 fix: multi-tariff code review — 13 critical/high bugs fixed across 14 files
CRITICAL fixes:
- promocode_service: NameError (subscription_id not passed), TypeError (dict
  returns), savepoint without commit, dead else branch
- cabinet status/autopay/renewal: resolve_subscription() instead of
  user.subscription fallback in multi-tariff mode
- cabinet devices: MultipleResultsFound crash on 3 POST endpoints
- webhook service: IDOR returning cross-user subscription
- monitoring_service: real expiring notification keyboard with se:{sub_id}

HIGH fixes:
- subscription_purchase_service: FOR UPDATE on both branches of submit_purchase
- miniapp: 8 endpoints now pass subscription_id to _ensure_paid_subscription
- inline.py: se:{subscription_id} callback for expiring keyboard
- tariff_purchase: TransactionType.FAILED_REFUND + _persist_failed_refund()
- account_merge_service: panel sync after subscription transfer
- webhook service: .limit(1) on fallback queries to prevent MultipleResultsFound
2026-03-26 07:26:53 +03:00
c0mrade 05d1ae0560 fix: notifications include tariff name for multi-subscription clarity
Expiry, autopay success, daily charge, and traffic reset notifications
now append tariff name when multi-tariff is enabled, so users know which
subscription the notification is about.
2026-03-25 15:11:39 +03:00
c0mrade d87fb47e88 fix: multi-subscription UUID resolution and ownership validation
- Add _resolve_panel_uuid helper for per-subscription UUID in multi-tariff mode
- Add user ownership validation (user_id check) to all subscription queries
- Add unique partial index on (user_id, tariff_id) for active subscriptions
- Generate remnawave_short_id for new subscriptions in all creation paths
- Fix trial endpoints to check all user subscriptions, not just first
- Fix channel member handler to enable/disable per-subscription UUIDs
- Fix channel checker middleware for multi-subscription iteration
- Fix tariff switch, traffic, and device endpoints to use correct panel UUID
- Fix monitoring, auto-purchase, renewal services for multi-subscription
- Fix user_service, miniapp, subscriptions and users webapi routes
2026-03-23 18:45:46 +03:00
c0mrade d071269b8c fix: comprehensive multi-subscription audit fixes across routes, handlers, and services
- Fix UUID resolution in monitoring and webhook services for multi-tariff mode
- Update cabinet routes to properly resolve per-subscription UUIDs
- Fix account merge service for multi-tariff subscription transfers
- Update admin handlers (users, promo_offers, servers) for multi-subscription
- Fix traffic, devices, servers, daily subscription modules
- Update payment handlers (stars, yookassa) and purchase services
- Fix broadcast, promocode, and subscription auto-purchase services
- Add multi-subscription support to keyboards and localization
- Fix CRUD operations for subscription queries
- Resolve code quality issues (ruff linting)
2026-03-23 18:40:23 +03:00
c0mrade 335be66980 feat: multi-subscription support (1 user = N subscriptions)
- Add MULTI_TARIFF_ENABLED feature flag for gradual rollout
- Migration 0041: remove unique constraint on subscriptions.user_id
- Migration 0042: add remnawave_short_id (NOT NULL, UNIQUE) to subscriptions
- Each subscription gets its own Remnawave user (user_{tg_id}_{short_id})
- Add _resolve_subscription() to all 30+ bot handlers for per-subscription routing
- Add my_subscriptions.py with list/detail views and delegation handlers
- Refactor cabinet subscription.py (4687 lines -> 10 focused modules)
- Add subscription_id parameter to all cabinet API endpoints
- Adapt all services: autopay, wheel, contests, campaign, guest purchase,
  monitoring, blocked users, account merge, promo codes, payments
- Replace all user.subscription (singular) with user.subscriptions iteration
- IDOR protection via get_subscription_by_id_for_user on all endpoints
- Full backward compatibility: MULTI_TARIFF_ENABLED=False = legacy behavior
2026-03-23 18:37:17 +03:00
Fringg 9de34900a2 fix: comprehensive html.escape() for all user/admin data in Telegram HTML messages
Bot uses default HTML parse mode — all messages are HTML-parsed by Telegram.
Added html.escape() to all user-controlled and admin-controlled strings
before interpolation into HTML messages to prevent injection and parse errors.

49 files, ~250+ injection points fixed:
- user.full_name, first_name across all handlers and services
- tariff.name/description in purchase flow, admin panel, auto-purchase service
- campaign.name, start_parameter in admin and user-facing handlers
- group.name, promo_group.name across promo management
- contest.title, prize_text, leaderboard names (including public channels)
- transaction.description (contains raw user.full_name from referral service)
- restriction_reason across all balance and subscription handlers
- ticket.title, message_text, poll.title, poll.description
- welcome text template placeholders (first_name, username)
- maintenance reason, admin_name, selected_prize.display_name

New helpers in app/utils/formatting.py:
- safe_html_name() for escaping display names
- user_html_link() replacing 15+ duplicated inline link patterns
2026-03-23 08:06:15 +03:00
Fringg 958ec489a2 fix: respect per-channel disable_on_leave settings in monitoring service
The background monitoring service was deactivating trial subscriptions
when users unsubscribed from channels, ignoring per-channel
disable_trial_on_leave and disable_paid_on_leave settings that the
real-time handler and middleware already respected.

Changes:
- Use shared should_disable_subscription() for all 3 deactivation paths
- Add global CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE override in should_disable_subscription
- Add admin skip in monitoring (consistent with handler/middleware)
- Replace inline reactivation with reactivate_subscription() CRUD
- Switch to enable_remnawave_user() instead of heavy update_remnawave_user()
- Add commit=False to deactivate/reactivate/record/clear_notification for batch atomicity
- Include paid subs in monitoring when any channel has disable_paid_on_leave=True
- Use skip_deactivation flag instead of early return to preserve reactivation path
- Commit batch before create_remnawave_user which internally commits
2026-03-23 05:54:26 +03:00
Fringg 48265f1cd4 chore: remove redundant comments from DISABLED status fix 2026-03-21 09:07:43 +03:00
Fringg 3b9568fcc1 style: format long lines in monitoring and subscription services 2026-03-21 09:06:01 +03:00
Fringg 79cfcbcece fix: send DISABLED instead of EXPIRED status to RemnaWave API
RemnaWave API only accepts ACTIVE/DISABLED for user status updates —
EXPIRED and LIMITED are managed internally. The bot was sending EXPIRED
status and past expireAt dates, causing 400 validation errors.

- Change UserStatus.EXPIRED → UserStatus.DISABLED in all 5 call sites
- Add 1-minute buffer to expire_at for inactive subscriptions to avoid
  "expiration date in the past" rejections (matches _safe_expire_at_for_panel)
- Include TRIAL status in is_actually_active checks (consistent with
  remnawave_service.py sync_users_to_panel)
2026-03-21 09:00:59 +03:00
Fringg 57c5c679ee fix: address review findings for guest purchase recovery
- Add FOR UPDATE to recovery path in try_fulfill_guest_purchase to prevent
  TOCTOU race that could overwrite DELIVERED back to PAID
- Isolate monitoring phases with independent try/except so Phase 1 failure
  does not block Phase 2/3
- Optimize recover_stuck_pending_purchases to select only token and
  payment_method columns instead of full ORM objects
- Remove dead elif branch in stars_payments.py (try_fulfill_guest_purchase
  no longer returns False)
- Add Phase 3 comment for consistency
2026-03-20 06:45:21 +03:00
Fringg 2781236011 fix: prevent guest purchases from getting stuck in PENDING/FAILED status
- Mark guest purchases as PAID (not FAILED) on transient fulfillment errors
  so monitoring service can retry them automatically
- Use fresh AsyncSessionLocal session for recovery to avoid tainted-session
  issues after rollback
- Add status guard to prevent overwriting terminal states (DELIVERED, etc.)
- Add recover_stuck_pending_purchases() to detect PENDING purchases where
  provider payment already succeeded (checks YooKassa payments table)
- Use SELECT ... FOR UPDATE to prevent TOCTOU races in recovery
- Add 3-phase monitoring pipeline: recover PENDING → retry PAID → retry
  PENDING_ACTIVATION
- Extract shared _resolve_base_payment_method() helper
2026-03-20 06:39:01 +03:00
Fringg f1d45343e9 fix: handle None autopay_days_before in autopayment processing
Existing subscriptions may have NULL autopay_days_before in DB,
causing TypeError in min(None, 3). Default to 3 when None.
2026-03-18 21:16:08 +03:00
Fringg c0b282a189 fix: уведомление об истечении подписки теперь учитывает autopay_enabled пользователя
- Статус автоплатежа в уведомлении основан на subscription.autopay_enabled, а не на глобальном ENABLE_AUTOPAY
- Продление с баланса (_process_autopayments) работает всегда при autopay_enabled=True
- Рекуррентные карточные платежи по-прежнему за гейтом ENABLE_AUTOPAY + YOOKASSA_RECURRENT_ENABLED
2026-03-16 04:04:01 +03:00
Fringg 3d68db0a51 fix: не пересылать externalSquadUuid в рутинных обновлениях RemnaWave
Стейловый externalSquadUuid (c6c0a338-062d-4d3a-826d-7015a24d681c) из тарифа
не существует в таблице ExternalSquads панели → FK violation → A039.
Теперь externalSquadUuid отправляется только при sync_squads=True (создание подписки).
2026-03-16 03:47:13 +03:00
Fringg 4aaf0ddd25 fix: не пересылать activeInternalSquads в рутинных обновлениях RemnaWave (A039)
Стейловые UUID сквадов в connected_squads вызывали FK violation в RemnaWave → A039.
- update_remnawave_user: добавлен параметр sync_squads (default=False)
- Сквады шлются только при явном sync_squads=True (promo_offer, countries)
- monitoring_service: убрана пересылка сквадов в рутинном sync
- Расширен лог PATCH payload для диагностики
2026-03-16 03:41:56 +03:00
Fringg e453521098 fix: устранена отправка externalSquadUuid=null в RemnaWave API (A039) и исправлен reduce_devices
- reduce_devices: убрано молчаливое проглатывание ошибки RemnaWave, теперь при неудаче делается rollback и возвращается HTTP 502
- Убрана отправка external_squad_uuid=None в 8 местах: subscription_service, monitoring_service, remnawave_service, admin/users, cabinet/admin_users
2026-03-16 03:25:24 +03:00
Fringg 8d3cd50098 refactor: централизация всех расчётов цен в PricingEngine
- Мигрирован confirm_purchase() на calculate_classic_new_subscription_price()
- Мигрирован compute_simple_subscription_price на делегацию в PricingEngine
- Мигрирован handle_custom_confirm на calculate_tariff_purchase_price()
- Мигрированы daily confirm handlers (confirm_daily_tariff_purchase,
  confirm_daily_tariff_switch, confirm_instant_switch daily path)
- Мигрирован gift.py на calculate_tariff_purchase_price()
- Мигрированы FSM cache prices (select_period, select_devices, toggle_country)
- Добавлен lock_user_for_pricing в admin_buy_tariff_execute (TOCTOU fix)
- Добавлен lock + recompute в _auto_add_devices и _auto_add_traffic
- Исправлено двойное применение promo-offer в simple_subscription (критический баг)
- Унифицирован daily price display (group+offer) на всех 6 поверхностях
- PricingEngine.get_addon_discount_percent: добавлен promo_group= kwarg
- PricingEngine._calculate_switch_to/from_daily: добавлен promo-offer discount
- Удалён мёртвый код из common.py (_get_addon_discount_percent_for_user)
- Miniapp period_discounts: исправлен доступ через get_discount_percent()
2026-03-16 03:10:22 +03:00
Fringg e24b911283 refactor: migrate all callers to pricing_engine singleton + fix miniapp discount
- 13 PricingEngine() instantiation sites → import pricing_engine singleton
- miniapp _apply_promo_discount now delegates to PricingEngine.apply_discount
  (fixes float division vs floor division inconsistency)
2026-03-13 05:22:18 +03:00
Fringg f59b215645 style: fix import sorting and formatting after lint
ruff auto-fix for import ordering in cabinet/subscription.py and
formatting adjustments across changed files.
2026-03-12 22:58:35 +03:00
Fringg 978f68e7be refactor: migrate recurrent and monitoring services to PricingEngine
Mechanical re-point of calculate_renewal_price calls to use unified
PricingEngine. Both services now get consistent pricing with correct
discount formulas and server fallback behavior.
2026-03-12 22:50:13 +03:00
firewookie c84dbf82fc update 2026-03-10 14:50:26 +05:00
FireWookie 9ad684c8c9 Merge pull request #7 from FireWookie/dev
Dev
2026-03-10 14:42:53 +05:00
FireWookie d147be0316 Merge pull request #6 from FireWookie/dev
Dev
2026-03-10 14:42:05 +05:00
firewookie fd3466b75c fix PR problems 2026-03-10 14:29:29 +05:00
Fringg 5c34656476 fix: address review findings from 6-agent audit
1. Truncate tokens to 12 chars in all API responses (SentGift,
   PendingGift, ReceivedGift, PurchaseStatus, PurchaseResponse,
   return URL) — full token no longer leaves the server
2. Status endpoint supports prefix-based token lookup
3. create_paid_subscription/replace_subscription accept commit=False
   — activate_purchase now uses single atomic commit for subscription
   + purchase status update (fixes double-commit gap)
4. Bot handler uses flush() instead of commit() before svc_activate
   — consistent with cabinet endpoint, allows rollback on failure
5. Add retry_stuck_pending_activation() for purchases stuck in
   PENDING_ACTIVATION status (10 min threshold)
6. Add varchar_pattern_ops index for prefix queries on token column
2026-03-10 07:02:14 +03:00
Fringg 6a4140e3e2 fix: harden gift subscription feature after multi-agent review
- Add self-gift prevention (telegram username + email)
- Unify 404 response on purchase status (eliminate token oracle)
- Add period_days upper bound (le=3650) in schema
- Handle NULL paid_at in retry query with or_()
- Capture purchase_token before fulfill_purchase (session safety)
- Upgrade Bot API pre-check logging to warning level
- Add exc_info=True for monitoring retry errors
- Add database indexes: (user_id, is_gift, status), (status, paid_at), buyer_user_id
- Use datetime instead of str for created_at in PendingGiftResponse
- Align GuestPurchase model __table_args__ with all migrations
2026-03-09 20:34:39 +03:00
firewookie be2ec091a6 Правки по замечаниям 2026-03-09 14:06:00 +05:00
Fringg 2649e12f64 fix: use parsed HTML length for Telegram caption limit checks
Replace hardcoded raw HTML length checks (len(text) <= 900/1000/1024)
with centralized caption_exceeds_telegram_limit() that strips HTML tags
and unescapes entities before measuring against the real 1024-char limit.
Fixes logo disappearing when promo discounts add HTML markup to captions.
2026-03-09 02:43:43 +03:00
FireWookie 26daf9f6c8 Merge pull request #4 from FireWookie/main
Merge as main project
2026-03-08 09:32:37 +05:00
Fringg c10d6780ba feat: add external squad support for tariffs
- Add external_squad_uuid column to Tariff model with Alembic migration
- Add external_squad_uuid parameter to RemnaWave API create_user/update_user
- Pass external squad from tariff to RemnaWave on subscription creation/update
- Sync external squad in monitoring service, sync service, admin user management
- Clear external squad when tariff has none (consistent across all call sites)
- Add GET /available-external-squads endpoint with UUID validation and response model
- Update tariff schemas with UUID pattern validation
- Fix db.refresh to include tariff relationship for async safety
2026-03-07 05:44:19 +03:00
firewookie 8620aaedb1 fix formatting 2026-03-06 09:54:55 +05:00
firewookie aaffc26a90 - Интеграция рекурентов от Юкассы
- Багфикс личного кабинета
2026-03-06 09:47:58 +05:00
Fringg 53a67d7573 chore: автоформатирование ruff 2026-03-05 10:56:17 +03:00
Fringg 9f35088788 fix: добавить create_transaction и admin-уведомления для автопродлений
- monitoring_service: добавлен create_transaction(SUBSCRIPTION_PAYMENT, BALANCE)
  и admin-уведомление через with_admin_notification_service
- daily_subscription_service: исправлен PaymentMethod.MANUAL → BALANCE,
  добавлено admin-уведомление через with_admin_notification_service
- subscription_auto_purchase_service: admin-уведомления вынесены из блока
  if bot: и используют with_admin_notification_service (3 локации)
2026-03-05 10:15:25 +03:00
Fringg c8ef808539 fix: consume promo offer in tariff_purchase.py, fix negative transaction amount
- Add consume_promo_offer to 5 call sites in tariff_purchase.py where
  _get_user_period_discount() stacks promo_offer into blended discount
  (lines 823, 1134, 1706, 2238, 2971)
- Fix negative amount_kopeks in miniapp.py:5351 transaction record
  (was -final_total, all other SUBSCRIPTION_PAYMENT use positive)
- Replace duplicate _get_user_promo_offer_discount_percent in
  monitoring_service with shared get_user_active_promo_discount_percent
2026-03-05 07:25:27 +03:00
Fringg 5f2d855702 fix: add missing mark_as_paid_subscription, fix operation order, remove dead code
- Add mark_as_paid_subscription=True to cabinet trial activation
- Reorder menu.py: charge balance BEFORE creating subscription (prevents orphaned subscription)
- Remove dead _consume_user_promo_offer_discount method from monitoring_service (55 lines)
- Remove unused imports (get_latest_claimed_offer_for_user, log_promo_offer_action)
- Fix inconsistent _get_promo import alias to use full function name
2026-03-05 07:02:12 +03:00
Fringg 0466528925 fix: centralize balance deduction and fix unchecked return values
- Replace inline SELECT FOR UPDATE in renew_subscription with subtract_user_balance
- Replace direct balance_kopeks -= in trial activation with subtract_user_balance
- Add success checks to 4 unchecked subtract_user_balance calls (devices x2, menu, tariff switch)
- Add consume_promo_offer to monitoring_service autopay (was non-atomic)
- Add mark_as_paid_subscription=True to trial_activation_service, daily_subscription_service, admin purchase paths
- Remove 3 redundant has_had_paid_subscription assignments in auto_purchase_service
- Fix stale cart consume_promo_offer: compute from live user state instead of cart data
2026-03-05 06:45:57 +03:00
Fringg e4a6aad621 fix: centralize has_had_paid_subscription into subtract_user_balance
Add mark_as_paid_subscription parameter to subtract_user_balance() that
atomically sets has_had_paid_subscription=True within the same FOR UPDATE
transaction as the balance deduction. This closes ALL purchase paths:

- Cabinet renew: add SELECT FOR UPDATE row lock (fix race condition),
  set has_had_paid_subscription atomically, remove standalone call
- Cabinet purchase_tariff: pass consume_promo_offer to subtract_user_balance
  (fix: inline clearing was wiped by db.refresh), remove standalone call
- Cabinet switch_tariff: add mark_as_paid_subscription=True
- Auto-extend: add mark_as_paid_subscription, remove standalone call
- Auto-purchase tariff: add consume_promo_offer + mark_as_paid_subscription
- Auto-purchase daily: add mark_as_paid_subscription
- Bot purchase/extend/trial handlers: add mark_as_paid_subscription
- All 8 tariff_purchase.py handlers: add mark_as_paid_subscription
- Both simple_subscription.py handlers: add mark_as_paid_subscription
- Menu smart activation: add mark_as_paid_subscription
- Monitoring autopay: add mark_as_paid_subscription
- Renewal service finalize: add mark_as_paid_subscription
- MiniApp purchase service: add mark_as_paid_subscription, remove standalone
- MiniApp renewal/tariff/switch: add mark_as_paid_subscription
2026-03-05 06:29:34 +03:00
Fringg 618c936ac9 fix: close remaining daily subscription expire paths
- get_all_subscriptions: add selectinload(tariff) so validate_and_fix guard works
- get_subscriptions_batch: add selectinload(tariff) for sync flows
- get_expiring_subscriptions: exclude active daily subs (prevents spurious notifications)
- update_remnawave_user: add daily guard to prevent expire during panel sync
- _handle_user_disabled webhook: add daily guard to prevent deactivation
2026-03-04 05:46:37 +03:00
Fringg 0ed6397fa9 fix: prevent daily subscriptions from being expired by middleware/CRUD/webhook
Daily subscriptions have end_date = +24h, and between 30-min check cycles
they would get expired by 5 different code paths before DailySubscriptionService
could charge and renew them. Users saw "subscription expired" while having balance.

Root cause fixes (6 paths protected):
- subscription_checker middleware: skip active daily subscriptions
- check_and_update_subscription_status CRUD: skip active daily subscriptions
- monitoring_service: run autopay BEFORE expired check, expand query to
  include recently-expired subscriptions (2h window)
- remnawave_webhook_service: _handle_user_expired skips daily tariffs
- remnawave_service: validate_and_fix_subscriptions skips daily tariffs

Recovery mechanisms:
- New get_expired_daily_subscriptions_for_recovery() CRUD function
- DailySubscriptionService.process_auto_resume() restores DISABLED (balance
  topped up) and EXPIRED (incorrectly expired) daily subscriptions
- Runs before daily charges in each monitoring cycle
2026-03-04 05:12:19 +03:00
Fringg dce9eaa597 fix: reset traffic purchases on expired subscription renewal + pricing fixes
- Reset TrafficPurchase records and purchased_traffic_gb when renewing
  expired subscriptions (was incorrectly preserving stale purchases,
  inflating traffic limit e.g. 100GB+20GB=120GB instead of fresh 100GB)
- Fix in extend_subscription() CRUD, cabinet /renew, bot handlers,
  simple_subscription handlers
- Add RemnaWave sync to cabinet /renew endpoint after subscription changes
- Fix device_price_kopeks=0 falsy-zero bug (11+ instances, or → is not None)
- Fix double-increment of purchased_traffic_gb in cabinet traffic purchase
- Fix orphaned TrafficPurchase records in 5 locations (replace_subscription,
  extend_subscription fixed_with_topup, classic mode, switch_tariff,
  purchase.py is_traffic_fixed)
- Fix admin_users.py UnboundLocalError from TrafficPurchase inline import
  shadowing module-level import
- Standardize pricing order: base + devices → promo_group → promo_offer
  across all 5+ pricing paths (cabinet, miniapp, autopay, auto-purchase,
  monitoring)
- Fix exception handlers in calculate_renewal_price (raise instead of
  returning fallback 0)
- Fix monitoring_service double-discount (promo_offer applied twice)
- Fix auto-purchase _get_tariff_price_for_period return type to tuple
  (base_price, discount_percent) so callers add devices before discount
- Pass traffic_limit_gb/device_limit to extend_subscription in
  simple_subscription.py instead of manual overwrites
2026-03-04 04:46:29 +03:00
Fringg 314c892c4d style: format monitoring_service.py 2026-02-24 03:30:00 +03:00
Fringg 2aead9a68b fix: improve deduplication log message wording in monitoring service 2026-02-24 03:16:03 +03:00
Fringg 8375d7ecc5 feat: add multi-channel mandatory subscription system
- Multi-channel subscription enforcement via middleware, events, and cabinet API
- 3-layer cache architecture: Redis -> PostgreSQL -> rate-limited Telegram API
- ChatMemberUpdated event-driven tracking with automatic VPN access control
- Admin management via bot FSM handler and REST API with full CRUD
- Channel ID normalization: @username resolved to numeric ID at creation time
- Fail-closed error handling: API errors deny access (security-first)
- Background reconciliation with keyset pagination (100 per batch)
- Per-user rate limiting on subscription check button (5s cooldown)
- Redis connection pooling via cache singleton (no per-request connections)
- Database: channel_id index, multi-row upsert optimization
- Localization: en, ru, zh, fa, ua translations for all new strings
- Frontend blocking UI with channel list and subscription status
- Admin channel management page with toggle, delete, and create
2026-02-24 02:50:31 +03:00