diff --git a/.gitignore b/.gitignore
index 9bb24585..0a73af74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ docker-compose.override.yml
!requirements.txt
!docs/
!docs/**
+!QWEN.md
# Разрешаем папку app/ и все её содержимое рекурсивно
!app/
diff --git a/QWEN.md b/QWEN.md
new file mode 100644
index 00000000..70d3f4fa
--- /dev/null
+++ b/QWEN.md
@@ -0,0 +1,255 @@
+# Qwen Code Context - Remnawave Bedolaga Telegram Bot
+
+## Project Overview
+
+The Remnawave Bedolaga Telegram Bot is a modern VPN subscription management system built with Python, aiogram, and FastAPI. It provides a comprehensive solution for managing VPN subscriptions through the Remnawave API with extensive features including multi-channel payments, user management, admin functionality, and automated systems.
+
+## Architecture and Technology Stack
+
+### Core Technologies
+- **Python 3.13+** with AsyncIO for maximum performance
+- **aiogram 3** for Telegram Bot API interactions
+- **FastAPI** for REST API services
+- **PostgreSQL 15+** for reliable data storage (with SQLite fallback)
+- **Redis** for caching and session management
+- **SQLAlchemy ORM** for safe database operations
+- **Pydantic v2** for data validation
+
+### Key Components
+- **Payment Service**: Supports multiple payment methods (Telegram Stars, YooKassa, Tribute, CryptoBot, Heleket, MulenPay, Pal24, WATA)
+- **Subscription Service**: Manages VPN subscriptions and syncs with Remnawave panel
+- **Monitoring Service**: Regular API checks and automatic maintenance mode
+- **Backup Service**: Smart automatic backups with scheduling
+- **Web API**: Full-featured admin API with endpoints for all aspects
+
+## Project Structure
+
+```
+remnawave-bedolaga-telegram-bot/
+├── app/ # Main application code
+│ ├── bot.py # Bot initialization and setup
+│ ├── config.py # Configuration and settings
+│ ├── database/ # Database models and CRUD operations
+│ ├── external/ # External webhook servers
+│ ├── handlers/ # Bot command and callback handlers
+│ ├── keyboards/ # Inline and reply keyboards
+│ ├── localization/ # Translation and locale management
+│ ├── middlewares/ # Bot middlewares
+│ ├── services/ # Business logic services
+│ ├── utils/ # Utility functions
+│ ├── webapi/ # Web API endpoints
+│ └── states.py # FSM states
+├── assets/ # Project assets
+├── data/ # Runtime data storage
+├── docs/ # Documentation
+├── logs/ # Log files
+├── .env.example # Environment configuration template
+├── docker-compose.yml # Docker services configuration
+├── Dockerfile # Docker image definition
+├── install_bot.sh # Installation and management script
+├── main.py # Main application entry point
+├── README.md # Project documentation
+└── requirements.txt # Python dependencies
+```
+
+## Key Features
+
+### For Users
+- **Multi-channel payments**: Telegram Stars, Tribute, CryptoBot, YooKassa, MulenPay, PayPalych, WATA
+- **Smart subscription purchase**: Flexible periods with discounts, traffic selection, server selection
+- **Trial subscriptions**: Configurable trial with welcome sequence
+- **Auto-pay**: Configurable auto-renewal with day selection
+- **Referral system**: Commission-based referral program
+- **MiniApp dashboard**: Full personal account inside Telegram
+- **Multiple languages**: RU/EN with dynamic localization
+
+### For Administrators
+- **Analytics**: Detailed dashboards for users, subscriptions, and payments
+- **User management**: Search, filters, detailed user cards
+- **Promo system**: Codes, groups, personal offers
+- **Ticket system**: Support with priorities and SLA
+- **Backup and restore**: Smart automatic backups with scheduling
+- **Web API**: Full integration capabilities
+- **Monitoring**: Server monitoring and health checks
+
+## Configuration
+
+### Environment Variables
+The bot is configured through `.env` file with comprehensive settings including:
+- Bot token and admin IDs
+- Remnawave panel integration (URL, API key, secret key)
+- Database configuration (PostgreSQL or SQLite)
+- Payment system configurations
+- Pricing and subscription settings
+- Feature flags and UI options
+
+### Key Configuration Options
+- `BOT_TOKEN`: Telegram bot token from BotFather
+- `REMNAWAVE_API_URL`: Your Remnawave panel URL
+- `REMNAWAVE_API_KEY`: API key for your panel
+- `REMNAWAVE_SECRET_KEY`: Panel protection key (for eGames panels)
+- `ADMIN_IDS`: Telegram IDs of administrators
+- Payment system specific configurations
+- Database and Redis settings
+
+## Building and Running
+
+### Docker Setup (Recommended)
+The project uses Docker Compose for easy deployment:
+
+```bash
+# Clone repository
+git clone https://github.com/Fr1ngg/remnawave-bedolaga-telegram-bot.git
+cd remnawave-bedolaga-telegram-bot
+
+# Configure environment
+cp .env.example .env
+# Edit .env with your settings
+
+# Create necessary directories
+mkdir -p ./logs ./data ./data/backups ./data/referral_qr
+chmod -R 755 ./logs ./data
+sudo chown -R 1000:1000 ./logs ./data
+
+# Start all services
+docker compose up -d
+
+# Check status
+docker compose logs
+```
+
+### Management Script
+The `install_bot.sh` script provides an interactive management menu:
+- Container status monitoring and resource monitoring
+- Service management (start/stop/rebuild)
+- Log viewing and search
+- Git update with automatic backup
+- Backup and restore functionality
+- Environment configuration
+
+## Key Services and Components
+
+### Payment Service
+Handles all payment system integrations with support for:
+- Telegram Stars (built-in)
+- YooKassa (SBP and cards)
+- Tribute (cryptocurrency)
+- CryptoBot (USDT, TON, BTC, ETH and others)
+- Heleket (cryptocurrency with markup)
+- MulenPay (SBP)
+- PayPalych/Pal24 (SBP and cards)
+- WATA (bank card payments)
+
+### Subscription Service
+Manages VPN subscriptions and integrates with Remnawave API:
+- Creates and updates users in the Remnawave panel
+- Handles subscription expiration and traffic limits
+- Supports different traffic reset strategies
+- Syncs subscription usage and status
+
+### Web API and MiniApp
+- FastAPI Web API with endpoints for all aspects
+- MiniApp personal account inside Telegram
+- Integrated payments in MiniApp
+- App Config for centralized link distribution
+
+### Database and Storage
+- PostgreSQL: Primary database for user data and subscriptions
+- Redis: Fast caching and session storage for cart functionality
+- Migration system: Automatic schema updates
+- Backup system: Automatic database backups
+
+## Development Conventions
+
+### Code Structure
+- **Modular architecture**: Subscription and payment modules are separate
+- **AsyncIO**: All operations are asynchronous for maximum performance
+- **Type hints**: Full type annotation coverage
+- **Dependency injection**: Services are properly injected and managed
+- **Pydantic models**: Data validation and configuration management
+
+### Error Handling
+- **Graceful shutdown**: Proper cleanup on termination signals
+- **Service restart**: Automatic restart of failed services
+- **Comprehensive logging**: Detailed logs for debugging and monitoring
+- **Middleware protection**: Global error handling for callback queries
+
+### Testing and Quality
+- The codebase includes various test files and debugging tools
+- Uses pytest for testing (evidenced by .pytest_cache directory)
+- Type checking supported through development practices
+
+## Security Features
+
+- **API key validation**: All external API calls validated
+- **Rate limiting**: Protection against spam
+- **Data encryption**: Sensitive data encrypted
+- **Session management**: Automatic session management
+- **Suspicious activity monitoring**: Activity monitoring and alerts
+- **Username blocking**: Automatic blocking of suspicious names
+
+## Health Checks and Monitoring
+
+- Main: `http://localhost:8081/health`
+- YooKassa: `http://localhost:8082/health`
+- Pal24: `http://localhost:8084/health`
+
+## Troubleshooting Commands
+
+```bash
+# View logs in real-time
+docker compose logs -f bot
+
+# Status of all containers
+docker compose ps
+
+# Restart only the bot
+docker compose restart bot
+
+# Check database
+docker compose exec postgres pg_isready -U remnawave_user
+
+# Connect to database
+docker compose exec postgres psql -U remnawave_user -d remnawave_bot
+
+# Check Redis
+docker compose exec redis redis-cli ping
+```
+
+## Main Entry Point
+
+The application starts in `main.py` which:
+- Initializes the database and runs migrations
+- Sets up the bot with aiogram
+- Starts webhook servers for various payment systems
+- Launches background services (monitoring, maintenance, version checking)
+- Begins polling for Telegram updates
+- Handles graceful shutdown
+
+## Localization
+
+The bot supports multiple languages (RU/EN) with:
+- Dynamic language selection
+- Comprehensive translation files
+- Localized user interface elements
+- Proper formatting for prices and dates
+
+## Payment Integration
+
+The bot supports multiple payment systems with:
+- Webhook handling for payment confirmations
+- Automatic balance updates
+- Transaction history tracking
+- Refund and cancellation support
+- Payment method-specific configurations
+
+## Administrative Features
+
+Administrative functionality includes:
+- User management (search, edit, ban)
+- Subscription management (create, modify, extend)
+- Promotional tools (codes, groups, offers)
+- Payment configuration
+- System monitoring and health checks
+- Backup and restore capabilities
+- Analytics and reporting
\ No newline at end of file
diff --git a/app/handlers/balance/main.py b/app/handlers/balance/main.py
index 42982fbc..bf948858 100644
--- a/app/handlers/balance/main.py
+++ b/app/handlers/balance/main.py
@@ -395,6 +395,8 @@ async def handle_successful_topup_with_cart(
success_text = (
f"✅ Баланс пополнен на {texts.format_price(amount_kopeks)}!\n\n"
f"💰 Текущий баланс: {texts.format_price(user.balance_kopeks)}\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n"
f"🛒 У вас есть сохраненная корзина подписки\n"
f"Стоимость: {texts.format_price(total_price)}\n\n"
f"Хотите продолжить оформление?"
diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py
index 3186ca6c..4eebcb97 100644
--- a/app/handlers/stars_payments.py
+++ b/app/handlers/stars_payments.py
@@ -130,6 +130,8 @@ async def handle_successful_payment(
"⭐ Потрачено звезд: {stars_spent}\n"
"💰 Зачислено на баланс: {amount} ₽\n"
"🆔 ID транзакции: {transaction_id}...\n\n"
+ "⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ "Обязательно активируйте подписку отдельно!\n\n"
"Спасибо за пополнение! 🚀",
).format(
stars_spent=payment.total_amount,
diff --git a/app/handlers/webhooks.py b/app/handlers/webhooks.py
index 50a279ef..3f534fc3 100644
--- a/app/handlers/webhooks.py
+++ b/app/handlers/webhooks.py
@@ -114,7 +114,9 @@ async def handle_successful_payment(message: types.Message):
)
await message.answer(
- f"✅ Баланс успешно пополнен на {settings.format_price(amount_kopeks)}!"
+ f"✅ Баланс успешно пополнен на {settings.format_price(amount_kopeks)}!\n\n"
+ "⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ "Обязательно активируйте подписку отдельно!"
)
logger.info(f"✅ Обработан Stars платеж: {payment.telegram_payment_charge_id}")
diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json
index bdc2258e..83f4c718 100644
--- a/app/localization/locales/en.json
+++ b/app/localization/locales/en.json
@@ -1242,7 +1242,7 @@
"SKIP_BUTTON": "Skip ➡️",
"STARS_PAYMENT_ENROLLMENT_ERROR": "❌ Failed to credit funds. Please contact support; the payment will be verified manually.",
"STARS_PAYMENT_PROCESSING_ERROR": "❌ Technical error processing the payment. Please contact support for assistance.",
- "STARS_PAYMENT_SUCCESS": "🎉 Payment processed successfully!\n\n⭐ Stars spent: {stars_spent}\n💰 Added to balance: {amount} ₽\n🆔 Transaction ID: {transaction_id}...\n\nThank you for topping up! 🚀",
+ "STARS_PAYMENT_SUCCESS": "🎉 Payment processed successfully!\n\n⭐ Stars spent: {stars_spent}\n💰 Added to balance: {amount} ₽\n🆔 Transaction ID: {transaction_id}...\n\n⚠️ Important: Balance top-up does not automatically activate your subscription. Please activate your subscription separately!\n\nThank you for topping up! 🚀",
"STARS_PAYMENT_USER_NOT_FOUND": "❌ Error: user not found. Please contact support.",
"STARS_PRECHECK_INVALID_PAYLOAD": "Payment validation error. Please try again.",
"STARS_PRECHECK_TECHNICAL_ERROR": "Technical error. Please try again later.",
diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json
index 140353b3..ab671910 100644
--- a/app/localization/locales/ru.json
+++ b/app/localization/locales/ru.json
@@ -1262,7 +1262,7 @@
"SKIP_BUTTON": "⏭️ Пропустить",
"STARS_PAYMENT_ENROLLMENT_ERROR": "❌ Произошла ошибка при зачислении средств. Обратитесь в поддержку, платеж будет проверен вручную.",
"STARS_PAYMENT_PROCESSING_ERROR": "❌ Техническая ошибка при обработке платежа. Обратитесь в поддержку для решения проблемы.",
- "STARS_PAYMENT_SUCCESS": "🎉 Платеж успешно обработан!\n\n⭐ Потрачено звезд: {stars_spent}\n💰 Зачислено на баланс: {amount} ₽\n🆔 ID транзакции: {transaction_id}...\n\nСпасибо за пополнение! 🚀",
+ "STARS_PAYMENT_SUCCESS": "🎉 Платеж успешно обработан!\n\n⭐ Потрачено звезд: {stars_spent}\n💰 Зачислено на баланс: {amount} ₽\n🆔 ID транзакции: {transaction_id}...\n\n⚠️ Важно: Пополнение баланса не активирует подписку автоматически. Обязательно активируйте подписку отдельно!\n\nСпасибо за пополнение! 🚀",
"STARS_PAYMENT_USER_NOT_FOUND": "❌ Ошибка: пользователь не найден. Обратитесь в поддержку.",
"STARS_PRECHECK_INVALID_PAYLOAD": "Ошибка валидации платежа. Попробуйте еще раз.",
"STARS_PRECHECK_TECHNICAL_ERROR": "Техническая ошибка. Попробуйте позже.",
diff --git a/app/services/payment/common.py b/app/services/payment/common.py
index 3fe7f99c..2e7ececc 100644
--- a/app/services/payment/common.py
+++ b/app/services/payment/common.py
@@ -42,6 +42,7 @@ class PaymentCommonMixin:
user and user.subscription and not user.subscription.is_trial and user.subscription.is_active
)
+ # Создаем основную кнопку: если есть активная подписка - продлить, иначе купить
first_button = build_miniapp_or_callback_button(
text=(
texts.MENU_EXTEND_SUBSCRIPTION
@@ -53,7 +54,16 @@ class PaymentCommonMixin:
),
)
- keyboard_rows: list[list[InlineKeyboardButton]] = [[first_button]]
+ # Кнопка активации подписки (всегда отображается)
+ activate_subscription_button = build_miniapp_or_callback_button(
+ text="🚀 Активировать подписку",
+ callback_data="menu_buy" # Используем ту же callback_data что и "Купить подписку"
+ )
+
+ keyboard_rows: list[list[InlineKeyboardButton]] = [
+ [first_button],
+ [activate_subscription_button]
+ ]
# Если для пользователя есть незавершённый checkout, предлагаем вернуться к нему.
if user:
@@ -128,7 +138,9 @@ class PaymentCommonMixin:
"✅ Платеж успешно завершен!\n\n"
f"💰 Сумма: {settings.format_price(amount_kopeks)}\n"
f"💳 Способ: {payment_method}\n\n"
- "Средства зачислены на ваш баланс!"
+ "Средства зачислены на ваш баланс!\n\n"
+ "⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ "Обязательно активируйте подписку отдельно!"
)
await self.bot.send_message(
diff --git a/app/services/payment/cryptobot.py b/app/services/payment/cryptobot.py
index e4d3d5e5..88d9b868 100644
--- a/app/services/payment/cryptobot.py
+++ b/app/services/payment/cryptobot.py
@@ -346,7 +346,9 @@ class CryptoBotPaymentMixin:
await self.bot.send_message(
chat_id=user.telegram_id,
- text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}",
+ text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}",
reply_markup=keyboard
)
logger.info(
diff --git a/app/services/payment/mulenpay.py b/app/services/payment/mulenpay.py
index 69f672a9..0bb8317c 100644
--- a/app/services/payment/mulenpay.py
+++ b/app/services/payment/mulenpay.py
@@ -389,7 +389,9 @@ class MulenPayPaymentMixin:
await self.bot.send_message(
chat_id=user.telegram_id,
- text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}",
+ text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}",
reply_markup=keyboard
)
logger.info(
diff --git a/app/services/payment/pal24.py b/app/services/payment/pal24.py
index 501dbe2c..6c257403 100644
--- a/app/services/payment/pal24.py
+++ b/app/services/payment/pal24.py
@@ -489,7 +489,9 @@ class Pal24PaymentMixin:
chat_id=user.telegram_id,
text=(
"✅ Баланс пополнен на "
- f"{settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}"
+ f"{settings.format_price(payment.amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}"
),
reply_markup=keyboard,
)
diff --git a/app/services/payment/platega.py b/app/services/payment/platega.py
index bfe331cb..ac42eb0d 100644
--- a/app/services/payment/platega.py
+++ b/app/services/payment/platega.py
@@ -498,6 +498,8 @@ class PlategaPaymentMixin:
chat_id=user.telegram_id,
text=(
f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n"
f"{cart_message}"
),
reply_markup=keyboard,
diff --git a/app/services/payment/stars.py b/app/services/payment/stars.py
index 9d7e4e33..c6b0fa27 100644
--- a/app/services/payment/stars.py
+++ b/app/services/payment/stars.py
@@ -604,7 +604,9 @@ class TelegramStarsMixin:
await self.bot.send_message(
chat_id=user.telegram_id,
- text=f"✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n{cart_message}",
+ text=f"✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}",
reply_markup=keyboard,
)
logger.info(
diff --git a/app/services/payment/wata.py b/app/services/payment/wata.py
index 5bff005b..631cd690 100644
--- a/app/services/payment/wata.py
+++ b/app/services/payment/wata.py
@@ -514,6 +514,8 @@ class WataPaymentMixin:
f"💰 Сумма: {settings.format_price(payment.amount_kopeks)}\n"
"🦊 Способ: WATA\n"
f"🆔 Транзакция: {transaction.id}\n\n"
+ "⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ "Обязательно активируйте подписку отдельно!\n\n"
"Баланс пополнен автоматически!"
),
parse_mode="HTML",
diff --git a/app/services/payment/yookassa.py b/app/services/payment/yookassa.py
index 171a6bf0..be83e8e5 100644
--- a/app/services/payment/yookassa.py
+++ b/app/services/payment/yookassa.py
@@ -554,7 +554,9 @@ class YooKassaPaymentMixin:
await self.bot.send_message(
chat_id=user.telegram_id,
- text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}",
+ text=f"✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}",
reply_markup=keyboard,
)
logger.info(
diff --git a/app/services/tribute_service.py b/app/services/tribute_service.py
index c21906d7..eb16a534 100644
--- a/app/services/tribute_service.py
+++ b/app/services/tribute_service.py
@@ -257,6 +257,8 @@ class TributeService:
f"💰 Сумма: {int(amount_rubles)} ₽\n"
f"💳 Способ оплаты: Tribute\n"
f"🎉 Средства зачислены на баланс!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n"
f"Спасибо за оплату! 🙏"
)
@@ -318,7 +320,9 @@ class TributeService:
await self.bot.send_message(
chat_id=user_id,
- text=f"✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n{cart_message}",
+ text=f"✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n"
+ f"⚠️ Важно: Пополнение баланса не активирует подписку автоматически. "
+ f"Обязательно активируйте подписку отдельно!\n\n{cart_message}",
reply_markup=keyboard
)
logger.info(
diff --git a/app/webapi/routes/users.py b/app/webapi/routes/users.py
index c2031719..c10c938d 100644
--- a/app/webapi/routes/users.py
+++ b/app/webapi/routes/users.py
@@ -158,6 +158,12 @@ async def get_user(
_: Any = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> UserResponse:
+ # First check if the provided ID is a telegram_id
+ user = await get_user_by_telegram_id(db, user_id)
+ if user:
+ return _serialize_user(user)
+
+ # If not found as telegram_id, check as internal user ID
user = await get_user_by_id(db, user_id)
if not user:
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
@@ -202,8 +208,15 @@ async def update_user_endpoint(
_: Any = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> UserResponse:
- user = await get_user_by_id(db, user_id)
- if not user:
+ # First check if the provided ID is a telegram_id
+ user = await get_user_by_telegram_id(db, user_id)
+ if user:
+ found_user = user
+ else:
+ # If not found as telegram_id, check as internal user ID
+ found_user = await get_user_by_id(db, user_id)
+
+ if not found_user:
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
updates: dict[str, Any] = {}
@@ -234,18 +247,23 @@ async def update_user_endpoint(
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Promo group not found")
updates["promo_group_id"] = promo_group.id
- if payload.referral_code is not None and payload.referral_code != user.referral_code:
+ if payload.referral_code is not None and payload.referral_code != found_user.referral_code:
existing_code_owner = await get_user_by_referral_code(db, payload.referral_code)
- if existing_code_owner and existing_code_owner.id != user.id:
+ if existing_code_owner and existing_code_owner.id != found_user.id:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Referral code already in use")
updates["referral_code"] = payload.referral_code
if not updates:
- return _serialize_user(user)
+ return _serialize_user(found_user)
- user = await update_user(db, user, **updates)
- user = await get_user_by_id(db, user.id)
- return _serialize_user(user)
+ found_user = await update_user(db, found_user, **updates)
+ # Reload the user to ensure we have the latest data
+ if found_user.telegram_id == user_id:
+ found_user = await get_user_by_telegram_id(db, user_id)
+ else:
+ found_user = await get_user_by_id(db, found_user.id)
+
+ return _serialize_user(found_user)
@router.post("/{user_id}/balance", response_model=UserResponse)
@@ -258,13 +276,20 @@ async def update_balance(
if payload.amount_kopeks == 0:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Amount must be non-zero")
- user = await get_user_by_id(db, user_id)
- if not user:
+ # First check if the provided ID is a telegram_id
+ user = await get_user_by_telegram_id(db, user_id)
+ if user:
+ found_user = user
+ else:
+ # If not found as telegram_id, check as internal user ID
+ found_user = await get_user_by_id(db, user_id)
+
+ if not found_user:
raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
success = await add_user_balance(
db,
- user,
+ found_user,
amount_kopeks=payload.amount_kopeks,
description=payload.description or "Корректировка через веб-API",
create_transaction=payload.create_transaction,
@@ -273,5 +298,10 @@ async def update_balance(
if not success:
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to update balance")
- user = await get_user_by_id(db, user_id)
- return _serialize_user(user)
+ # Reload the user to ensure we have the latest data
+ if found_user.telegram_id == user_id:
+ found_user = await get_user_by_telegram_id(db, user_id)
+ else:
+ found_user = await get_user_by_id(db, found_user.id)
+
+ return _serialize_user(found_user)
diff --git a/docs/web-admin-integration.md b/docs/web-admin-integration.md
index 01e2b84d..4f31ed9e 100644
--- a/docs/web-admin-integration.md
+++ b/docs/web-admin-integration.md
@@ -105,10 +105,10 @@ curl -X POST "http://127.0.0.1:8080/tokens" \
| `PUT` | `/settings/{key}` | Обновить значение настройки.
| `DELETE` | `/settings/{key}` | Сбросить настройку к значению по умолчанию.
| `GET` | `/users` | Список пользователей с фильтрами и пагинацией.
-| `GET` | `/users/{id}` | Детали пользователя.
+| `GET` | `/users/{id}` | Детали пользователя. ID может быть как внутренним (user.id), так и Telegram ID (user.telegram_id).
| `POST` | `/users` | Создать пользователя (например, для ручной выдачи доступа).
-| `PATCH` | `/users/{id}` | Обновить профиль пользователя или статус.
-| `POST` | `/users/{id}/balance` | Корректировка баланса с созданием транзакции.
+| `PATCH` | `/users/{id}` | Обновить профиль пользователя или статус. ID может быть как внутренним (user.id), так и Telegram ID (user.telegram_id).
+| `POST` | `/users/{id}/balance` | Корректировка баланса с созданием транзакции. ID может быть как внутренним (user.id), так и Telegram ID (user.telegram_id).
| `GET` | `/subscriptions` | Список подписок с фильтрами.
| `POST` | `/subscriptions` | Создать триальную или платную подписку.
| `POST` | `/subscriptions/{id}/extend` | Продлить подписку на N дней.
@@ -260,7 +260,7 @@ curl -X POST "http://127.0.0.1:8080/tokens" \
1. **Health-check** — перед авторизацией UI вызывает `GET /health`, чтобы отобразить статус и версию бота.
2. **Настройки UI** — подгружает категории через `GET /settings/categories`, далее выводит форму со значениями из `GET /settings`.
3. **Статистика дашборда** — `GET /stats/overview` для карточек с показателями.
-4. **Раздел пользователи** — `GET /users` с поиском (`search`), фильтрами по статусу или промо-группе. Для детальной карточки использовать `GET /users/{id}`.
+4. **Раздел пользователи** — `GET /users` с поиском (`search`), фильтрами по статусу или промо-группе. Для детальной карточки использовать `GET /users/{id}` (в качестве ID можно использовать как внутренний user.id, так и telegram_id пользователя).
5. **Операции с подпиской** — использовать `POST /subscriptions/{id}/...` эндпоинты для продления, выдачи трафика и устройств.
6. **Поддержка** — список тикетов (`GET /tickets`), изменение статуса (`POST /tickets/{id}/status`), блокировка ответов (`POST /tickets/{id}/reply-block`).
7. **История операций** — `GET /transactions` с фильтрами по пользователю, типу и периоду.