Add admin notifications to miniapp subscription flows

This commit is contained in:
Egor
2025-10-11 08:39:19 +03:00
parent e78d75a440
commit 60b85911e1

View File

@@ -6,7 +6,7 @@ import math
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP, ROUND_FLOOR
from datetime import datetime, timedelta, timezone
from uuid import uuid4
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union
from aiogram import Bot
from fastapi import APIRouter, Depends, HTTPException, status
@@ -54,6 +54,7 @@ from app.database.models import (
PaymentMethod,
User,
)
from app.services.admin_notification_service import AdminNotificationService
from app.services.faq_service import FaqService
from app.services.privacy_policy_service import PrivacyPolicyService
from app.services.public_offer_service import PublicOfferService
@@ -169,6 +170,27 @@ router = APIRouter()
promo_code_service = PromoCodeService()
async def _with_admin_notification_service(
handler: Callable[[AdminNotificationService], Awaitable[Any]],
) -> None:
if not getattr(settings, "ADMIN_NOTIFICATIONS_ENABLED", False):
return
if not settings.BOT_TOKEN:
logger.debug("Skipping admin notification: bot token is not configured")
return
bot: Bot | None = None
try:
bot = Bot(token=settings.BOT_TOKEN)
service = AdminNotificationService(bot)
await handler(service)
except Exception as error: # pragma: no cover - defensive logging
logger.error("Failed to send admin notification from miniapp: %s", error)
finally:
if bot:
await bot.session.close()
_CRYPTOBOT_MIN_USD = 1.0
_CRYPTOBOT_MAX_USD = 1000.0
_CRYPTOBOT_FALLBACK_RATE = 95.0
@@ -2784,6 +2806,10 @@ async def activate_subscription_trial_endpoint(
else:
message = "Trial activated successfully. Enjoy!"
await _with_admin_notification_service(
lambda service: service.send_trial_activation_notification(db, user, subscription)
)
return MiniAppSubscriptionTrialResponse(
message=message,
subscription_id=getattr(subscription, "id", None),
@@ -3946,6 +3972,7 @@ async def submit_subscription_renewal_endpoint(
consume_promo_offer = bool(pricing.get("promo_discount_value"))
description = f"Продление подписки на {period_days} дней"
old_end_date = subscription.end_date
if final_total > 0 or consume_promo_offer:
success = await subtract_user_balance(
@@ -4001,8 +4028,9 @@ async def submit_subscription_renewal_endpoint(
error,
)
transaction: Optional[Transaction] = None
try:
await create_transaction(
transaction = await create_transaction(
db=db,
user_id=user.id,
type=TransactionType.SUBSCRIPTION_PAYMENT,
@@ -4019,6 +4047,20 @@ async def submit_subscription_renewal_endpoint(
await db.refresh(user)
await db.refresh(subscription)
if transaction and old_end_date and subscription.end_date:
await _with_admin_notification_service(
lambda service: service.send_subscription_extension_notification(
db,
user,
subscription,
transaction,
period_days,
old_end_date,
new_end_date=subscription.end_date,
balance_after=user.balance_kopeks,
)
)
language_code = _normalize_language_code(user)
amount_label = settings.format_price(final_total)
date_label = (
@@ -4164,6 +4206,29 @@ async def subscription_purchase_endpoint(
await db.refresh(user)
subscription = result.get("subscription")
transaction = result.get("transaction")
was_trial_conversion = bool(result.get("was_trial_conversion"))
period_days = getattr(getattr(pricing, "selection", None), "period", None)
period_days = getattr(period_days, "days", None) if period_days else None
if subscription is not None:
try:
await db.refresh(subscription)
except Exception: # pragma: no cover - defensive refresh safeguard
pass
if subscription and transaction and period_days:
await _with_admin_notification_service(
lambda service: service.send_subscription_purchase_notification(
db,
user,
subscription,
transaction,
period_days,
was_trial_conversion=was_trial_conversion,
)
)
balance_label = settings.format_price(getattr(user, "balance_kopeks", 0))
return MiniAppSubscriptionPurchaseResponse(
@@ -4202,6 +4267,7 @@ async def update_subscription_servers_endpoint(
user = await _authorize_miniapp_user(payload.init_data, db)
subscription = _ensure_paid_subscription(user)
_validate_subscription_id(payload.subscription_id, subscription)
old_servers = list(getattr(subscription, "connected_squads", []) or [])
raw_selection: List[str] = []
for collection in (
@@ -4373,10 +4439,26 @@ async def update_subscription_servers_endpoint(
subscription.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(subscription)
try:
await db.refresh(user)
except Exception: # pragma: no cover - defensive refresh safeguard
pass
service = SubscriptionService()
await service.update_remnawave_user(db, subscription)
await _with_admin_notification_service(
lambda service: service.send_subscription_update_notification(
db,
user,
subscription,
"servers",
old_servers,
subscription.connected_squads or [],
price_paid=max(total_cost, 0),
)
)
return MiniAppSubscriptionUpdateResponse(success=True)
@@ -4391,6 +4473,7 @@ async def update_subscription_traffic_endpoint(
user = await _authorize_miniapp_user(payload.init_data, db)
subscription = _ensure_paid_subscription(user)
_validate_subscription_id(payload.subscription_id, subscription)
old_traffic = subscription.traffic_limit_gb
raw_value = (
payload.traffic
@@ -4520,10 +4603,26 @@ async def update_subscription_traffic_endpoint(
subscription.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(subscription)
try:
await db.refresh(user)
except Exception: # pragma: no cover - defensive refresh safeguard
pass
service = SubscriptionService()
await service.update_remnawave_user(db, subscription)
await _with_admin_notification_service(
lambda service: service.send_subscription_update_notification(
db,
user,
subscription,
"traffic",
old_traffic,
subscription.traffic_limit_gb,
price_paid=max(total_price_difference, 0),
)
)
return MiniAppSubscriptionUpdateResponse(success=True)
@@ -4573,6 +4672,7 @@ async def update_subscription_devices_endpoint(
)
current_devices = int(subscription.device_limit or settings.DEFAULT_DEVICE_LIMIT or 1)
old_devices = current_devices
if new_devices == current_devices:
return MiniAppSubscriptionUpdateResponse(success=True, message="No changes")
@@ -4649,9 +4749,25 @@ async def update_subscription_devices_endpoint(
subscription.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(subscription)
try:
await db.refresh(user)
except Exception: # pragma: no cover - defensive refresh safeguard
pass
service = SubscriptionService()
await service.update_remnawave_user(db, subscription)
await _with_admin_notification_service(
lambda service: service.send_subscription_update_notification(
db,
user,
subscription,
"devices",
old_devices,
subscription.device_limit,
price_paid=max(price_to_charge, 0),
)
)
return MiniAppSubscriptionUpdateResponse(success=True)