1305 Commits

Author SHA1 Message Date
Fringg 17732a0370 style: apply ruff format to payment integrations
CI ruff format --check failed on 13 files. Applied ruff format to bring
them in line with project formatting (line wrapping, trailing commas,
quote consistency). No functional changes.
2026-05-04 20:47:48 +03:00
Fringg cd8be32671 fix: register all providers in payment search and verification services
The admin Payments page filter and pending payments tab were missing newer
providers (paypear, rollypay, aurapay, etoplatezhi, antilopay, jupiter, donut,
lava) because they were never registered in the search/verification registries.
Customer payments via these providers were invisible in admin filtering.

payment_search_service.py:
- Add 8 _search_<provider> functions matching the existing pattern
- Register them in _PROVIDER_SEARCH_MAP (now 22 methods total)
- Filter dropdown and stats.by_method now include all providers

payment_verification_service.py:
- Add 8 _is_<provider>_pending and _fetch_<provider>_payments functions
- Wire them into list_recent_pending_payments and get_payment_record
- Add display name and is_enabled dispatch branches for all 8
- Register paypear / rollypay / aurapay in SUPPORTED_MANUAL_CHECK_METHODS and
  SUPPORTED_AUTO_CHECK_METHODS (they have full API+DB sync via check_*)
- Wire them into run_manual_check
- etoplatezhi / antilopay / jupiter / donut / lava remain webhook-driven and
  appear in pending tab without manual-check button (no fake API sync)
2026-05-04 20:43:47 +03:00
Fringg afea054c8f feat: integrate Lava Business payment provider
- Lava Business via gate.lava.ru (HMAC-SHA256 signed JSON requests)
- Sub-methods: card and SBP via includeService filter
- Webhook signature verified from raw bytes with secret_key_2
- Sticky terminal-status guard (success after amount_mismatch escalates to ERROR)
- Order ID with full uuid4 hex (128-bit entropy)
- Cross-row contamination guard: order_id assertion on invoice_id fallback
- Warning when hook URL cannot be derived from webhook/web_api/cabinet bases
- Explicit failure when Lava response lacks payment_url (no orphan rows)
- Adds LAVA settings category, /lava-webhook endpoint, cabinet topup branch
- Mirrors existing Antilopay/Jupiter/Donut mixin pattern
2026-05-04 20:14:31 +03:00
Fringg f321ded9c0 feat: integrate Jupiter (FPGate P2P) and Donut payment providers
- Jupiter: SBP via app.juppiter.tech (FPGate P2P v2.1)
- Donut: CARD/SBP/SBP_QR via gw.donut.business (Donut P2P)
- HMAC-SHA256 signing verified against spec reference vectors
- Sticky terminal-status guard in callback (amount_mismatch/declined/cancelled
  cannot be re-credited by replayed webhook)
- Mirrors existing Antilopay/Etoplatezhi mixin pattern: service, mixin, CRUD,
  Alembic migration, handlers, keyboards, webhook, cabinet route, status mapping
- Adds JUPITER and DONUT settings categories with title/description/prefix
- Backfills missing ANTILOPAY and ETOPLATEZHI category metadata
2026-05-04 19:36:22 +03:00
Fringg 719664208e feat: integrate Antilopay payment provider (API v2)
- Add antilopay_service.py with SHA256WithRSA signing (pycryptodome),
  private key for requests, public key for callback verification
- Add payment mixin with create/callback/finalize/check_status flows,
  kopeks↔rubles conversion, 7 status mappings, prefer_methods support
- Add CRUD with FOR UPDATE locking, idempotency checks
- Add handlers with SBP/Card/SberPay sub-method selection
- Add Alembic migration for antilopay_payments table
- Add config (ANTILOPAY_ENABLED, SECRET_ID, PRIVATE_KEY, PUBLIC_KEY,
  PROJECT_ID, SBP/CARD/SBERPAY enabled/display names)
- Add webhook endpoint with X-Apay-Callback header signature verification
- Register in keyboard, router, utils, backup, method config
2026-05-04 07:44:17 +03:00
Fringg 6524f66da2 feat: integrate Etoplatezhi payment provider
- Add etoplatezhi_service.py with HMAC-SHA512+base64 signature algorithm,
  payment URL builder, and callback signature verification
- Add payment mixin with create/process/finalize flow, 12 status mappings
- Add CRUD operations with FOR UPDATE locking, idempotency checks
- Add Telegram handlers with SBP/Card sub-method selection
- Add Alembic migration for etoplatezhi_payments table
- Add config settings (ETOPLATEZHI_ENABLED, PROJECT_ID, SECRET_KEY,
  SBP_ENABLED, CARD_ENABLED, display names, min/max amounts)
- Add webhook endpoint with JSON-body signature verification
- Register in payment keyboard, router, utils, backup, method config
2026-05-04 07:17:54 +03:00
Fringg 17ac3da3c4 fix: AuraPay webhook signature + add SBP/Card payment method selection
- Fix webhook signature: str(None) produced "None" (4 chars) instead of
  "" like PHP implode() does, causing all webhooks with custom_fields=null
  to fail signature verification
- Add AURAPAY_SBP_ENABLED / AURAPAY_CARD_ENABLED env vars with display
  names, following Freekassa pattern for sub-method selection
- Add aurapay_sbp / aurapay_card buttons in payment keyboard
- Add start_aurapay_sbp_topup / start_aurapay_card_topup handlers
- Route dispatch handles aurapay / aurapay_sbp / aurapay_card
- payment_utils updated with SBP/Card availability checks
- service parameter ("sbp"/"card") now passed through to AuraPay API
2026-05-04 06:44:03 +03:00
Fringg ecde2fb8f0 feat: Apple IAP integration with security hardening 2026-05-04 05:49:38 +03:00
Fringg 99648a956e fix: persist campaign across bot→webapp registration handoff via Redis 2026-05-04 05:27:49 +03:00
Fringg 2478ff7c3d fix: expired_1d notification — use PricingEngine instead of hardcoded PRICE_30_DAYS 2026-05-04 05:14:47 +03:00
Fringg 443a826402 fix: PayPear webhook signature — strip signature field before hashing + IP fallback
The old code hashed the full raw body INCLUDING the 'signature' field
itself — a circular computation that can never match (you can't include
the signature in the data being signed).

Fix:
1. Strip 'signature' key from payload before HMAC-SHA256 computation
2. Try both sorted and unsorted keys (PayPear docs don't specify)
3. Fallback to IP allowlist check (158.160.85.101 per PayPear docs)
4. Pass client_ip from request headers to the verification function
2026-04-29 11:23:20 +03:00
Fringg 06db393488 feat: add bulk_actions, info_pages, news to PERMISSION_REGISTRY
- bulk_actions: read, execute (was using users:edit)
- info_pages: read, create, edit, delete (was using settings:read/edit)
- news: read, create, edit, delete (was missing from registry entirely)

Backend endpoints updated to use dedicated permissions instead of
piggybacking on users:edit / settings:read.
2026-04-29 11:14:24 +03:00
Fringg 62e7ecba01 fix: deadlock on user deletion — webhook handler never checked intentional mark
mark_intentional_panel_deletion was called before api.delete_user,
but _is_intentional_panel_deletion_event was never called in the
webhook handler — it was dead code. The user.deleted webhook processed
unconditionally, causing a deadlock between delete_user_account (Tx1
holding subscription row locks) and the webhook handler (Tx2 trying
to lock the same rows via decrement_subscription_server_counts).

Fix: check _is_intentional_panel_deletion_event at the top of
_handle_user_deleted — if True, log and return immediately without
touching the DB.
2026-04-29 10:28:57 +03:00
Fringg c905fa6000 fix: downgrade Pal24 API validation errors from error to debug 2026-04-29 10:25:08 +03:00
Fringg 2fbdbf5ab0 fix: autopay renewing trial subscriptions at classic-mode pricing
Three bugs caused trial subscriptions to be auto-renewed without a
tariff at arbitrary prices:

1. try_auto_extend_expired_after_topup: is_trial guard used truthiness
   check — NULL (legacy rows) passed as falsy. Changed to
   `is_trial is not False` (NULL-safe).

2. Multi-tariff branch: `not s.is_trial` treated NULL as not-trial.
   Changed to `s.is_trial is False`.

3. Telegram bot autopay toggle: no is_trial guard — users could enable
   autopay on trial subscriptions. Added trial check before enabling.
2026-04-29 08:16:39 +03:00
Fringg f37eb9a1bd fix: cabinet purchase fails after panel user deletion — stale UUID
Two bugs caused "RemnaWave UUID не найден" when a user repurchased
after their panel user was deleted (expired user cleanup):

1. Webhook handler only cleared subscription.remnawave_uuid in
   multi-tariff mode. In single-tariff mode the stale UUID remained,
   causing the cabinet to try update_remnawave_user on a deleted
   panel user instead of creating a new one.

2. Cabinet purchase-tariff used subscription.remnawave_uuid for the
   create/update decision. In single-tariff mode this was stale.
   Now mirrors the bot handler logic: checks user.remnawave_uuid
   in single-tariff mode (correctly cleared by webhook).
2026-04-29 08:04:23 +03:00
Fringg bada41ecd6 fix: remaining pricing-critical .days floor calculations → math.ceil
Same bug as device pricing: timedelta.days floors partial days.
Fixed 14 more pricing-critical locations across 7 files:

- traffic addon pricing (bot handler + cabinet + miniapp)
- country addon pricing (bot handler + miniapp)
- generic addon pricing helper (common.py)
- auto-purchase device recomputation
- subscription CRUD pricing helper

Display-only .days usages intentionally left as floor (correct for
showing "X days left" to users).
2026-04-29 07:27:55 +03:00
Fringg 47c7d45793 fix: traffic addon discount also bypassed tariff-promo-group check 2026-04-29 07:14:14 +03:00
Fringg 4ab5928b61 fix: promo group discount applied to restricted tariffs in autopay
The pricing engine applied promo group discounts unconditionally,
without checking if the tariff is available for the user's promo group.

In autopay: user with VIP group (60% discount, restricted to Premium
tariff) would get 60% off when auto-renewing a Basic tariff that their
group should not cover.

Fix: in _calculate_tariff_core, check tariff.is_available_for_promo_group
before applying group discounts. If tariff is not available for the
user's promo group, the discount is zeroed — subscription renews at
full price. Protects ALL pricing paths (autopay, recurrent, manual).
2026-04-29 07:09:55 +03:00
Fringg fb857d792b feat: per-category enable/disable for admin notifications
Add ADMIN_NOTIFICATIONS_{CATEGORY}_ENABLED settings (default True) for
all 10 notification categories: purchases, renewals, trials, balance,
addons, infrastructure, errors, promo, partners, tickets.

Setting ADMIN_NOTIFICATIONS_PROMO_ENABLED=false now completely suppresses
promo notifications (promocode activations, campaign visits, promo group
changes) instead of silently falling back to the general topic.

Also fix referral_contest_service direct bot.send_message bypass —
now respects ADMIN_NOTIFICATIONS_PROMO_ENABLED setting.
2026-04-29 06:58:57 +03:00
Fringg c619dbcae2 fix: handle A018 error code as user-not-found fallback to create_user 2026-04-29 06:48:30 +03:00
Fringg 134e7fb0e1 fix: false subscription expiry notifications — 4 bugs fixed
1. _check_expired_subscription_followups: added Subscription.status=EXPIRED
   filter (was matching ALL statuses including ACTIVE), User.status=ACTIVE
   filter, and 30-day lookback window to stop scanning ancient subscriptions

2. _get_expiring_paid_subscriptions: added User.status=ACTIVE filter to
   prevent sending "expiring" notifications to blocked/deleted users

3. Multi-tariff: before sending expired/followup notifications, check if
   user has another ACTIVE subscription with end_date > now — skip if they
   still have service through another tariff

4. Multi-tariff: same check for _check_expired_subscriptions — don't send
   "subscription expired" if user has another active sub
2026-04-29 05:47:56 +03:00
Fringg b9b695799c refactor: remove unused EXTERNAL_ADMIN_TOKEN functionality
- Delete app/services/external_admin_service.py entirely
- Remove EXTERNAL_ADMIN_TOKEN and EXTERNAL_ADMIN_TOKEN_BOT_ID from config
- Remove build_external_admin_token, get_external_admin_token, get_external_admin_bot_id methods
- Remove unused hashlib/hmac imports from config.py
- Remove from system_settings_service: READ_ONLY_KEYS, PLAIN_TEXT_KEYS,
  category title, category description, prefix mapping, documentation metadata
- Remove from bot_configuration.py category group
- Remove from main.py startup sequence (ensure_external_admin_token call)
- Remove from .env.example
- Remove from docs/project_structure_reference.md
2026-04-26 19:54:33 +03:00
Fringg 5cf19c76e6 fix: backup import crash + upload handler hardening
- Fix PaypearPayment → PayPearPayment (capital P) — import crash
- Fix AurapayPayment → AuraPayPayment (capital P) — import crash
- Update upload instruction message to mention .tar.gz format
- Add null guard on document.file_name before extension check
2026-04-26 19:39:09 +03:00
Fringg eafb243882 fix: backup completeness — add 15 missing tables, accept .tar.gz uploads
Tables added to backup AND clear lists:
- Payment providers: riopay, severpay, paypear, rollypay, overpay, aurapay, saved_payment_methods
- Content: email_templates, info_pages, news_articles, news_categories, news_tags
- Landing: landing_pages, guest_purchases
- Analytics: yandex_client_id_map

Also:
- Telegram backup upload handler now accepts .tar.gz format (was .json/.json.gz only)
- All 92 ORM models + 3 association tables now covered
2026-04-26 19:29:55 +03:00
Fringg 5ed9a0d4fb fix: use fresh DB session for deactivate after long unpin loop 2026-04-24 18:13:48 +03:00
Fringg ab4661b5c6 fix: unpin messages in Telegram BEFORE deactivating in DB
The "Unpin all" button called deactivate_active_pinned_message() first,
then looped over users to unpin. If Telegram API calls failed or timed
out, the message was already marked inactive in the DB with no way to
retry. Now: get active message → unpin from all chats → deactivate in DB.
2026-04-24 18:01:52 +03:00
Fringg 7d512d214a fix: integrate Yandex Metrika offline conv + S2S postback hooks
Restore integration hooks dropped in PR #2851 merge:
- PurchaseRequest accepts yandex_cid, referrer, subid from frontend
- Cache yandex_cid and subid in Redis at purchase creation (24h TTL)
- On fulfill_purchase: extract subid from cache, persist to DB
- Save Yandex CID from Redis to yandex_client_id_map
- Fire on_registration + S2S postback for new accounts
- Fire on_purchase + S2S postback for all paid purchases
- All hooks wrapped in try/except — failures never block delivery
2026-04-24 16:59:27 +03:00
Fringg 1522d35f2d fix: gift purchases no longer inflate promo group level
Two bugs caused max promo group assignment on gift send/activate:

1. Buyer: GIFT_PAYMENT was counted in get_user_total_spent_kopeks
   alongside SUBSCRIPTION_PAYMENT. Now only SUBSCRIPTION_PAYMENT
   counts as personal spending for promo group auto-assignment.

2. Recipient: fulfill_purchase and activate_purchase created a
   SUBSCRIPTION_PAYMENT transaction for the recipient with the full
   gift price. Now skipped for gift recipients — they didn't pay.
2026-04-24 16:43:32 +03:00
Fringg 2c3ffc8c8a feat: integrate Overpay payment provider (pay.overpay.io)
Full integration across bot, cabinet, admin panel and landing pages:

- Config: 16 OVERPAY_* settings (API URL, credentials, P12 cert path,
  project ID, currency, amount limits, webhook path, payment methods)
- Service: overpay_service.py with httpx mTLS (P12 cert) + Basic Auth,
  create_payment, get_payment, refund_payment methods
- Payment mixin: OverpayPaymentMixin with create, webhook processing,
  finalize (balance credit + notifications), status check
- Model: OverpayPayment table + OVERPAY enum value
- CRUD: 9 standard operations (create, get_by_*, for_update, link)
- Bot handler: start_overpay_topup + process_overpay_payment_amount
- Handler registration: route_payment_by_method + callback registration
- Webhook: POST /overpay-webhook with DB-based anti-spoofing validation
- Cabinet: balance topup + guest payment dispatch
- Keyboard: payment method button in bot
- Admin: settings category + test payment button
- Infrastructure: payment_method_config_service, system_settings_service,
  payment_verification_service, payment_search_service
- Migration 0064: create overpay_payments table

Overpay API specifics: amount as string "100.00" (not kopeks),
success status "charged", HPP redirect via resultUrl
2026-04-23 04:36:56 +03:00
Fringg 1068c1387a feat: Yandex Metrika offline conversions + S2S postbacks
- Add YandexClientIdMap model for user → yandex_cid mapping with
  upsert-safe CRUD (ON CONFLICT DO UPDATE)
- Add yandex_cid, subid, referrer columns to GuestPurchase
- Add yandex_offline_conv_service: Measurement Protocol integration
  with mc.yandex.ru/collect (registration, trial, purchase events),
  background task management, CID parsing from /start params
- Add s2s_postback_service: server-to-server affiliate postbacks
  with URL template placeholders and URL-safe encoding
- Add analytics offline conversion info to branding API (masked secret)
- Add POST /analytics/yandex-cid endpoint for cabinet CID capture
- Add 11 config settings (YANDEX_OFFLINE_CONV_*, S2S_POSTBACK_*)
- Add migration 0063 (yandex_client_id_map table + guest_purchases cols)
- Fix: mask measurement secret aggressively (show only last 4 chars)
- Fix: always replace {user_id} placeholder in S2S postback URLs
- Fix: use structlog kwargs instead of f-strings with LOG_PREFIX

Based on PR #2851 by @smediainfo — CI/CD workflow changes excluded
2026-04-22 05:57:46 +03:00
Fringg 9ca3320a02 fix: classic mode renewal resets device_limit to 1 via cart key mismatch
- Fix cart key mismatch: extend cart saved 'device_limit' but
  confirm_purchase read 'devices' key, falling back to DEFAULT=1.
  Now both keys are saved in both cart-save paths
- Fix confirm_purchase device resolution: use explicit is None checks
  instead of or-chain to avoid falsy-zero trap
- Fix return_to_saved_cart display: fall back to 'device_limit' and
  'traffic_limit_gb' keys when 'devices'/'traffic_gb' are absent
- Fix second cart-save path in _extend_existing_subscription with
  same dual-key pattern
- Fix RemnaWaveService import path in renewal service
- Add RESET_DEVICES_ON_RENEWAL setting: resets all connected devices
  (hwid) via RemnaWave API on each subscription renewal
2026-04-22 05:12:27 +03:00
Fringg da855a7c89 fix: tariff switch pricing showing free for upgrades, admin duplicate subscription guard
- pricing_engine: use shortest period for daily rate comparison instead
  of period closest to remaining_days — fixes incorrect free/zero cost
  for upgrades when tariffs have different period sets
- pricing_engine: remove unused target_days parameter from
  get_tariff_daily_rate_fraction
- admin_users: add duplicate subscription check before create,
  change_tariff and activate actions to prevent UniqueViolationError
  on uq_subscriptions_user_tariff_active constraint
- admin_users: add IntegrityError fallback on create as TOCTOU safety net
2026-04-22 04:25:42 +03:00
Fringg 7be404b918 fix: FSM state loss on balance topup, PayPear confirmation_url, hidden trial tariff in renewal
- balance/platega: re-set FSM state after min/max validation errors,
  set state before pending_amount path, use balance_topup callback for back button
- balance/main: set FSM state and payment_method in handle_topup_amount_callback
  for all providers before routing, use balance_topup callback in validation errors
- payment/paypear: fix confirmation_url key (was 'url'), add fallback,
  store charged amount with commission for correct webhook amount comparison
- tariff_purchase: redirect to active tariff list when current tariff is
  inactive (hidden trial after promo code activation)
- cabinet/renewal: check tariff.is_active in both GET and POST endpoints
  to prevent hidden trial tariff periods from appearing
2026-04-22 04:05:46 +03:00
Dmitry V. Lunin b71e58c8d2 fix: do not reset subscription_crypto_link when cryptoLink absent in webhook (#2891)
Co-authored-by: Dmitry Lunin <br@slack.ru>
2026-04-21 08:05:11 +03:00
Danila Yudin 905cea68b4 fix: grant all available squads for unrestricted trials (#2897) 2026-04-21 08:01:28 +03:00
c0mrade 3b03c253cc fix: устранить root cause MissingGreenlet в автоплатежах через refetch по id
Трейс показал: subscription.user падает на lazy-load → pool._checkout →
do_ping → await_ → MissingGreenlet. SQLAlchemy 2.0 async session не
поддерживает sync-lazy-load для relationships. Причина рассинхрона:
lock_user_for_pricing делает populate_existing=True + selectinload(
User.subscriptions).selectinload(Subscription.tariff), что разгружает
Subscription.user backref для сестринских подписок того же user.
Последующее обращение sub.user у другой подписки падает.

Фикс: захватываем (sub_id, user_id) пары ДО цикла, каждую итерацию
делаем fresh refetch через async select с eager load user+tariff+
promo_group. Никаких lazy access в горячем пути. В except используем
локально захваченные id вместо getattr(subscription, ...), чтобы
логирование не падало каскадом на expired объекте.
2026-04-19 12:08:34 +03:00
c0mrade db79cc9eb0 fix: устранить MissingGreenlet в автоплатежах и починить traceback в логах
- subtract_user_balance: пишем promo_offer_log в отдельной сессии вместо rollback после commit, который экспайрил объекты основной сессии и ломал последующие обращения к subscription/user attrs
- monitoring_service._process_autopayments: перезагружаем subscription с eager-load user/tariff после списания, оборачиваем каждую итерацию в try/except + rollback, чтобы одна ошибка не валила весь батч
- logging_config: новый processor _auto_capture_exc_info автоматически подтягивает traceback из sys.exc_info() или error-kwarg → полный traceback в файле, консоли и Telegram без exc_info=True на каждом вызове
- logging_handler: дублирующая логика захвата exc_info в TelegramNotifierProcessor как резерв
2026-04-19 11:50:40 +03:00
Fringg ecc4a6147d fix: rate-limit daily subscription insufficient balance notifications to 6 hours
Users with daily subscriptions and low balance were getting
"Подписка приостановлена" notification every 30 minutes (on each
charge cycle). Now rate-limited via Redis cache to max 1 notification
per 6 hours per subscription.
2026-04-18 00:59:09 +03:00
Fringg 16bc1d4198 fix: align campaign top registrations revenue with period comparison
Top registrations list was summing DEPOSIT + SUBSCRIPTION_PAYMENT
transactions (total user spending), while period comparison revenue
only counted DEPOSIT with real payment methods (actual money paid in).

Now both use the same calculation: only DEPOSIT transactions with
real payment methods. This fixes the discrepancy where a user showed
500₽ in the list but total revenue was 250₽.
2026-04-18 00:50:23 +03:00
Fringg 0f814be1b7 fix: add missing RollyPay CRUD wrappers and guest payment flow
RollyPay was missing:
- 7 CRUD wrapper functions in payment_service.py (create, get, update, link)
- Guest payment block in create_guest_payment()

Both were present for PayPear and AuraPay but omitted for RollyPay.
2026-04-18 00:23:38 +03:00
Fringg 97179360c0 feat: integrate AuraPay payment provider
Full integration following PayPear/RollyPay patterns:
- API client with X-ApiKey + X-ShopId auth, HMAC-SHA256 webhook verification
  (sorted keys, concatenated values, secret key #2)
- AuraPayPaymentMixin with create, webhook, finalize (all side effects), status check
- CRUD, model, migration (0060), PaymentMethod.AURAPAY enum
- Webhook endpoint, cabinet topup, bot handler with sub-options (card, sbp)
- Admin panel: AURAPAY category in bot_configuration + system_settings_service
- Config: AURAPAY_ENABLED, AURAPAY_API_KEY, AURAPAY_SHOP_ID, AURAPAY_SECRET_KEY
2026-04-18 00:02:49 +03:00
Fringg 2aa5927433 fix: register PayPear and RollyPay in admin panel settings
- bot_configuration.py: added PAYPEAR/ROLLYPAY to payment categories
  and test payment buttons
- system_settings_service.py: added category titles, descriptions,
  and prefix mappings for PAYPEAR_* and ROLLYPAY_* settings
2026-04-17 23:44:35 +03:00
Fringg a59858227f fix: support payment_method selection for RollyPay (sbp/card/crypto)
- Mixin accepts payment_method_type parameter (None = show all on form)
- API service sends payment_method only when specified
- Cabinet route passes payment_option to mixin
- Config service has sub_options: sbp, card, crypto
- Keyboard label no longer hardcodes "СБП"
2026-04-16 06:03:33 +03:00
Fringg ccc2f4efec feat: integrate RollyPay payment provider (SBP via USDT)
Full integration following PayPear/SeverPay patterns:
- API client with X-API-Key + X-Nonce auth, HMAC-SHA256 webhook verification
- RollyPayPaymentMixin with create, webhook, finalize (all side effects), status check
- CRUD, model, migration (0059), PaymentMethod.ROLLYPAY enum
- Webhook endpoint, cabinet topup, bot handler
- Statuses: created, processing, paid, expired, canceled, chargeback
- Config: ROLLYPAY_ENABLED, ROLLYPAY_API_KEY, ROLLYPAY_SIGNING_SECRET, etc.
2026-04-16 05:52:25 +03:00
Fringg a18f6caa9b feat: integrate PayPear payment provider
Full integration following SeverPay patterns:
- API client with Basic Auth, idempotency, HMAC webhook verification
- PayPearPaymentMixin with create, webhook, finalize (all side effects), status check
- CRUD, model, migration (0058), PaymentMethod.PAYPEAR enum
- Webhook endpoint, cabinet topup, bot handler, payment verification
- Sub-options: bank_card, sbp, sberpay, tpay
- Config: PAYPEAR_ENABLED, PAYPEAR_SHOP_ID, PAYPEAR_SECRET_KEY, etc.
2026-04-16 05:36:48 +03:00
Fringg 2d5afe5d75 fix: low balance alerts disabled by default, add quiet hours, expiry filter, top-up button
- Default balance_low_enabled changed to False (opt-in via cabinet)
- Quiet hours: alerts skipped between 22:00-09:00 UTC
- Only alerts when subscription expires within LOW_BALANCE_ALERT_EXPIRY_DAYS (default 3)
- Added inline "Top up" button linking to cabinet miniapp
- Synced defaults across notification_prefs, cabinet notifications route
2026-04-16 04:42:36 +03:00
Fringg 4fe67a9c74 fix: ruff format traffic.py and monitoring_service.py 2026-04-15 13:57:15 +03:00
Fringg 30a1a31978 fix: apply create-vs-update fix to cabinet traffic/devices and monitoring
Same multi-tariff create-vs-update bug in 5 more locations:
- cabinet/subscription_modules/traffic.py (2 instances)
- cabinet/subscription_modules/devices.py (2 instances)
- services/monitoring_service.py (1 instance)

All now use _should_create pattern based on subscription.remnawave_uuid
in multi-tariff mode instead of falling back to user.remnawave_uuid.
2026-04-15 13:28:06 +03:00
Fringg cea7260a85 fix: apply same create-vs-update fix to renewal and purchase flows
Same bug as the tariff purchase fix: in multi-tariff mode, new
subscriptions without remnawave_uuid incorrectly fell back to
user.remnawave_uuid and called update instead of create.

Fixed in subscription_renewal_service.py and purchase.py to use
the same _should_create pattern based on mode.
2026-04-15 13:17:35 +03:00