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` с фильтрами по пользователю, типу и периоду.