14 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Telegram bot for selling and managing Remnawave VPN subscriptions. Built with Aiogram 3.x (async), SQLAlchemy 2.x (async ORM), and PostgreSQL. Supports multiple payment providers (YooKassa, CryptoPay, FreeKassa, Platega, SeverPay, Telegram Stars) and integrates with the Remnawave panel API.
Common Development Commands
Running the Bot
# Using Docker Compose (recommended)
docker compose up -d
# View logs
docker compose logs -f remnawave-tg-shop
# Stop
docker compose down
# Local development (requires PostgreSQL running)
python main.py
Environment Setup
# Copy example environment file
cp .env.example .env
# Edit .env with your configuration
# Required: BOT_TOKEN, ADMIN_IDS, WEBHOOK_BASE_URL, POSTGRES_*, PANEL_API_URL, PANEL_API_KEY
Database Operations
The bot auto-creates tables on startup via init_db() in db/database_setup.py. No manual migrations needed for initial setup.
Architecture Overview
Layered Architecture
Handlers (routing, input validation)
↓
Services (business logic, orchestration)
↓
DAL (data access layer - pure async functions)
↓
Models (SQLAlchemy ORM)
↓
PostgreSQL
Core Components
Entry Point: main.py → bot/main_bot.py::run_bot()
Initialization Flow:
- Load
.envviaconfig/settings.py(Pydantic) - Initialize database connection (
db/database_setup.py) - Create dispatcher with middlewares (
bot/app/controllers/dispatcher_controller.py) - Build all services via factory (
bot/app/factories/build_services.py) - Register routers (
bot/routers.py) - Start AIOHTTP web server for webhooks (
bot/app/web/web_server.py)
Middleware Pipeline (Execution Order)
All middlewares are outer middlewares applied at dispatcher level:
- DBSessionMiddleware - Provides
sessionto handlers, auto-commits/rollbacks - I18nMiddleware - Sets
current_languageandi18n_instancein handler data - ProfileSyncMiddleware - Syncs Telegram user profile to local DB
- BanCheckMiddleware - Blocks banned users
- ChannelSubscriptionMiddleware - Enforces required channel subscription (if configured)
- ActionLoggerMiddleware - Logs all user actions to
message_logstable
Router Hierarchy
root_router (Private chat filter)
├── user_router_aggregate
│ ├── start_router (CommandStart)
│ ├── payment_router (payment flow callbacks)
│ ├── subscription_router (my_subscription callbacks)
│ ├── trial_router
│ ├── referral_router
│ ├── promo_user_router
│ └── payment method routers (yookassa, stars, crypto, etc.)
├── inline_mode.router
└── admin_main_filtered_router (AdminFilter)
├── admin_router_aggregate
├── admin_common_router
├── admin_payments_router
├── admin_promo_routers
├── admin_stats_router
├── admin_logs_router
├── admin_broadcast_router
├── admin_ads_router
├── admin_sync_router
└── admin_user_management_router
Key Subsystems
1. Service Layer (bot/services/)
All services are created once at startup in build_core_services() and injected into dispatcher. Handlers access them via data["service_name"].
Core Services:
- PanelApiService - REST client for Remnawave panel API (user CRUD, device management, stats)
- SubscriptionService - Subscription lifecycle management (trial, paid, renewals, traffic packages)
- YooKassaService - Primary payment provider with auto-renewal support
- StarsService, CryptoPayService, FreeKassaService, PlategaService, SeverPayService - Alternative payment providers
- PanelWebhookService - Handles panel subscription events (expiry notifications, auto-renew triggers)
- ReferralService - Referral tracking and bonus distribution
- PromoCodeService - Promo code validation and activation
- LknpdService - Tax receipt generation (nalog.ru integration)
Service Wiring Pattern:
# Services are cross-injected after creation
subscription_service.yookassa_service = yookassa_service
panel_webhook_service.subscription_service = subscription_service
2. Data Access Layer (db/dal/)
Pure async functions (no classes). Each module provides CRUD operations for specific entities.
Key DAL Modules:
user_dal.py- User CRUD, statisticssubscription_dal.py- Subscription upsert, status tracking, expiry queriespayment_dal.py- Payment records, financial statisticspromo_code_dal.py- Promo code management, activation trackinguser_billing_dal.py- Saved payment methods for auto-renewal
Common Pattern:
async def get_user_by_id(session: AsyncSession, user_id: int) -> Optional[User]:
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
return result.scalar_one_or_none()
3. Database Models (db/models.py)
Key Entities:
- User - Telegram user profile, panel linkage, referral tree
- Subscription - Panel-synced subscription state (active/inactive, expiry, traffic, auto-renew flag)
- Payment - Payment transaction records (status, provider, amount, promo code linkage)
- PromoCode / PromoCodeActivation - Promo codes with usage limits
- UserBilling / UserPaymentMethod - Saved payment methods for auto-renewal
- MessageLog - Audit trail of all user/admin actions
- PanelSyncStatus - Tracks automatic panel sync state
- AdCampaign / AdAttribution - Ad tracking
Important Relationships:
- User → Subscription (1:Many)
- User → Payment (1:Many)
- User → User (self-referencing for referral tree via
referred_by_id) - Subscription has
panel_user_uuidandpanel_subscription_uuidfor sync
4. Webhook Handling (bot/app/web/web_server.py)
Single AIOHTTP application hosts multiple webhook routes:
| Path | Handler | Purpose |
|---|---|---|
/{BOT_TOKEN} |
SimpleRequestHandler | Telegram updates |
/webhook/yookassa |
yookassa_webhook_route | YooKassa payment status |
/webhook/cryptopay |
cryptopay_webhook_route | CryptoPay transactions |
/webhook/freekassa |
freekassa_webhook_route | FreeKassa payments |
/webhook/platega |
platega_webhook_route | Platega payments |
/webhook/severpay |
severpay_webhook_route | SeverPay payments |
/webhook/panel |
panel_webhook_route | Panel subscription events |
Security: All webhooks verify signatures (HMAC-SHA256 for panel, provider-specific for payment systems).
5. Panel Integration
Remnawave API (bot/services/panel_api_service.py):
- REST API client with Bearer token auth
- User CRUD operations
- Device management (HWID disconnect)
- Subscription link generation
- System/bandwidth stats
User Linking Logic: When activating subscription, the bot ensures a panel user exists:
- Check local DB for
panel_user_uuid - If missing, search panel by
telegramId - If not found, search by username pattern
tg_{telegram_id} - If still missing, create new panel user with configured settings
- Save returned UUID to local DB
Panel Webhook Events (bot/services/panel_webhook_service.py):
user.expires_in_72_hours→ Send notificationuser.expires_in_48_hours→ Send notification (special if auto-renew enabled)user.expires_in_24_hours→ Trigger auto-renewal if YooKassa + saved carduser.expired→ Send expiration noticeuser.expired_24_hours_ago→ Follow-up reminder
6. Payment Flow
Standard Payment Flow:
- User selects subscription duration
- Handler creates
Paymentrecord (status:pending_{provider}) - Service creates payment with provider API
- User redirected to payment page
- Provider sends webhook on completion
- Webhook handler verifies signature, updates Payment status
- If succeeded: call
subscription_service.activate_subscription()- Ensures panel user exists/linked
- Creates/updates Subscription record
- Updates panel user expiry and traffic
- Enables auto-renew if payment method saved
- Send success notification with config link
Auto-Renewal Flow (YooKassa only):
- Panel webhook triggers 24h before expiry
PanelWebhookServicechecks if auto-renew enabled- Calls
subscription_service.charge_subscription_renewal() - Uses saved
payment_method_idfor off-session charge - If successful: suppress 24h notification, create new Payment
- If failed: send notification to user
Important Architectural Patterns
1. Dependency Injection via Factory
All services created in build_core_services(), stored in dispatcher:
dp["panel_service"] = panel_service
# Handlers access:
panel_service = data["panel_service"]
2. Upsert Pattern for Panel Sync
Uses panel_subscription_uuid as idempotency key to prevent duplicate subscriptions:
async def upsert_subscription(session, payload):
existing = await get_subscription_by_panel_subscription_uuid(...)
if existing:
# Update fields
else:
# Create new
3. FSM for Multi-Step Flows
State machine for complex interactions (defined in bot/states/):
# Set state
await state.set_state(UserPromoStates.waiting_for_promo_code)
# Next handler checks state, processes, clears
if await state.get_state() == UserPromoStates.waiting_for_promo_code:
# Process promo code
await state.clear()
4. Notification Suppression
Track last_notification_sent per subscription to avoid spam:
if last_notification_sent is None or date(last_notification_sent) < date(now):
# Send notification
await update_last_notification_sent(subscription)
Configuration
All settings loaded via Pydantic from .env (see .env.example for full reference).
Critical Settings:
BOT_TOKEN- Telegram bot tokenADMIN_IDS- Comma-separated admin Telegram IDsWEBHOOK_BASE_URL- External URL for webhooks (HTTPS required)PANEL_API_URL,PANEL_API_KEY- Remnawave panel accessPANEL_WEBHOOK_SECRET- HMAC signature verification- Payment provider credentials (YooKassa, CryptoPay, etc.)
- Pricing:
RUB_PRICE_1_MONTH,STARS_PRICE_1_MONTH, etc.
Sales Modes:
- Time-based (default): User buys N months subscription
- Traffic-based (
traffic_sale_mode): User buys X GB with far-future expiry
Development Guidelines
Adding New Payment Provider
- Create service in
bot/services/{provider}_service.pyimplementing:create_payment()- Generate payment URL/data- Webhook handler function
- Add webhook route in
bot/app/web/web_server.py - Add service to
build_core_services()factory - Create handler router in
bot/handlers/user/subscription/payments_{provider}.py - Register router in
bot/routers.py - Add pricing settings to
config/settings.py - Update payment method selection in
bot/handlers/user/subscription/payment_methods.py
Adding New Admin Feature
- Create handler in
bot/handlers/admin/{feature}.py - Add keyboard buttons in
bot/keyboards/inline/admin_keyboards.py - Register router in
bot/routers.pyunderadmin_main_filtered_router - Use
AdminFilter()to protect routes - Access services via
data["service_name"]
Database Changes
The project uses SQLAlchemy models without formal migrations. To add fields:
- Update model in
db/models.py - Add corresponding DAL functions in
db/dal/ - Drop and recreate tables in dev (bot auto-creates on startup)
- For production, manually ALTER tables or use
db/migrator.pyas template
Localization
Translations stored in locales/{lang}/LC_MESSAGES/messages.json. To add strings:
- Add key-value to both
ruandenfiles - Access in handlers via:
i18n.get("key_name") - I18n instance available in handler data:
data["i18n_instance"]
Testing Checklist
When making changes, verify:
- Webhooks are reachable (use ngrok for local testing)
- Payment flow completes end-to-end
- Panel user creation/linking works
- Subscription activation updates both DB and panel
- Auto-renewal triggers correctly
- Notifications send at proper times
- Admin panel functions accessible only to admins
- Banned users cannot access bot
- Referral bonuses apply correctly
- Promo codes validate and activate
Deployment
Production Setup:
- Configure reverse proxy (Nginx/HAProxy) to route HTTPS webhooks to container
- Set
WEBHOOK_BASE_URLto external domain - Run
docker compose up -d - Verify webhook registration in logs
- Add bot as admin to required channel (if using
REQUIRED_CHANNEL_ID) - Test payment flow with small amount
Webhook Paths:
- Telegram:
https://yourdomain.com/{BOT_TOKEN} - YooKassa:
https://yourdomain.com/webhook/yookassa - Panel:
https://yourdomain.com/webhook/panel - Other providers:
https://yourdomain.com/webhook/{provider}
Troubleshooting
Bot not responding:
- Check webhook is set correctly (logs show webhook URL on startup)
- Verify HTTPS is working for
WEBHOOK_BASE_URL - Check
docker compose logsfor errors
Payment not completing:
- Verify webhook route is accessible from provider
- Check signature verification is passing
- Look for errors in payment webhook handler logs
- Ensure
provider_payment_idis unique (duplicate payments are rejected)
Panel sync issues:
- Check
PANEL_API_KEYis valid - Verify panel user exists with matching UUID
- Check panel webhook secret matches
- Review
panel_sync_statustable for last sync time
Auto-renewal not working:
- Only works with YooKassa
- User must have saved payment method (
user_billingoruser_payment_methodstable) - Subscription must have
auto_renew_enabled = true - Panel webhook must trigger 24h before expiry