fix_refferal/GIFTS/redirect/name_sub and more

This commit is contained in:
Vladless
2024-12-19 20:37:41 +03:00
parent 725167f663
commit c01b528055
49 changed files with 147036 additions and 1723 deletions
+16 -1
View File
@@ -83,5 +83,20 @@ CREATE TABLE IF NOT EXISTS servers
api_url TEXT NOT NULL,
subscription_url TEXT NOT NULL,
inbound_id TEXT NOT NULL,
UNIQUE (cluster_name, server_name) -- Уникальность по названию кластера и сервера
UNIQUE (cluster_name, server_name)
);
CREATE TABLE IF NOT EXISTS gifts
(
gift_id TEXT PRIMARY KEY NOT NULL,
sender_tg_id BIGINT NOT NULL,
selected_months INTEGER NOT NULL,
expiry_time TIMESTAMP WITH TIME ZONE NOT NULL,
gift_link TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
is_used BOOLEAN NOT NULL DEFAULT FALSE,
recipient_tg_id BIGINT,
CONSTRAINT fk_sender FOREIGN KEY (sender_tg_id) REFERENCES users (tg_id),
CONSTRAINT fk_recipient FOREIGN KEY (recipient_tg_id) REFERENCES users (tg_id)
);
+1 -1
View File
@@ -3,8 +3,8 @@ import subprocess
from datetime import datetime
from aiogram.types import BufferedInputFile
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER
from logger import logger
+1 -2
View File
@@ -5,8 +5,8 @@ from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import ErrorEvent
from config import API_TOKEN
from config import API_TOKEN
from logger import logger
from middlewares.admin import AdminMiddleware
from middlewares.database import DatabaseMiddleware
@@ -23,7 +23,6 @@ dp.callback_query.middleware(LoggingMiddleware())
dp.message.middleware(AdminMiddleware())
dp.callback_query.middleware(AdminMiddleware())
dp.message.middleware(UserMiddleware())
dp.callback_query.middleware(UserMiddleware())
+2 -1
View File
@@ -1,5 +1,6 @@
import py3xui
from config import LIMIT_IP
from logger import logger
@@ -72,7 +73,7 @@ async def extend_client_key(
client.sub_id = email
client.total_gb = total_gb
client.enable = True
client.limit_ip = 1
client.limit_ip = LIMIT_IP
client.inbound_id = inbound_id
await xui.client.update(client.id, client)
+99 -49
View File
@@ -2,8 +2,8 @@ from datetime import datetime
from typing import Any
import asyncpg
from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from logger import logger
@@ -11,7 +11,6 @@ async def init_db(file_path: str = "assets/schema.sql"):
with open(file_path) as file:
sql_content = file.read()
# Split the file content into individual SQL statements and connect to the database
statements = [stmt.strip() for stmt in sql_content.split(";") if stmt.strip()]
conn = await asyncpg.connect(DATABASE_URL)
@@ -92,7 +91,6 @@ async def get_all_coupons(session: Any):
Exception: В случае ошибки при получении данных из базы
"""
try:
# Выполняем запрос на получение всех купонов из базы данных
coupons = await session.fetch(
"""
SELECT code, amount, usage_limit, usage_count
@@ -100,12 +98,10 @@ async def get_all_coupons(session: Any):
"""
)
# Логируем успешное получение списка купонов
logger.info(f"Успешно получено {len(coupons)} купонов из базы данных")
return coupons
except Exception as e:
# Подробное логирование ошибки при получении купонов
logger.error(f"Критическая ошибка при получении списка купонов: {e}")
logger.exception("Трассировка стека ошибки получения купонов")
return []
@@ -129,7 +125,6 @@ async def delete_coupon_from_db(coupon_code: str, session: Any):
result = await delete_coupon_from_db('SALE50', session)
"""
try:
# Проверяем существование купона в базе данных
coupon_record = await session.fetchrow(
"""
SELECT id FROM coupons WHERE code = $1
@@ -137,12 +132,10 @@ async def delete_coupon_from_db(coupon_code: str, session: Any):
coupon_code,
)
# Если купон не найден, возвращаем False
if not coupon_record:
logger.info(f"Купон {coupon_code} не найден в базе данных")
return False
# Удаляем купон из базы данных
await session.execute(
"""
DELETE FROM coupons WHERE code = $1
@@ -150,12 +143,10 @@ async def delete_coupon_from_db(coupon_code: str, session: Any):
coupon_code,
)
# Логируем успешное удаление купона
logger.info(f"Купон {coupon_code} успешно удален из базы данных")
return True
except Exception as e:
# Логируем ошибку на русском с подробным описанием
logger.error(f"Произошла ошибка при удалении купона {coupon_code}: {e}")
return False
@@ -577,12 +568,11 @@ async def get_all_users(conn):
async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any):
try:
# Если айди приглашенного совпадает с айди пригласившего
if referred_tg_id == referrer_tg_id:
logger.warning(f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку.")
return
# Вставка записи о реферале в базу данных
await session.execute(
"""
INSERT INTO referrals (referred_tg_id, referrer_tg_id)
@@ -619,7 +609,6 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
logger.info(f"Начало обработки реферальной системы для пользователя {tg_id}")
MAX_REFERRAL_LEVELS = len(REFERRAL_BONUS_PERCENTAGES.keys())
visited_tg_ids = set()
current_tg_id = tg_id
@@ -644,11 +633,16 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
)
if not referral:
logger.info(f"Цепочка рефералов завершена на уровне {level}.")
break
referrer_tg_id = referral["referrer_tg_id"]
referral_chain.append({"tg_id": referrer_tg_id, "level": level})
if referrer_tg_id in visited_tg_ids:
logger.warning(f"Реферер {referrer_tg_id} уже обработан. Пропуск.")
break
referral_chain.append({"tg_id": referrer_tg_id, "level": level})
current_tg_id = referrer_tg_id
for referral in referral_chain:
@@ -656,14 +650,16 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
level = referral["level"]
bonus_percent = REFERRAL_BONUS_PERCENTAGES.get(level, 0)
bonus = amount * bonus_percent
bonus = max(bonus, 0)
if bonus_percent <= 0:
logger.warning(f"Процент бонуса для уровня {level} равен 0. Пропуск.")
continue
bonus = round(amount * bonus_percent, 2)
if bonus > 0:
logger.info(
f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}"
f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}."
)
await update_balance(referrer_tg_id, bonus)
except Exception as e:
@@ -676,22 +672,6 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
async def get_referral_stats(referrer_tg_id: int):
"""
Получение подробной статистики рефералов для указанного пользователя.
Args:
referrer_tg_id (int): Telegram ID пользователя, для которого запрашивается статистика рефералов.
Returns:
dict: Словарь с детальной статистикой рефералов, содержащий:
- total_referrals (int): Общее количество рефералов
- active_referrals (int): Количество активных рефералов (с начисленным бонусом)
- referrals_by_level (dict): Количество рефералов по каждому уровню
- total_referral_bonus (float): Общая сумма бонусов от рефералов
Raises:
Exception: В случае ошибки при подключении к базе данных или выполнении запроса
"""
conn = None
try:
conn = await asyncpg.connect(DATABASE_URL)
@@ -699,25 +679,22 @@ async def get_referral_stats(referrer_tg_id: int):
f"Установлено подключение к базе данных для получения статистики рефералов пользователя {referrer_tg_id}"
)
# Общее количество рефералов
total_referrals = await conn.fetchval(
"""
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1
""",
""",
referrer_tg_id,
)
logger.debug(f"Получено общее количество рефералов: {total_referrals}")
# Активные рефералы
active_referrals = await conn.fetchval(
"""
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1 AND reward_issued = TRUE
""",
""",
referrer_tg_id,
)
logger.debug(f"Получено количество активных рефералов: {active_referrals}")
# Рефералы по уровням
referrals_by_level_records = await conn.fetch(
"""
WITH RECURSIVE referral_levels AS (
@@ -739,11 +716,10 @@ async def get_referral_stats(referrer_tg_id: int):
JOIN referrals r ON rl.referred_tg_id = r.referred_tg_id
GROUP BY level
ORDER BY level
""",
""",
referrer_tg_id,
)
# Преобразование результатов в словарь
referrals_by_level = {
record["level"]: {
"total": record["level_count"],
@@ -753,17 +729,37 @@ async def get_referral_stats(referrer_tg_id: int):
}
logger.debug(f"Получена статистика рефералов по уровням: {referrals_by_level}")
# Общая сумма бонусов от рефералов
total_referral_bonus = await conn.fetchval(
"""
SELECT COALESCE(SUM(amount), 0)
FROM payments
WHERE tg_id IN (
SELECT referred_tg_id
WITH RECURSIVE referral_levels AS (
SELECT
referred_tg_id,
referrer_tg_id,
1 AS level
FROM referrals
WHERE referrer_tg_id = $1
) AND status = 'success'
""",
UNION
SELECT
r.referred_tg_id,
r.referrer_tg_id,
rl.level + 1
FROM referrals r
JOIN referral_levels rl ON r.referrer_tg_id = rl.referred_tg_id
WHERE rl.level < 5
)
SELECT
SUM(p.amount * CASE
WHEN rl.level = 1 THEN 0.25
WHEN rl.level = 2 THEN 0.10
WHEN rl.level = 3 THEN 0.05
ELSE 0
END) AS total_bonus
FROM referral_levels rl
JOIN payments p ON rl.referred_tg_id = p.tg_id
WHERE p.status = 'success'
""",
referrer_tg_id,
)
logger.debug(
@@ -788,6 +784,7 @@ async def get_referral_stats(referrer_tg_id: int):
logger.info("Закрытие подключения к базе данных")
async def update_key_expiry(client_id: str, new_expiry_time: int):
"""
Обновление времени истечения ключа для указанного клиента.
@@ -1204,7 +1201,7 @@ async def get_servers_from_db():
async def delete_user_data(session: Any, tg_id: int):
try:
await session.execute("DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id)
except Exception as e:
@@ -1216,3 +1213,56 @@ async def delete_user_data(session: Any, tg_id: int):
await session.execute("DELETE FROM connections WHERE tg_id = $1", tg_id)
await session.execute("DELETE FROM keys WHERE tg_id = $1", tg_id)
await session.execute("DELETE FROM referrals WHERE referrer_tg_id = $1", tg_id)
async def store_gift_link(
gift_id: str, sender_tg_id: int, selected_months: int, expiry_time: datetime, gift_link: str, session: Any = None
):
"""
Добавляет информацию о подарке в базу данных.
Args:
gift_id (str): Уникальный идентификатор подарка
sender_tg_id (int): Идентификатор пользователя, который отправил подарок
selected_months (int): Количество месяцев подписки
expiry_time (datetime): Время окончания подписки
gift_link (str): Ссылка для активации подарка
session (Any): Сессия базы данных для выполнения запроса
Returns:
bool: True, если информация о подарке успешно добавлена, иначе False
Raises:
Exception: В случае ошибки при сохранении информации о подарке
"""
conn = None
try:
conn = session if session is not None else await asyncpg.connect(DATABASE_URL)
result = await conn.execute(
"""
INSERT INTO gifts (gift_id, sender_tg_id, recipient_tg_id, selected_months, expiry_time, gift_link, created_at, is_used)
VALUES ($1, $2, NULL, $3, $4, $5, $6, FALSE)
""",
gift_id,
sender_tg_id,
selected_months,
expiry_time,
gift_link,
datetime.utcnow(),
)
if result:
logger.info(f"Подарок с ID {gift_id} успешно добавлен в базу данных.")
return True
else:
logger.error(f"Не удалось добавить подарок с ID {gift_id} в базу данных.")
return False
except Exception as e:
logger.error(f"Ошибка при сохранении подарка с ID {gift_id} в базе данных: {e}")
return False
finally:
if conn is not None and session is None:
await conn.close()
+1
View File
@@ -1,6 +1,7 @@
from aiogram.filters import BaseFilter
from aiogram.types import Message
from config import ADMIN_ID
+1
View File
@@ -21,6 +21,7 @@ router.include_routers(
pay_router,
donate_router,
coupons_router,
notifications_router,
payments_router,
keys_router,
+1 -1
View File
@@ -4,10 +4,10 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from py3xui import AsyncApi
from backup import create_backup_and_send_to_admins
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from database import check_unique_server_name, get_servers_from_db
from filters.admin import IsAdminFilter
+2 -9
View File
@@ -7,16 +7,9 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import TOTAL_GB
from database import (
get_client_id_by_email,
get_servers_from_db,
restore_trial,
update_key_expiry,
delete_user_data
)
from database import delete_user_data, get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry
from filters.admin import IsAdminFilter
from handlers.keys.key_utils import (
delete_key_from_cluster,
+1 -1
View File
@@ -3,8 +3,8 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import RUB_TO_XTR
from config import RUB_TO_XTR
from logger import logger
+1 -1
View File
@@ -4,8 +4,8 @@ from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CONNECT_IOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
from config import CONNECT_IOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
from handlers.texts import INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE
router = Router()
+33 -75
View File
@@ -8,6 +8,7 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CONNECT_ANDROID,
CONNECT_IOS,
@@ -18,7 +19,6 @@ from config import (
SUPPORT_CHAT_URL,
TRIAL_TIME,
)
from database import get_balance, get_trial, store_key, update_balance
from handlers.keys.key_utils import create_key_on_cluster
from handlers.texts import DISCOUNTS, KEY, key_message_success
@@ -36,9 +36,7 @@ class Form(StatesGroup):
@router.callback_query(F.data == "create_key")
async def process_callback_create_key(
callback_query: CallbackQuery, state: FSMContext, session: Any
):
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
server_id = "все сервера"
await state.update_data(selected_server_id=server_id)
await select_server(callback_query, state, session)
@@ -49,14 +47,9 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext, sessio
if trial_status == 1:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="✅ Да, подключить новое устройство",
callback_data="confirm_create_new_key",
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
InlineKeyboardButton(text="✅ Да, подключить новое устройство", callback_data="confirm_create_new_key")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
text=KEY,
@@ -64,31 +57,22 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext, sessio
)
await state.update_data(creating_new_key=True)
else:
await handle_key_creation(
callback_query.message.chat.id, state, session, callback_query
)
await handle_key_creation(callback_query.message.chat.id, state, session, callback_query)
@router.callback_query(F.data == "confirm_create_new_key")
async def confirm_create_new_key(
callback_query: CallbackQuery, state: FSMContext, session: Any
):
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
tg_id = callback_query.message.chat.id
logger.info(f"User {tg_id} confirmed creation of a new key.")
logger.info(
f"Balance for user {tg_id} is sufficient. Proceeding with key creation."
)
logger.info(f"Balance for user {tg_id} is sufficient. Proceeding with key creation.")
await handle_key_creation(tg_id, state, session, callback_query)
async def handle_key_creation(
tg_id: int,
state: FSMContext,
session: Any,
message_or_query: Message | CallbackQuery,
tg_id: int, state: FSMContext, session: Any, message_or_query: Message | CallbackQuery
):
"""Создание ключа с учётом выбора тарифного плана."""
current_time = datetime.utcnow()
@@ -98,9 +82,7 @@ async def handle_key_creation(
expiry_time = current_time + timedelta(days=TRIAL_TIME)
logger.info(f"Assigned 1-day trial to user {tg_id}.")
await session.execute(
"UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id
)
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
await create_key(tg_id, expiry_time, state, session, message_or_query)
else:
builder = InlineKeyboardBuilder()
@@ -117,27 +99,21 @@ async def handle_key_creation(
builder.row(
InlineKeyboardButton(
text=f"📅 {plan_id} мес. - {price}{discount_text}",
callback_data=f"select_plan_{plan_id}",
text=f"📅 {plan_id} мес. - {price}{discount_text}", callback_data=f"select_plan_{plan_id}"
)
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await message_or_query.message.answer(
"💳 Выберите тарифный план для создания нового ключа:",
reply_markup=builder.as_markup(),
"💳 Выберите тарифный план для создания нового ключа:", reply_markup=builder.as_markup()
)
await state.update_data(tg_id=tg_id)
await state.set_state(Form.waiting_for_server_selection)
@router.callback_query(F.data.startswith("select_plan_"))
async def select_tariff_plan(
callback_query: CallbackQuery, state: FSMContext, session: Any
):
async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, session: Any):
tg_id = callback_query.message.chat.id
plan_id = callback_query.data.split("_")[-1]
plan_price = RENEWAL_PRICES.get(plan_id)
@@ -151,12 +127,8 @@ async def select_tariff_plan(
balance = await get_balance(tg_id)
if balance < plan_price:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay")
)
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
"💳 Недостаточно средств для создания подписки. Пополните баланс в личном кабинете.",
reply_markup=builder.as_markup(),
@@ -172,11 +144,7 @@ async def select_tariff_plan(
async def create_key(
tg_id: int,
expiry_time: datetime,
state: FSMContext,
session: Any,
message_or_query: Message | CallbackQuery,
tg_id: int, expiry_time: datetime, state: FSMContext, session: Any, message_or_query: Message | CallbackQuery
):
"""Создаёт ключ с заданным сроком действия."""
while True:
@@ -190,9 +158,7 @@ async def create_key(
)
if not existing_key:
break
logger.warning(
f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one."
)
logger.warning(f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one.")
client_id = str(uuid.uuid4())
email = key_name.lower()
@@ -217,21 +183,17 @@ async def create_key(
await asyncio.gather(*tasks)
logger.info(f"Key created on cluster {least_loaded_cluster} for user {tg_id}.")
await store_key(
tg_id,
client_id,
email,
expiry_timestamp,
public_link,
least_loaded_cluster,
session,
)
await store_key(tg_id, client_id, email, expiry_timestamp, public_link, least_loaded_cluster, session)
except Exception as e:
logger.error(f"Error while creating the key for user {tg_id} on cluster: {e}")
await message_or_query.message.answer(
"❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
)
if isinstance(message_or_query, Message):
await message_or_query.answer("❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова.")
elif isinstance(message_or_query, CallbackQuery):
await message_or_query.message.answer(
"❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
)
return
builder = InlineKeyboardBuilder()
@@ -241,23 +203,19 @@ async def create_key(
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
)
builder.row(
InlineKeyboardButton(
text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"
),
InlineKeyboardButton(
text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"
),
)
builder.row(
InlineKeyboardButton(
text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"
)
InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"),
InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"),
)
builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
remaining_time = expiry_time - datetime.utcnow()
days = remaining_time.days
key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
await message_or_query.message.answer(key_message, reply_markup=builder.as_markup())
if isinstance(message_or_query, Message):
await message_or_query.answer(key_message, reply_markup=builder.as_markup())
elif isinstance(message_or_query, CallbackQuery):
await message_or_query.message.answer(key_message, reply_markup=builder.as_markup())
await state.clear()
+3 -3
View File
@@ -1,10 +1,10 @@
import asyncio
import asyncpg
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
from py3xui import AsyncApi
from client import add_client, delete_client, extend_client_key
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, LIMIT_IP, TOTAL_GB
from database import get_servers_from_db
from logger import logger
@@ -46,7 +46,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
client_id,
email,
tg_id,
limit_ip=1,
limit_ip=LIMIT_IP,
total_gb=TOTAL_GB,
expiry_time=expiry_timestamp,
enable=True,
@@ -184,7 +184,7 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
client_id,
email,
tg_id,
limit_ip=1,
limit_ip=LIMIT_IP,
total_gb=TOTAL_GB,
expiry_time=expiry_time,
enable=True,
+1 -1
View File
@@ -7,6 +7,7 @@ from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import (
CONNECT_ANDROID,
CONNECT_IOS,
@@ -16,7 +17,6 @@ from config import (
RENEWAL_PLANS,
TOTAL_GB,
)
from database import (
delete_key,
get_balance,
+17 -10
View File
@@ -4,13 +4,10 @@ from datetime import datetime
import aiohttp
import asyncpg
from aiohttp import web
from config import DATABASE_URL, TRANSITION_DATE_STR
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, TRANSITION_DATE_STR
from database import get_servers_from_db
from logger import logger
import urllib.parse
from config import PROJECT_NAME, SUB_MESSAGE
async def fetch_url_content(url, tg_id):
@@ -78,12 +75,10 @@ async def handle_old_subscription(request):
conn = await asyncpg.connect(DATABASE_URL)
try:
key_info = await conn.fetchrow(
"SELECT created_at FROM keys WHERE email = $1", email
"SELECT created_at, server_id FROM keys WHERE email = $1", email
)
if not key_info:
logger.warning(f"Клиент с email {email} не найден в базе.")
return web.Response(
@@ -92,7 +87,14 @@ async def handle_old_subscription(request):
)
created_at_ms = key_info["created_at"]
cluster_name = key_info["cluster_name"]
cluster_name = key_info.get("server_id")
if not cluster_name:
logger.warning(f"У клиента с email {email} отсутствует cluster_name.")
return web.Response(
text="❌ Устаревшие данные. Обратитесь в поддержку.",
status=400,
)
logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}")
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
@@ -127,7 +129,9 @@ async def handle_old_subscription(request):
"Content-Type": "text/plain; charset=utf-8",
"Content-Disposition": "inline",
"profile-update-interval": "7",
"profile-title": encoded_project_name,
"profile-title": "base64:" + base64.b64encode(
encoded_project_name.encode("utf-8")
).decode("utf-8"),
}
logger.info(f"Возвращаем объединенные подписки для email: {email}")
@@ -137,6 +141,7 @@ async def handle_old_subscription(request):
await conn.close()
async def handle_new_subscription(request):
email = request.match_info.get("email")
tg_id = request.match_info.get("tg_id")
@@ -200,7 +205,9 @@ async def handle_new_subscription(request):
"Content-Type": "text/plain; charset=utf-8",
"Content-Disposition": "inline",
"profile-update-interval": "7",
"profile-title": encoded_project_name,
"profile-title": "base64:" + base64.b64encode(
encoded_project_name.encode("utf-8")
).decode("utf-8"),
}
logger.info(f"Возвращаем объединенные подписки для email: {email}")
+1 -1
View File
@@ -3,10 +3,10 @@ import uuid
from datetime import datetime, timedelta
from typing import Any
from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
from py3xui import AsyncApi
from client import add_client
from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
from database import get_servers_from_db, store_key, use_trial
from handlers.texts import INSTRUCTIONS
from handlers.utils import generate_random_email, get_least_loaded_cluster
+2 -2
View File
@@ -4,6 +4,8 @@ from datetime import datetime, timedelta
import asyncpg
from aiogram import Bot, Router, types
from aiogram.utils.keyboard import InlineKeyboardBuilder
from py3xui import AsyncApi
from config import (
ADMIN_PASSWORD,
ADMIN_USERNAME,
@@ -13,8 +15,6 @@ from config import (
TOTAL_GB,
TRIAL_TIME,
)
from py3xui import AsyncApi
from database import (
add_notification,
check_notification_time,
+9 -3
View File
@@ -2,9 +2,15 @@ from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE, DONATIONS_ENABLE, YOOMONEY_ENABLE
from config import (
CRYPTO_BOT_ENABLE,
DONATIONS_ENABLE,
FREEKASSA_ENABLE,
ROBOKASSA_ENABLE,
STARS_ENABLE,
YOOKASSA_ENABLE,
YOOMONEY_ENABLE,
)
router = Router()
+4 -3
View File
@@ -1,6 +1,7 @@
__all__ = ("router",)
from aiogram import Router
from config import (
CRYPTO_BOT_ENABLE,
FREEKASSA_ENABLE,
@@ -11,7 +12,7 @@ from config import (
)
from .cryprobot_pay import router as cryprobot_router
from .freekassa_pay import router as freekassa_router
from .gift import router as gift_router
from .robokassa_pay import router as robokassa_router
from .stars_pay import router as stars_router
from .yookassa_pay import router as yookassa_router
@@ -23,11 +24,11 @@ if YOOKASSA_ENABLE:
router.include_router(yookassa_router)
if YOOMONEY_ENABLE:
router.include_router(yoomoney_router)
if FREEKASSA_ENABLE:
router.include_router(freekassa_router)
if ROBOKASSA_ENABLE:
router.include_router(robokassa_router)
if CRYPTO_BOT_ENABLE:
router.include_router(cryprobot_router)
if STARS_ENABLE:
router.include_router(stars_router)
router.include_router(gift_router)
File diff suppressed because it is too large Load Diff
Binary file not shown.
-213
View File
@@ -1,213 +0,0 @@
from typing import Any
from aiocryptopay import AioCryptoPay, Networks
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from config import CRYPTO_BOT_ENABLE, CRYPTO_BOT_TOKEN, RUB_TO_USDT
from database import (
add_connection,
add_payment,
check_connection_exists,
get_key_count,
update_balance,
)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
router = Router()
if CRYPTO_BOT_ENABLE:
crypto = AioCryptoPay(token=CRYPTO_BOT_TOKEN, network=Networks.MAIN_NET)
class ReplenishBalanceState(StatesGroup):
choosing_amount_crypto = State()
waiting_for_payment_confirmation_crypto = State()
entering_custom_amount_crypto = State()
@router.callback_query(F.data == "pay_cryptobot")
async def process_callback_pay_cryptobot(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'crypto_{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'crypto_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'crypto_{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_crypto",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
key_count = await get_key_count(callback_query.message.chat.id)
if key_count == 0:
exists = await check_connection_exists(callback_query.message.chat.id)
if not exists:
await add_connection(
tg_id=callback_query.message.chat.id,
balance=0.0,
trial=0,
session=session,
)
await callback_query.message.answer(
"Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_crypto)
@router.callback_query(F.data.startswith("crypto_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
data = callback_query.data.split("|", 1)
if len(data) != 2:
await callback_query.message.answer("Неверные данные для выбора суммы.")
return
amount_str = data[1]
try:
amount = int(amount_str)
except ValueError:
await callback_query.message.answer("Некорректная сумма.")
return
await state.update_data(amount=amount)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_crypto)
try:
invoice = await crypto.create_invoice(
asset="USDT",
amount=str(int(amount // RUB_TO_USDT)),
description=f"Пополнения баланса на {amount} руб",
payload=f"{callback_query.message.chat.id}:{int(amount)}",
)
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
await callback_query.message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=builder.as_markup(),
)
else:
await callback_query.message.answer("Ошибка при создании платежа.")
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
async def cryptobot_webhook(request):
try:
data = await request.json()
logger.info(f"Получены данные вебхука: {data}")
if data.get("update_type") == "invoice_paid":
await process_crypto_payment(data["payload"])
return web.Response(status=200)
else:
logger.warning(
f"Неподдерживаемый тип обновления: {data.get('update_type')}"
)
return web.Response(status=400)
except Exception as e:
logger.error(f"Ошибка обработки вебхука: {e}")
return web.Response(status=500)
async def process_crypto_payment(payload):
if payload["status"] == "paid":
custom_payload = payload["payload"]
user_id_str, amount_str = custom_payload.split(":")
try:
user_id = int(user_id_str)
amount = int(amount_str)
await add_payment(int(user_id), float(amount), "cryptobot")
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
logger.error(f"Ошибка конвертации user_id или amount: {e}")
else:
logger.warning(f"Получен неоплаченный инвойс: {payload}")
@router.callback_query(F.data == "enter_custom_amount_crypto")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_cryptobot"))
await callback_query.message.answer(
"Пожалуйста, введите сумму пополнения.",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.entering_custom_amount_crypto)
@router.message(ReplenishBalanceState.entering_custom_amount_crypto)
async def process_custom_amount_input(message: types.Message, state: FSMContext):
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_USDT <= 0:
await message.answer(
f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:"
)
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_crypto
)
try:
invoice = await crypto.create_invoice(
asset="USDT",
amount=str(int(amount // RUB_TO_USDT)),
description=f"Пополнения баланса на {amount} руб",
payload=f"{message.chat.id}:{amount}",
)
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
)
await message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=builder.as_markup(),
)
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
else:
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
File diff suppressed because it is too large Load Diff
Binary file not shown.
-201
View File
@@ -1,201 +0,0 @@
import hashlib
import hmac
import logging
import time
import uuid
import requests
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID
from database import add_payment, update_balance
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
router = Router()
logging.basicConfig(level=logging.DEBUG)
class ReplenishBalanceState(StatesGroup):
choosing_amount_freekassa = State()
waiting_for_payment_confirmation_freekassa = State()
entering_custom_amount_freekassa = State()
def generate_signature(params, api_key):
sorted_params = {k: params[k] for k in sorted(params)}
sign_string = "|".join(str(value) for value in sorted_params.values())
return hmac.new(api_key.encode(), sign_string.encode(), hashlib.sha256).hexdigest()
async def create_payment(user_id, amount, email, ip):
payment_id = str(uuid.uuid4())
nonce = int(time.time() * 1000)
params = {
"shopId": FREEKASSA_SHOP_ID,
"amount": amount,
"currency": "RUB",
"paymentId": payment_id,
"email": email,
"ip": ip,
"i": 6,
"nonce": nonce,
}
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
try:
response = requests.post(
"https://api.freekassa.com/v1/orders/create", json=params
)
response_data = response.json()
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
if response_data.get("type") == "success":
return response_data["location"]
else:
logging.error(f"Ошибка создания платежа: {response_data}")
return None
except Exception as e:
logging.error(f"Ошибка запроса к FreeKassa: {e}")
return None
async def freekassa_webhook(request):
data = await request.json()
logging.debug(f"Получен вебхук от FreeKassa: {data}")
logging.debug(f"Данные вебхука от FreeKassa: {data}")
if data["status"] == "completed":
user_id = data["metadata"]["user_id"]
amount = float(data["amount"])
await add_payment(int(user_id), float(amount), "freekassa")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
return web.Response(status=200)
@router.callback_query(lambda c: c.data == "pay_freekassa")
async def process_callback_pay_freekassa(
callback_query: types.CallbackQuery, state: FSMContext
):
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'freekassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'freekassa_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'freekassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_freekassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
await callback_query.message.answer(
text="Выберите сумму пополнения через FreeKassa:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_freekassa)
@router.callback_query(F.data.startswith("freekassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
data = callback_query.data.split("|", 1)
amount_str = data[1]
try:
amount = int(amount_str)
except ValueError:
await callback_query.message.answer("Некорректная сумма.")
return
user_email = f"{callback_query.message.chat.id}@solo.net"
user_ip = callback_query.message.chat.id
payment_url = await create_payment(
callback_query.message.chat.id, amount, user_email, user_ip
)
if payment_url:
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=f"Оплатить {amount} рублей", url=payment_url
)
],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
await callback_query.message.answer(
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
reply_markup=confirm_keyboard,
)
else:
await callback_query.message.answer(
"Ошибка при создании платежа. Попробуйте позже.",
)
@router.callback_query(F.data == "enter_custom_amount_freekassa")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
await callback_query.message.answer(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
@router.message(ReplenishBalanceState.entering_custom_amount_freekassa)
async def process_custom_amount_input(message: types.Message, state: FSMContext):
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
await message.answer(
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
)
return
user_email = f"{message.chat.id}@solo.net"
user_ip = message.chat.id
payment_url = await create_payment(message.chat.id, amount, user_email, user_ip)
if payment_url:
keyboard = InlineKeyboardMarkup(
inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]]
)
await message.answer(
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
reply_markup=keyboard,
)
else:
await message.answer("Ошибка при создании платежа. Попробуйте позже.")
else:
await message.answer("Пожалуйста, введите корректную сумму.")
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-277
View File
@@ -1,277 +0,0 @@
import hashlib
from typing import Any
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from config import (
ROBOKASSA_ENABLE,
ROBOKASSA_LOGIN,
ROBOKASSA_PASSWORD1,
ROBOKASSA_PASSWORD2,
ROBOKASSA_TEST_MODE,
)
from robokassa import HashAlgorithm, Robokassa
from database import (
add_connection,
add_payment,
check_connection_exists,
get_key_count,
update_balance,
)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
router = Router()
class ReplenishBalanceState(StatesGroup):
choosing_amount_robokassa = State()
waiting_for_payment_confirmation_robokassa = State()
if ROBOKASSA_ENABLE:
robokassa = Robokassa(
merchant_login=ROBOKASSA_LOGIN,
password1=ROBOKASSA_PASSWORD1,
password2=ROBOKASSA_PASSWORD2,
algorithm=HashAlgorithm.md5,
is_test=ROBOKASSA_TEST_MODE,
)
logger.info("Robokassa initialized with login: {}", ROBOKASSA_LOGIN)
def generate_payment_link(amount, inv_id, description, tg_id):
"""Генерация ссылки на оплату."""
logger.debug(
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
)
payment_link = robokassa._payment.link.generate_by_script(
out_sum=amount,
inv_id=inv_id,
description="пополнение баланса",
id=f"{tg_id}",
)
logger.info(f"Generated payment link: {payment_link}")
return payment_link
@router.callback_query(F.data == "pay_robokassa")
async def process_callback_pay_robokassa(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
tg_id = callback_query.message.chat.id
logger.info(f"User {tg_id} initiated Robokassa payment.")
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_robokassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
key_count = await get_key_count(tg_id)
if key_count == 0:
exists = await check_connection_exists(tg_id)
if not exists:
await add_connection(tg_id, balance=0.0, trial=0, session=session)
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
await callback_query.message.answer(
text="Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
logger.info(f"Displayed amount selection for user {tg_id}.")
@router.callback_query(F.data.startswith("robokassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
logger.info(f"Получены данные callback_data: {callback_query.data}")
data = callback_query.data.split("|")
if len(data) != 3 or data[1] != "amount":
logger.error("Ошибка: callback_data не соответствует формату.")
await callback_query.message.answer("Ошибка: данные повреждены.")
return
amount_str = data[2]
try:
amount = int(amount_str)
if amount <= 0:
raise ValueError("Сумма должна быть положительным числом.")
except ValueError as e:
logger.error(f"Некорректное значение суммы: {amount_str}. Ошибка: {e}")
await callback_query.message.answer("Некорректная сумма.")
return
await state.update_data(amount=amount)
logger.info(f"User {callback_query.message.chat.id} selected amount: {amount}.")
inv_id = 0
tg_id = callback_query.message.chat.id
payment_url = generate_payment_link(amount, inv_id, "Пополнение баланса", tg_id)
logger.info(f"Payment URL for user {callback_query.message.chat.id}: {payment_url}")
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Оплатить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa")],
]
)
await callback_query.message.answer(
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
reply_markup=confirm_keyboard,
)
logger.info(f"Payment link sent to user {callback_query.message.chat.id}.")
async def robokassa_webhook(request):
"""Обработка webhook-уведомлений от Robokassa с учетом shp_id."""
try:
params = await request.post()
logger.info(f"Received webhook params: {params}")
amount = params.get("OutSum")
inv_id = params.get("InvId")
shp_id = params.get("shp_id")
signature_value = params.get("SignatureValue")
logger.info(
f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}"
)
if not check_payment_signature(params):
logger.error("Неверная подпись или данные запроса.")
return web.Response(status=400)
if not amount or not inv_id or not shp_id:
logger.error("Отсутствуют обязательные параметры.")
return web.Response(status=400)
tg_id = shp_id
logger.info(f"Processing payment for user {tg_id} with amount {amount}.")
await update_balance(int(tg_id), float(amount))
await send_payment_success_notification(tg_id, float(amount))
await add_payment(int(tg_id), float(amount), "robokassa")
logger.info(f"Payment successful. Balance updated for user {tg_id}.")
return web.Response(text=f"OK{inv_id}")
except Exception as e:
logger.error(f"Error processing webhook: {e}")
return web.Response(status=500)
def check_payment_signature(params):
"""Проверка подписи запроса от Robokassa с учетом shp_id."""
out_sum = params.get("OutSum")
inv_id = params.get("InvId")
signature_value = params.get("SignatureValue")
shp_id = params.get("shp_id")
signature_string = f"{out_sum}:{inv_id}:{ROBOKASSA_PASSWORD2}:shp_id={shp_id}"
logger.info(f"Signature string before hashing: {signature_string}")
expected_signature = (
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
)
logger.info(f"Expected signature: {expected_signature}")
logger.info(f"Received signature: {signature_value}")
return signature_value.upper() == expected_signature.upper()
@router.callback_query(F.data == "enter_custom_amount_robokassa")
async def process_custom_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
tg_id = callback_query.message.chat.id
logger.info(f"User {tg_id} chose to enter a custom amount.")
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_robokassa"))
await callback_query.message.answer(
"Пожалуйста, введите сумму пополнения.",
reply_markup=builder.as_markup(),
)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_robokassa
)
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
async def handle_custom_amount_input(message: types.Message, state: FSMContext):
tg_id = message.chat.id
logger.info(f"User {tg_id} entered custom amount: {message.text}")
inv_id = 0
try:
amount = int(message.text)
if amount <= 0:
raise ValueError("Сумма должна быть положительным числом.")
await state.update_data(amount=amount)
payment_url = generate_payment_link(amount, inv_id, "Пополнение баланса", tg_id)
logger.info(f"Generated payment link for user {tg_id}: {payment_url}")
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Оплатить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa")],
]
)
await message.answer(
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
reply_markup=confirm_keyboard,
)
await state.clear()
except ValueError as e:
logger.error(f"Некорректная сумма от пользователя {tg_id}: {e}")
await message.answer(
text="Введите корректную сумму в рублях (целое положительное число)."
)
File diff suppressed because it is too large Load Diff
Binary file not shown.
-213
View File
@@ -1,213 +0,0 @@
from typing import Any
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import RUB_TO_XTR
from database import (
add_connection,
add_payment,
check_connection_exists,
get_key_count,
update_balance,
)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
router = Router()
class ReplenishBalanceState(StatesGroup):
choosing_amount_stars = State()
waiting_for_payment_confirmation_stars = State()
entering_custom_amount_stars = State()
@router.callback_query(F.data == "pay_stars")
async def process_callback_pay_stars(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
tg_id = callback_query.message.chat.id
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
)
)
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'stars_{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'stars_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'stars_{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_stars",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
key_count = await get_key_count(tg_id)
if key_count == 0:
exists = await check_connection_exists(tg_id)
if not exists:
await add_connection(tg_id, balance=0.0, trial=0, session=session)
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Не удалось удалить сообщение: {e}")
await callback_query.message.answer(
text="Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_stars)
@router.callback_query(F.data.startswith("stars_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
data = callback_query.data.split("|", 1)
if len(data) != 2:
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await callback_query.message.answer("Неверные данные для выбора суммы.")
return
amount_str = data[1]
try:
amount = int(amount_str)
except ValueError:
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await callback_query.message.answer("Некорректная сумма.")
return
await state.update_data(amount=amount)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_stars)
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", pay=True),
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
)
await callback_query.message.answer_invoice(
title=f"Вы выбрали пополнение на {amount} рублей.",
description=f"Вы выбрали пополнение на {amount} рублей.",
prices=[LabeledPrice(label="XTR", amount=int(amount // RUB_TO_XTR))],
provider_token="",
payload=f"{amount}_stars",
currency="XTR",
reply_markup=builder.as_markup(),
)
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
await callback_query.message.answer("Произошла ошибка при создании платежа.")
@router.callback_query(F.data == "enter_custom_amount_stars")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_stars"))
await callback_query.message.answer(
"Пожалуйста, введите сумму пополнения.",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.entering_custom_amount_stars)
@router.message(ReplenishBalanceState.entering_custom_amount_stars)
async def process_custom_amount_input(message: types.Message, state: FSMContext):
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_XTR <= 0:
await message.answer(
f"Сумма должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
)
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_stars
)
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", pay=True),
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
)
await message.answer_invoice(
title=f"Вы выбрали пополнение на {amount} рублей.",
description=f"Вы выбрали пополнение на {amount} рублей.",
prices=[LabeledPrice(label="XTR", amount=int(amount // RUB_TO_XTR))],
provider_token="",
payload=f"{amount}_stars",
currency="XTR",
reply_markup=builder.as_markup(),
)
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
await message.answer("Произошла ошибка при создании платежа.")
else:
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
@router.pre_checkout_query()
async def on_pre_checkout_query(pre_checkout_query: PreCheckoutQuery):
await pre_checkout_query.answer(ok=True)
@router.message(F.successful_payment)
async def on_successful_payment(
message: types.Message,
):
try:
user_id = int(message.chat.id)
amount = float(message.successful_payment.invoice_payload.split("_")[0])
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await add_payment(int(user_id), float(amount), "stars")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
logger.error(f"Ошибка конвертации user_id или amount: {e}")
File diff suppressed because it is too large Load Diff
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot import bot
from logger import logger
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
await bot.send_message(
chat_id=user_id,
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
reply_markup=builder.as_markup(),
)
except Exception as e:
logger.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
File diff suppressed because it is too large Load Diff
Binary file not shown.
-265
View File
@@ -1,265 +0,0 @@
import uuid
from typing import Any
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from config import YOOKASSA_ENABLE, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
from yookassa import Configuration, Payment
from database import (
add_connection,
add_payment,
check_connection_exists,
get_key_count,
update_balance,
)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
router = Router()
if YOOKASSA_ENABLE:
Configuration.account_id = YOOKASSA_SHOP_ID
Configuration.secret_key = YOOKASSA_SECRET_KEY
logger.debug(f"Account ID: {YOOKASSA_SHOP_ID}")
logger.debug(f"Secret Key: {YOOKASSA_SECRET_KEY}")
class ReplenishBalanceState(StatesGroup):
choosing_amount_yookassa = State()
waiting_for_payment_confirmation_yookassa = State()
entering_custom_amount_yookassa = State()
@router.callback_query(F.data == "pay_yookassa")
async def process_callback_pay_yookassa(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
tg_id = callback_query.message.chat.id
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'yookassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'yookassa_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'yookassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_yookassa",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
key_count = await get_key_count(tg_id)
if key_count == 0:
exists = await check_connection_exists(tg_id)
if not exists:
await add_connection(tg_id, balance=0.0, trial=0, session=session)
await callback_query.message.answer(
text="Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_yookassa)
@router.callback_query(F.data.startswith("yookassa_amount|"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
data = callback_query.data.split("|", 1)
if len(data) != 2:
return
amount_str = data[1]
try:
amount = int(amount_str)
except ValueError:
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
)
# state_data = await state.get_data()
customer_name = callback_query.from_user.full_name
customer_id = callback_query.message.chat.id
customer_email = f"{customer_id}@solo.net"
payment = Payment.create(
{
"amount": {"value": str(amount), "currency": "RUB"},
"confirmation": {
"type": "redirect",
"return_url": "https://pocomacho.ru/success.html",
},
"capture": True,
"description": "Пополнение баланса",
"receipt": {
"customer": {
"full_name": customer_name,
"email": customer_email,
"phone": "79000000000",
},
"items": [
{
"description": "Пополнение баланса",
"quantity": "1.00",
"amount": {"value": str(amount), "currency": "RUB"},
"vat_code": 1,
}
],
},
"metadata": {"user_id": customer_id},
},
uuid.uuid4(),
)
if payment["status"] == "pending":
payment_url = payment["confirmation"]["confirmation_url"]
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
await callback_query.message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=confirm_keyboard,
)
else:
await callback_query.message.answer("Ошибка при создании платежа.")
async def yookassa_webhook(request):
event = await request.json()
logger.debug(f"Webhook event received: {event}")
if event["event"] == "payment.succeeded":
user_id_str = event["object"]["metadata"]["user_id"]
amount_str = event["object"]["amount"]["value"]
try:
user_id = int(user_id_str)
amount = float(amount_str)
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await add_payment(int(user_id), float(amount), "yookassa")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
logger.error(f"Ошибка конвертации user_id или amount: {e}")
return web.Response(status=400)
return web.Response(status=200)
@router.callback_query(F.data == "enter_custom_amount_yookassa")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_yookassa"))
await callback_query.message.answer(
"Пожалуйста, введите сумму пополнения.",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.entering_custom_amount_yookassa)
@router.message(ReplenishBalanceState.entering_custom_amount_yookassa)
async def process_custom_amount_input(message: types.Message, state: FSMContext):
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
await message.answer(
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
)
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
)
try:
payment = Payment.create(
{
"amount": {"value": str(amount), "currency": "RUB"},
"confirmation": {
"type": "redirect",
"return_url": "https://pocomacho.ru/success.html",
},
"capture": True,
"description": "Пополнение баланса",
"receipt": {
"customer": {
"full_name": message.from_user.full_name,
"email": f"{message.chat.id}@solo.net",
"phone": "79000000000",
},
"items": [
{
"description": "Пополнение баланса",
"quantity": "1.00",
"amount": {
"value": str(amount),
"currency": "RUB",
},
"vat_code": 1,
}
],
},
"metadata": {"user_id": message.chat.id},
},
uuid.uuid4(),
)
if payment["status"] == "pending":
payment_url = payment["confirmation"]["confirmation_url"]
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
await message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=confirm_keyboard,
)
else:
await message.answer("Ошибка при создании платежа.")
except Exception as e:
logger.error(f"Ошибка при создании платежа: {e}")
await message.answer("Произошла ошибка при создании платежа.")
else:
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
File diff suppressed because it is too large Load Diff
Binary file not shown.
-206
View File
@@ -1,206 +0,0 @@
import uuid
from typing import Any
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
from config import YOOMONEY_ENABLE, YOOMONEY_ID, YOOMONEY_SECRET_KEY
from database import (
add_connection,
add_payment,
check_connection_exists,
get_key_count,
update_balance,
)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
import hashlib
router = Router()
if YOOMONEY_ENABLE:
logger.debug(f"Account ID: {YOOMONEY_ID}")
class ReplenishBalanceState(StatesGroup):
choosing_amount_yoomoney = State()
waiting_for_payment_confirmation_yoomoney = State()
entering_custom_amount_yoomoney = State()
@router.callback_query(F.data == "pay_yoomoney")
async def process_callback_pay_yoomoney(
callback_query: types.CallbackQuery, state: FSMContext, session: Any
):
tg_id = callback_query.message.chat.id
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'yoomoney_{PAYMENT_OPTIONS[i]["callback_data"]}',
),
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i + 1]["text"],
callback_data=f'yoomoney_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
),
)
else:
builder.row(
InlineKeyboardButton(
text=PAYMENT_OPTIONS[i]["text"],
callback_data=f'yoomoney_{PAYMENT_OPTIONS[i]["callback_data"]}',
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму",
callback_data="enter_custom_amount_yoomoney",
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
key_count = await get_key_count(tg_id)
if key_count == 0:
exists = await check_connection_exists(tg_id)
if not exists:
await add_connection(tg_id, balance=0.0, trial=0, session=session)
await callback_query.message.answer(
text="Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.choosing_amount_yoomoney)
@router.callback_query(F.data.startswith("yoomoney_amount"))
async def process_amount_selection(
callback_query: types.CallbackQuery, state: FSMContext
):
data = callback_query.data.split("|", 1)
if len(data) != 2:
return
amount_str = data[1]
try:
amount = int(amount_str)
except ValueError:
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yoomoney
)
# state_data = await state.get_data()
#customer_name = callback_query.from_user.full_name
customer_id = callback_query.message.chat.id
account_id = YOOMONEY_ID
payment_url = f"https://yoomoney.ru/quickpay/confirm.xml?receiver={account_id}&quickpay-form=shop&targets=&sum={amount}&paymentType=PC&comment=Пополнение баланса&label={customer_id}"
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
await callback_query.message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=confirm_keyboard,
)
async def yoomoney_webhook(request: web.Request):
data = await request.post()
logger.debug(f"Webhook event received: {data}")
user_id_str = data.get("label")
amount_str = data.get("withdraw_amount")
notification_secret = YOOMONEY_SECRET_KEY
sha1_hash = data.get("sha1_hash")
# Строим строку для хэширования
string_to_hash = f"{data.get('notification_type')}&{data.get('operation_id')}&{data.get('amount')}&{data.get('currency')}&{data.get('datetime')}&{data.get('sender')}&{data.get('codepro')}&{notification_secret}&{user_id_str}"
# Высчитываем хэш
calculated_hash = hashlib.sha1(string_to_hash.encode('utf-8')).hexdigest()
# Проверяем хэш
if calculated_hash != sha1_hash:
logger.error("Проверка хэша не пройдена")
return web.Response(status=400)
try:
user_id = int(user_id_str)
amount = float(amount_str)
logger.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
await add_payment(user_id, amount, "yoomoney")
await update_balance(user_id, amount)
await send_payment_success_notification(user_id, amount)
except ValueError as e:
logger.error(f"Ошибка конвертации user_id или amount: {e}")
return web.Response(status=400)
return web.Response(status=200)
@router.callback_query(F.data == "enter_custom_amount_yoomoney")
async def process_enter_custom_amount(
callback_query: types.CallbackQuery, state: FSMContext
):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_yoomoney"))
await callback_query.message.answer(
"Пожалуйста, введите сумму пополнения.",
reply_markup=builder.as_markup(),
)
await state.set_state(ReplenishBalanceState.entering_custom_amount_yoomoney)
@router.message(ReplenishBalanceState.entering_custom_amount_yoomoney)
async def process_custom_amount_input(message: types.Message, state: FSMContext):
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
await message.answer(
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
)
return
await state.update_data(amount=amount)
await state.set_state(
ReplenishBalanceState.waiting_for_payment_confirmation_yoomoney
)
customer_id = message.chat.id
account_id = YOOMONEY_ID
payment_url = f"https://yoomoney.ru/quickpay/confirm.xml?receiver={account_id}&quickpay-form=shop&targets=&sum={amount}&paymentType=PC&comment=Пополнение баланса&label={customer_id}"
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
await message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
reply_markup=confirm_keyboard,
)
else:
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
+7 -3
View File
@@ -4,8 +4,8 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import NEWS_MESSAGE, RENEWAL_PLANS
from config import NEWS_MESSAGE, RENEWAL_PLANS
from database import get_balance, get_key_count, get_referral_stats
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
@@ -42,11 +42,15 @@ async def process_callback_view_profile(
callback_data="pay",
)
)
builder.row()
builder.row(
InlineKeyboardButton(text="👥 Пригласить друзей", callback_data="invite"),
InlineKeyboardButton(text="👥 Пригласить", callback_data="invite"),
InlineKeyboardButton(text="🎁 Подарить", callback_data="gifts"),
)
builder.row(
InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"),
InlineKeyboardButton(text="📘 Инструкции", callback_data="instructions"),
)
builder.row(InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"))
if admin:
builder.row(
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
+123 -24
View File
@@ -12,11 +12,21 @@ from aiogram.types import (
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import CHANNEL_URL, CHANNEL_EXISTS, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, SUPPORT_CHAT_URL,DONATIONS_ENABLE
from config import (
CHANNEL_EXISTS,
CHANNEL_URL,
CONNECT_ANDROID,
CONNECT_IOS,
DONATIONS_ENABLE,
DOWNLOAD_ANDROID,
DOWNLOAD_IOS,
SUPPORT_CHAT_URL,
)
from database import add_connection, add_referral, check_connection_exists, get_trial, use_trial
from handlers.keys.key_management import create_key
from handlers.keys.trial_key import create_trial_key
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
from logger import logger
router = Router()
@@ -30,40 +40,129 @@ async def handle_start_callback_query(
@router.message(Command("start"))
async def start_command(message: Message, state: FSMContext, session: Any, admin: bool):
"""Обрабатывает команду /start, включает логику рефералов и подарков."""
logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}")
await state.clear()
if message.text:
try:
referrer_tg_id = int(message.text.split("referral_")[1])
await add_referral(message.chat.id, referrer_tg_id, session)
except (ValueError, IndexError):
pass
connection_exists = await check_connection_exists(message.chat.id)
if not connection_exists:
await add_connection(tg_id=message.chat.id, session=session)
connection_exists = await check_connection_exists(message.chat.id)
logger.info(f"Проверка существования подключения: {connection_exists}")
if not connection_exists:
await add_connection(tg_id=message.chat.id, session=session)
logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.")
if "gift_" in message.text:
logger.info(f"Обнаружена ссылка на подарок: {message.text}")
parts = message.text.split("gift_")[1].split("_")
gift_id = parts[0]
recipient_tg_id = message.chat.id
gift_info = await session.fetchrow(
"SELECT * FROM gifts WHERE gift_id = $1 AND is_used = FALSE", gift_id
)
if gift_info is None:
logger.warning(f"Подарок с ID {gift_id} уже был использован или не существует.")
await message.answer("Этот подарок уже был использован или не существует.")
return await show_start_menu(message, admin, session)
if gift_info['sender_tg_id'] == recipient_tg_id:
logger.warning(
f"Пользователь {recipient_tg_id} попытался активировать подарок, который был отправлен им самим."
)
await message.answer("❌ Вы не можете получить подарок от самого себя.")
return await show_start_menu(message, admin, session)
selected_months = gift_info['selected_months']
expiry_time = gift_info['expiry_time']
expiry_time_naive = expiry_time.replace(tzinfo=None)
logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.")
await create_key(recipient_tg_id, expiry_time_naive, state, session, message)
logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.")
await session.execute(
"UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2",
recipient_tg_id,
gift_id,
)
await message.answer(
f"🎉 Ваш подарок на {selected_months} {'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
)
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
return
elif "referral_" in message.text:
try:
referrer_tg_id = int(message.text.split("referral_")[1])
if connection_exists:
logger.info(
f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом."
)
await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.")
return await show_start_menu(message, admin, session)
if referrer_tg_id == message.chat.id:
logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.")
await message.answer("❌ Вы не можете быть рефералом самого себя.")
return await show_start_menu(message, admin, session)
existing_referral = await session.fetchrow(
"SELECT * FROM referrals WHERE referred_tg_id = $1", message.chat.id
)
if existing_referral:
logger.info(f"Реферал с ID {message.chat.id} уже существует.")
return await show_start_menu(message, admin, session)
await add_referral(message.chat.id, referrer_tg_id, session)
logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}")
return await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
logger.error(f"Ошибка при обработке реферальной ссылки: {e}")
return
else:
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка.")
await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}")
await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.")
else:
await show_start_menu(message, admin, session)
async def show_start_menu(message: Message, admin: bool, session: Any):
"""Функция для отображения стандартного меню"""
logger.info(f"Показываю главное меню для пользователя {message.chat.id}")
trial_status = await get_trial(message.chat.id, session)
image_path = os.path.join("img", "pic.jpg")
builder = InlineKeyboardBuilder()
if trial_status == 0:
builder.row(
InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn")
)
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
if CHANNEL_EXISTS:
builder.row(
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL),
)
else:
builder.row(
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)
)
builder.row(
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL),
)
if admin:
builder.row(
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
)
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
builder.row(InlineKeyboardButton(text="🌐 О нашем VPN", callback_data="about_vpn"))
if os.path.isfile(image_path):
+1 -1
View File
@@ -2,9 +2,9 @@ import random
import re
import asyncpg
from config import DATABASE_URL
from bot import bot
from config import DATABASE_URL
from database import get_servers_from_db
from logger import logger
+1 -135
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -3,6 +3,7 @@ from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from config import ADMIN_ID
+1
View File
@@ -4,6 +4,7 @@ from typing import Any
import asyncpg
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from config import DATABASE_URL
+1 -1
View File
@@ -5,10 +5,10 @@ from datetime import datetime, timedelta
import asyncpg
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import ADMIN_ID, DATABASE_URL
from ping3 import ping
from bot import bot
from config import ADMIN_ID, DATABASE_URL
from database import get_servers_from_db
from logger import logger