Commit Graph

129 Commits

Author SHA1 Message Date
Fringg ecde2fb8f0 feat: Apple IAP integration with security hardening 2026-05-04 05:49:38 +03:00
Danila Yudin 905cea68b4 fix: grant all available squads for unrestricted trials (#2897) 2026-04-21 08:01:28 +03:00
Fringg 977950b97f fix: address review issues in PR #2829 webhook intentional deletion guard
5 issues found by review agents and fixed:

1. Intentional deletion guard was at top of process_event, skipping ALL
   cleanup (subscription URLs, server counts, expired marking). Moved
   check into _handle_user_deleted so cleanup still runs but re-creation
   is suppressed via subscription_still_valid=False.

2. Variable shadowing: telegram_id loop var in any() generator shadowed
   the outer telegram_id local. Renamed to tid/uid.

3. No hard cap on in-memory dicts: added _MAX_INTENTIONAL_ENTRIES=10000
   with early return in mark_intentional_panel_deletion.

4. mark_intentional_panel_deletion called inside for-loop with single
   UUID — race window if webhook from first delete arrives before
   second UUID is marked. Moved to before the loop with all UUIDs.

5. Tests: @pytest.mark.anyio → @pytest.mark.anyio('asyncio') for
   consistency. Replaced process_event(db=None) test with direct
   mark+detect unit tests + hard cap test.
2026-04-02 06:34:35 +03:00
Fringg 6f6b9fa039 Merge branch 'yazhog/main' into dev 2026-04-02 06:30:45 +03:00
Fringg 033d0da5e0 fix: remove non-existent Platega method code 10, rename 11 to Карты (RUB)
Platega API defines: 2=СБП, 11=Карточный эквайринг, 12=Международная,
13=Крипто. Code 10 does not exist in their API but was defined in our
config as "Банковские карты (RUB)", while real code 11 was mislabeled
as "Банковские карты". This caused two card options to appear in the
admin panel, one of which didn't work.

- Removed code 10 from definitions, defaults, allowed set, .env.example
- Renamed code 11: "Банковские карты" → "Карты (RUB)"
- Removed redundant filter in handlers/balance/platega.py
- Updated tests to match
2026-04-02 05:44:24 +03:00
yazhog b1820c651d Fix RemnaWave webhook deletion race 2026-03-30 15:56:45 +03:00
Fringg aec04f0085 fix: correctly price unlimited traffic (0 GB) in classic subscription mode
_calculate_traffic_price treated unlimited traffic as free because
base_gb=0 triggered the `if base_gb > 0 else 0` guard, skipping
the price lookup. Added early return for total_gb==0 to use the
configured unlimited tier price.
2026-03-23 06:42:57 +03:00
Fringg b40a812f3a fix: fix Platega and CryptoBot webhook verification
Platega: handle verification ping POST without auth headers (empty body → 200 OK)
CryptoBot: always use API token for signature verification per docs, not WEBHOOK_SECRET
CryptoBot: reject requests without signature in both FastAPI and aiohttp handlers
Remove dead self.webhook_secret from CryptoBotService
Update tests to match new behavior
2026-03-18 20:11:55 +03:00
Fringg e1bcb1ba91 fix: реферальный бонус инвайтера — сумма вместо максимума, защита флага первого пополнения
- referral_service: inviter_bonus = fixed + commission вместо max(fixed, commission)
- 13 платёжных провайдеров: has_made_first_topup ставится только для нереферальных юзеров
- riopay: критический фикс — флаг ставился до вызова referral_service
- Обновлены уведомления с разбивкой бонуса
- Исправлен и дополнен тест referral_service
2026-03-16 03:57:50 +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 04697fd4cb style: ruff format 8 files 2026-03-13 05:53:02 +03:00
Fringg c9f2dffabf fix: address 6-agent review findings for PricingEngine
H1: log error when tariff_id set but tariff relationship not loaded
H2: warn on CLASSIC_PERIOD_PRICES→PERIOD_PRICES fallback
M1: fix float division in miniapp tariff purchase (use PricingEngine.apply_discount)
M2: fix format_period Russian pluralization for teen-hundreds (111-119, etc.)
M3: deduplicate _resolve_discount_percent — import from pricing_utils
M4: fix N+1 queries in compute_simple_subscription_price (batch fetch)
M5: add period_days validation tests (negative, zero, float)
M6: add user=None tests for tariff and classic modes
M7: fix float division in calculate_prorated_price (use // instead of /)
L1: add context to _calculate_servers_price error log
L2: add comment clarifying ClassicBreakdown.group_discount_pct type
L3: add test for original_total property
L4: inline _apply_percentage_discount wrapper in subscription_purchase_service
L5: replace global _server_id_counter with itertools.count() in tests
2026-03-13 05:45:46 +03:00
Fringg 3a3bd9d499 test: expand PricingEngine tests + update CryptoBot payment tests
- Add 45 unit tests covering tariff/classic modes, discounts, edge cases
- Update CryptoBot payment tests for new PricingEngine integration
- Add original_total identity tests for both pricing modes
2026-03-13 05:12:47 +03:00
Fringg 02e5401327 feat: implement calculate_renewal_price with tariff and classic modes
Add the main public method calculate_renewal_price to PricingEngine,
routing to _calculate_tariff_mode or _calculate_classic_mode based on
whether the subscription has a linked tariff. Both modes apply stacked
discounts (promo-group then promo-offer). Classic mode tries
CLASSIC_PERIOD_PRICES first, falling back to PERIOD_PRICES. Adds 8
new tests covering both modes, discounts, extra devices, and fallback.
2026-03-12 22:29:44 +03:00
Fringg 88369eec50 feat: add _calculate_servers_price (fixed fallback) and _calculate_traffic_price
_calculate_servers_price ALWAYS uses real server.price_kopeks even when
is_available=False or is_full=True, fixing the silent zero-price bug.
_calculate_traffic_price separates base from purchased GB to prevent
purchased top-ups from inflating the tier lookup.
2026-03-12 22:20:48 +03:00
Fringg 83ca51cd5b feat: add RenewalPricing dataclass and PricingEngine discount methods 2026-03-12 22:18:08 +03:00
Fringg 3c96c2affd fix: 3 bugs — notification type, referral with channel sub, BOT_USERNAME
1. Admin notification showed "renewal" instead of "first purchase" for new
   users because has_had_paid_subscription was set before notification.
   All 21 call sites now pass explicit purchase_type.

2. Partner referral not counted when mandatory channel subscription enabled.
   required_sub_channel_check saved campaign_id but not referrer_id from
   campaign.partner_user_id. Also removed duplicate DB query.

3. BOT_USERNAME auto-detection moved before web server start to close
   race window on /cabinet/branding/telegram-widget endpoint.
2026-03-10 21:19:18 +03:00
firewookie fd3466b75c fix PR problems 2026-03-10 14:29:29 +05:00
Fringg 531d5cff30 fix: negative balance transfer, linking state validation, referrer migration
- Transfer negative balances on merge (debt must not vanish)
- Validate OAuth state was initiated for account linking flow
- Transfer secondary's referrer to primary when primary has none
- Type MergePreviewSubscription schema (replace dict[str, Any])
- Cap restore_merge_token TTL to prevent clock-skew extension
- Add 4 new tests (negative balance, referrer transfer scenarios)
2026-03-04 15:57:03 +03:00
Fringg 8ee97ba1ba test: relax hardcoded execute count, add telegram_id conflict assertion
- Change == 17 to >= 17 so test doesn't break on new bulk updates
- Assert secondary.telegram_id is None in conflict test
2026-03-04 15:46:51 +03:00
Fringg 9582758d1c fix: restore merge token on DB failure, fix partner_status priority
- Add restore_merge_token() to re-store consumed token if execute_merge
  or db.commit fails, allowing the user to retry instead of being stuck
- Fix partner_status priority: PENDING (2) now beats REJECTED (1), so
  an active application is not lost during merge
- Add tests for pending-vs-rejected edge cases (47 tests total)
2026-03-04 15:29:50 +03:00
Fringg f204b67880 fix: delete cross-referral earnings before bulk reassignment, clear secondary.referred_by_id
Prevents data corruption when merging accounts that have mutual referral
relationships. Cross-referral ReferralEarning rows are now deleted before
any bulk UPDATE to avoid self-referral records. Secondary's referred_by_id
is cleared during cleanup to prevent orphaned FK references.
2026-03-04 15:22:23 +03:00
Fringg db61365e11 fix: prevent self-referral loops, invalidate all sessions on merge
- Add User.id != primary.id filter to referred_by_id reassignment to
  prevent self-referral loops when primary was referred by secondary
- Clear primary.referred_by_id if it pointed to secondary
- Add exclusion filter to ReferralEarning.referral_id reassignment to
  prevent user_id == referral_id rows
- Invalidate refresh tokens for BOTH primary and secondary during merge
  (primary gets a fresh session after merge)
- Fix duplicate step 4 comment numbering in execute_merge_endpoint
- Add referred_by_id field to test fixture _make_user
2026-03-04 15:01:51 +03:00
Fringg 64ee0459e4 fix: second round review fixes for account merge
- Rename _compute_auth_methods to compute_auth_methods (public API)
- Add Literal type to _handle_subscription_merge param
- Add Literal type to keep_from in route handler
- Add Path(min_length=32, max_length=64) on merge_token params
- Import Path and Literal in account_linking routes
2026-03-04 07:55:26 +03:00
Fringg d855e9e47f fix: harden account merge security and correctness
- Clear ALL unique constraint fields on secondary user after merge
  (telegram_id, OAuth IDs, email, referral_code, remnawave_uuid)
- Add Literal type + runtime validation for keep_subscription_from
- Reject merge when primary user is deleted
- Validate OAuth state user_id matches authenticated user in link callback
- Replace leaked ValueError messages with generic error detail
- Fix exc_info usage for idiomatic structlog
- Fix _get_remnawave_api return type to AsyncIterator
- Remove unnecessary from __future__ import annotations
- Add 3 new tests (42 total, all passing)
2026-03-04 07:46:07 +03:00
Fringg dc7b8dc72a feat: account linking and merge system for cabinet
Add OAuth provider linking/unlinking endpoints, merge token service
(Redis-backed, 30-min TTL), and atomic account merge executor that
transfers OAuth IDs, telegram_id, email, balance, subscriptions,
transactions, payments, referral data, and partner status between
two user accounts. Unchosen subscription is deleted from RemnaWave
with disable as fallback.

Includes 39 unit tests covering all merge scenarios.
2026-03-04 07:24:15 +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
Fringg 90278f1f5f style: fix ruff formatting in broadcast_service and tests 2026-02-17 18:50:25 +03:00
Fringg 10e231e52e feat: blocked user detection during broadcasts, filter blocked from all notifications
- Broadcast tri-state return: 'sent'/'blocked'/'failed' with blocked_count tracking
- Background cleanup: mark blocked users + disable their subscriptions + Remnawave
- blocked_count in BroadcastHistory model, schemas, API responses, admin UI
- Filter User.status==ACTIVE in subscription queries: get_expiring_subscriptions,
  get_expired_subscriptions, get_subscriptions_for_autopay,
  get_daily_subscriptions_for_charge, get_disabled_daily_subscriptions_for_resume
- Guard in notification_delivery_service.send_notification for BLOCKED/DELETED users
- Fix subscription tariff switch: preserve remaining days with total_seconds()
- Fix redundant local UTC imports across 16 files
- Fix test mocks: add **kwargs, correct assertion, remove dead expression
2026-02-17 18:37:25 +03:00
Fringg 094609005a fix: add naive datetime guards to parsers and fix test datetime literals 2026-02-17 05:00:13 +03:00
Fringg eb18994b7d fix: complete datetime.utcnow() → datetime.now(UTC) migration
- Migrate 660+ datetime.utcnow() across 153 files to datetime.now(UTC)
- Migrate 30+ datetime.now() without UTC to datetime.now(UTC)
- Convert all 170 DateTime columns to DateTime(timezone=True)
- Add migrate_datetime_to_timestamptz() in universal_migration with SET LOCAL timezone='UTC' safety
- Remove 70+ .replace(tzinfo=None) workarounds
- Fix utcfromtimestamp → fromtimestamp(..., tz=UTC)
- Fix fromtimestamp() without tz= (system_logs, backup_service, referral_diagnostics)
- Fix fromisoformat/isoparse to ensure aware output (platega, yookassa, wata, miniapp, nalogo)
- Fix strptime() to add .replace(tzinfo=UTC) (backup_service, referral_diagnostics)
- Fix datetime.combine() to include tzinfo=UTC (remnawave_sync, traffic_monitoring)
- Fix datetime.max/datetime.min sentinels with .replace(tzinfo=UTC)
- Rename panel_datetime_to_naive_utc → panel_datetime_to_utc
- Remove DTZ003 from ruff ignore list
2026-02-17 04:45:40 +03:00
Fringg ee2e79db31 refactor: remove modem functionality from classic subscriptions
Remove all modem purchase/management code:
- Delete modem handler, service, and tests
- Remove modem button from keyboards and admin panel
- Remove modem pricing from calculations
- Remove modem REST API endpoint and schemas
- Remove modem decorator, config settings, and notification formatting
- Keep DB column and migration for backwards compatibility
2026-02-11 21:14:08 +03:00
Fringg 184c52d4ea feat: webhook protection — prevent sync/monitoring from overwriting webhook data
Add last_webhook_update_at timestamp to Subscription model. When a webhook
handler modifies a subscription, it stamps this field. Auto-sync, monitoring,
and force-check services skip subscriptions updated by webhook within the
last 60 seconds, preventing stale panel data from overwriting fresh
real-time changes.

- Add last_webhook_update_at column + migration
- Stamp all 8 webhook handlers with commit in every code path
- Add is_recently_updated_by_webhook() guard in 12 sync/monitoring paths
- Add REMNAWAVE_WEBHOOK_* variables to .env.example
- Add webhook setup documentation to README with Caddy/nginx examples
- Fix pre-existing yookassa webhook test (mock AsyncSessionLocal)
2026-02-10 07:16:22 +03:00
Fringg a3903a252e refactor: remove smart auto-activation & activation prompt, fix production bugs
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)
2026-02-09 21:39:53 +03:00
Ali Morshedzadeh 5482e609f8 Add initial Persian locale support and language handling updates 2026-02-09 16:53:50 +03:30
gy9vin 551112d2d9 make fix 2026-02-01 14:41:45 +03:00
gy9vin 56a69fa1ba правки 2026-01-30 23:43:26 +03:00
gy9vin e0d667df28 fix реф системы! фишки конкурсной систем! проверка логов по рефералам и начисления бонусов 2026-01-30 09:35:17 +03:00
gy9vin 95b7152c05 касса и прочее 2026-01-27 23:47:39 +03:00
gy9vin 0d9498f169 Багфиксы и плюшки для Кассааи 2026-01-26 12:45:08 +03:00
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- 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
2026-01-24 17:45:27 +03:00
gy9vin d47a65c29f fix(referral): исправить потерю реферальных кодов при обязательной подписке на канал
Проблема: у некоторых пользователей реферальный код из deep link терялся,
  потому что pending_start_payload сохранялся только в FSM state, который
  мог быть недоступен (state=None) в edge cases.
                                                            Исправления:
  - Добавлен Redis fallback для хранения payload (TTL 1 час)
  - _capture_start_payload() теперь сохраняет в FSM state И в Redis
  - cmd_start() и required_sub_channel_check() проверяют Redis если FSM state
пуст
  - Добавлено логирование warning при state=None
  - Изменён уровень лога успешного сохранения с debug на info

  Изменённые файлы:
  - app/middlewares/channel_checker.py — Redis-функции и улучшенное логирование
  - app/handlers/start.py — Redis fallback в обработчиках

  Добавлены тесты:
  - tests/middlewares/test_channel_checker_payload.py (14 тестов)
2026-01-22 21:54:32 +03:00
gy9vin 6e1d671df2 feat(traffic): добавлен новый мониторинг трафика v2 с проверкой дельты и snapshot
Новый функционал:
- Быстрая проверка (TRAFFIC_FAST_CHECK_*) — отслеживает дельту трафика за интервал через snapshot
- Суточная проверка (TRAFFIC_DAILY_CHECK_*) — анализирует трафик за 24 часа через bandwidth API
- Фильтрация по нодам (TRAFFIC_MONIT
2026-01-10 00:47:23 +03:00
PEDZEO 6b69ec750e feat: add cabinet (personal account) backend API
- Add JWT authentication for cabinet users
- Add Telegram WebApp authentication
- Add subscription management endpoints
- Add balance and transactions endpoints
- Add referral system endpoints
- Add tickets support for cabinet
- Add webhooks and websocket for real-time updates
- Add email verification service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 23:20:20 +03:00
gy9vin bc19ec32bb fix Реализация корзины пользователя! запоминает настройки подписки после пополнения баланса 2025-12-30 22:35:54 +03:00
gy9vin 449f82d538 refactor(auto-purchase): перезагрузка пользователя после списания баланса для восстановления связей
- Добавлен get_user_by_id в импорты
- Перезагрузка user через get_user_by_id после subtract_user_balance
- Восстановление связи user_promo_groups, сбрасываемой после db.refresh() в payment-сервисах
- Добавлен мок get_user_by_id в тесте
2025-12-30 11:34:41 +03:00
gy9vin da46e39c61 refactor(modem): рефакторинг модуля управления модемом
Рефакторинг архитектуры управления модемом:

- Создан сервис app/services/modem_service.py:
  - ModemService с бизнес-логикой подключения/отключения
  - ModemError enum для типизации ошибок
  - ModemPriceInfo, ModemOperationResult dataclass'ы
  - Константы MODEM_WARNING_DAYS_* для уровней предупреждений
2025-12-25 18:44:27 +03:00
PEDZEO 931b282f5b Enhance button handling in MenuLayoutService to improve connect button identification and URL management 2025-12-20 03:27:31 +03:00
Egor 2761255e65 Ensure ReDoc loads reliably 2025-11-28 03:58:59 +03:00
Egor 7c9dfc352c Apply base promo discounts to all order components 2025-11-23 05:51:30 +03:00