mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-05-04 03:36:24 +00:00
Merge pull request #27 from Fr1ngg/dev
Доработка статистики рефералов, плюс уведомления для рефералов и приглашаюших
This commit is contained in:
@@ -66,6 +66,10 @@ class Settings(BaseSettings):
|
||||
REFERRAL_FIRST_TOPUP_BONUS_KOPEKS: int = 10000
|
||||
REFERRAL_INVITER_BONUS_KOPEKS: int = 10000
|
||||
REFERRAL_COMMISSION_PERCENT: int = 25
|
||||
|
||||
REFERRAL_NOTIFICATIONS_ENABLED: bool = True
|
||||
REFERRAL_NOTIFICATION_RETRY_ATTEMPTS: int = 3
|
||||
REFERRED_USER_REWARD: int = 0
|
||||
|
||||
AUTOPAY_WARNING_DAYS: str = "3,1"
|
||||
|
||||
@@ -297,6 +301,19 @@ class Settings(BaseSettings):
|
||||
|
||||
def rubles_to_stars(self, rubles: float) -> int:
|
||||
return max(1, int(rubles / self.get_stars_rate()))
|
||||
|
||||
def get_referral_settings(self) -> Dict:
|
||||
return {
|
||||
"minimum_topup_kopeks": self.REFERRAL_MINIMUM_TOPUP_KOPEKS,
|
||||
"first_topup_bonus_kopeks": self.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS,
|
||||
"inviter_bonus_kopeks": self.REFERRAL_INVITER_BONUS_KOPEKS,
|
||||
"commission_percent": self.REFERRAL_COMMISSION_PERCENT,
|
||||
"notifications_enabled": self.REFERRAL_NOTIFICATIONS_ENABLED,
|
||||
"referred_user_reward": self.REFERRED_USER_REWARD
|
||||
}
|
||||
|
||||
def is_referral_notifications_enabled(self) -> bool:
|
||||
return self.REFERRAL_NOTIFICATIONS_ENABLED
|
||||
|
||||
def get_traffic_packages(self) -> List[Dict]:
|
||||
import logging
|
||||
|
||||
@@ -126,7 +126,8 @@ async def add_user_balance(
|
||||
user: User,
|
||||
amount_kopeks: int,
|
||||
description: str = "Пополнение баланса",
|
||||
create_transaction: bool = True
|
||||
create_transaction: bool = True,
|
||||
bot = None
|
||||
) -> bool:
|
||||
try:
|
||||
old_balance = user.balance_kopeks
|
||||
@@ -157,7 +158,7 @@ async def add_user_balance(
|
||||
if has_topup_keywords and not has_exclude_keywords:
|
||||
try:
|
||||
from app.services.referral_service import process_referral_topup
|
||||
await process_referral_topup(db, user.id, amount_kopeks)
|
||||
await process_referral_topup(db, user.id, amount_kopeks, bot)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки реферального пополнения: {e}")
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@ from aiogram import Dispatcher, types, F
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database.crud.referral import get_referral_earnings_sum
|
||||
from app.database.models import User
|
||||
from app.keyboards.inline import get_referral_keyboard, get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.utils.user_utils import get_user_referral_summary
|
||||
from app.utils.user_utils import get_user_referral_summary, get_detailed_referral_list, get_referral_analytics
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,30 +26,58 @@ async def show_referral_info(
|
||||
referral_text = f"👥 <b>Реферальная программа</b>\n\n"
|
||||
|
||||
referral_text += f"📊 <b>Ваша статистика:</b>\n"
|
||||
referral_text += f"• Приглашено пользователей: {summary['invited_count']}\n"
|
||||
referral_text += f"• Сделали первое пополнение: {summary['paid_referrals_count']}\n"
|
||||
referral_text += f"• Заработано всего: {texts.format_price(summary['total_earned_kopeks'])}\n"
|
||||
referral_text += f"• За последний месяц: {texts.format_price(summary['month_earned_kopeks'])}\n\n"
|
||||
referral_text += f"• Приглашено пользователей: <b>{summary['invited_count']}</b>\n"
|
||||
referral_text += f"• Сделали первое пополнение: <b>{summary['paid_referrals_count']}</b>\n"
|
||||
referral_text += f"• Активных рефералов: <b>{summary['active_referrals_count']}</b>\n"
|
||||
referral_text += f"• Конверсия: <b>{summary['conversion_rate']}%</b>\n"
|
||||
referral_text += f"• Заработано всего: <b>{texts.format_price(summary['total_earned_kopeks'])}</b>\n"
|
||||
referral_text += f"• За последний месяц: <b>{texts.format_price(summary['month_earned_kopeks'])}</b>\n\n"
|
||||
|
||||
referral_text += f"🎁 <b>Как работают награды:</b>\n"
|
||||
referral_text += f"• Новый пользователь получает: {texts.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS)} при первом пополнении от {texts.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS)}\n"
|
||||
referral_text += f"• Вы получаете при первом пополнении реферала: {texts.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)}\n"
|
||||
referral_text += f"• Комиссия с каждого пополнения реферала: {settings.REFERRAL_COMMISSION_PERCENT}%\n\n"
|
||||
referral_text += f"• Новый пользователь получает: <b>{texts.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS)}</b> при первом пополнении от <b>{texts.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS)}</b>\n"
|
||||
referral_text += f"• Вы получаете при первом пополнении реферала: <b>{texts.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)}</b>\n"
|
||||
referral_text += f"• Комиссия с каждого пополнения реферала: <b>{settings.REFERRAL_COMMISSION_PERCENT}%</b>\n\n"
|
||||
|
||||
referral_text += f"🔗 <b>Ваша реферальная ссылка:</b>\n"
|
||||
referral_text += f"<code>{referral_link}</code>\n\n"
|
||||
referral_text += f"🆔 <b>Ваш код:</b> <code>{db_user.referral_code}</code>\n\n"
|
||||
|
||||
if summary['recent_earnings']:
|
||||
referral_text += f"💰 <b>Последние начисления:</b>\n"
|
||||
for earning in summary['recent_earnings'][:3]:
|
||||
reason_text = {
|
||||
"referral_first_topup": "🎉 Первое пополнение",
|
||||
"referral_commission_topup": "💰 Комиссия с пополнения",
|
||||
"referral_registration_pending": "⏳ Ожидание пополнения"
|
||||
}.get(earning['reason'], earning['reason'])
|
||||
|
||||
referral_text += f"• {reason_text}: {texts.format_price(earning['amount_kopeks'])} от {earning['referral_name']}\n"
|
||||
meaningful_earnings = [
|
||||
earning for earning in summary['recent_earnings'][:5]
|
||||
if earning['amount_kopeks'] > 0
|
||||
]
|
||||
|
||||
if meaningful_earnings:
|
||||
referral_text += f"💰 <b>Последние начисления:</b>\n"
|
||||
for earning in meaningful_earnings[:3]:
|
||||
reason_text = {
|
||||
"referral_first_topup": "🎉 Первое пополнение",
|
||||
"referral_commission_topup": "💰 Комиссия с пополнения",
|
||||
"referral_commission": "💰 Комиссия с покупки"
|
||||
}.get(earning['reason'], earning['reason'])
|
||||
|
||||
referral_text += f"• {reason_text}: <b>{texts.format_price(earning['amount_kopeks'])}</b> от {earning['referral_name']}\n"
|
||||
referral_text += "\n"
|
||||
|
||||
if summary['earnings_by_type']:
|
||||
referral_text += f"📈 <b>Доходы по типам:</b>\n"
|
||||
|
||||
if 'referral_first_topup' in summary['earnings_by_type']:
|
||||
data = summary['earnings_by_type']['referral_first_topup']
|
||||
if data['total_amount_kopeks'] > 0:
|
||||
referral_text += f"• Бонусы за первые пополнения: <b>{data['count']}</b> ({texts.format_price(data['total_amount_kopeks'])})\n"
|
||||
|
||||
if 'referral_commission_topup' in summary['earnings_by_type']:
|
||||
data = summary['earnings_by_type']['referral_commission_topup']
|
||||
if data['total_amount_kopeks'] > 0:
|
||||
referral_text += f"• Комиссии с пополнений: <b>{data['count']}</b> ({texts.format_price(data['total_amount_kopeks'])})\n"
|
||||
|
||||
if 'referral_commission' in summary['earnings_by_type']:
|
||||
data = summary['earnings_by_type']['referral_commission']
|
||||
if data['total_amount_kopeks'] > 0:
|
||||
referral_text += f"• Комиссии с покупок: <b>{data['count']}</b> ({texts.format_price(data['total_amount_kopeks'])})\n"
|
||||
|
||||
referral_text += "\n"
|
||||
|
||||
referral_text += "📢 Приглашайте друзей и зарабатывайте!"
|
||||
@@ -63,6 +90,111 @@ async def show_referral_info(
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def show_detailed_referral_list(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
page: int = 1
|
||||
):
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
referrals_data = await get_detailed_referral_list(db, db_user.id, limit=10, offset=(page - 1) * 10)
|
||||
|
||||
if not referrals_data['referrals']:
|
||||
await callback.message.edit_text(
|
||||
"📋 У вас пока нет рефералов.\n\nПоделитесь своей реферальной ссылкой, чтобы начать зарабатывать!",
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]
|
||||
])
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
text = f"👥 <b>Ваши рефералы</b> (стр. {referrals_data['current_page']}/{referrals_data['total_pages']})\n\n"
|
||||
|
||||
for i, referral in enumerate(referrals_data['referrals'], 1):
|
||||
status_emoji = "🟢" if referral['status'] == 'active' else "🔴"
|
||||
|
||||
topup_emoji = "💰" if referral['has_made_first_topup'] else "⏳"
|
||||
|
||||
text += f"{i}. {status_emoji} <b>{referral['full_name']}</b>\n"
|
||||
text += f" {topup_emoji} Пополнений: {referral['topups_count']}\n"
|
||||
text += f" 💎 Заработано с него: {texts.format_price(referral['total_earned_kopeks'])}\n"
|
||||
text += f" 📅 Регистрация: {referral['days_since_registration']} дн. назад\n"
|
||||
|
||||
if referral['days_since_activity'] is not None:
|
||||
text += f" 🕐 Активность: {referral['days_since_activity']} дн. назад\n"
|
||||
else:
|
||||
text += f" 🕐 Активность: давно\n"
|
||||
|
||||
text += "\n"
|
||||
|
||||
keyboard = []
|
||||
nav_buttons = []
|
||||
|
||||
if referrals_data['has_prev']:
|
||||
nav_buttons.append(types.InlineKeyboardButton(
|
||||
text="⬅️ Назад",
|
||||
callback_data=f"referral_list_page_{page - 1}"
|
||||
))
|
||||
|
||||
if referrals_data['has_next']:
|
||||
nav_buttons.append(types.InlineKeyboardButton(
|
||||
text="Вперед ➡️",
|
||||
callback_data=f"referral_list_page_{page + 1}"
|
||||
))
|
||||
|
||||
if nav_buttons:
|
||||
keyboard.append(nav_buttons)
|
||||
|
||||
keyboard.append([types.InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
callback_data="menu_referrals"
|
||||
)])
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def show_referral_analytics(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
analytics = await get_referral_analytics(db, db_user.id)
|
||||
|
||||
text = f"📊 <b>Аналитика рефералов</b>\n\n"
|
||||
|
||||
text += f"💰 <b>Доходы по периодам:</b>\n"
|
||||
text += f"• Сегодня: {texts.format_price(analytics['earnings_by_period']['today'])}\n"
|
||||
text += f"• За неделю: {texts.format_price(analytics['earnings_by_period']['week'])}\n"
|
||||
text += f"• За месяц: {texts.format_price(analytics['earnings_by_period']['month'])}\n"
|
||||
text += f"• За квартал: {texts.format_price(analytics['earnings_by_period']['quarter'])}\n\n"
|
||||
|
||||
if analytics['top_referrals']:
|
||||
text += f"🏆 <b>Топ-{len(analytics['top_referrals'])} рефералов:</b>\n"
|
||||
for i, ref in enumerate(analytics['top_referrals'], 1):
|
||||
text += f"{i}. {ref['referral_name']}: {texts.format_price(ref['total_earned_kopeks'])} ({ref['earnings_count']} начислений)\n"
|
||||
text += "\n"
|
||||
|
||||
text += "📈 Продолжайте развивать свою реферальную сеть!"
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text=texts.BACK, callback_data="menu_referrals")]
|
||||
]),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def create_invite_message(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User
|
||||
@@ -80,18 +212,14 @@ async def create_invite_message(
|
||||
invite_text += f"👇 Переходи по ссылке:\n{referral_link}"
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="📤 Поделиться",
|
||||
switch_inline_query=invite_text
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
callback_data="menu_referrals"
|
||||
)
|
||||
]
|
||||
[types.InlineKeyboardButton(
|
||||
text="📤 Поделиться",
|
||||
switch_inline_query=invite_text
|
||||
)],
|
||||
[types.InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
callback_data="menu_referrals"
|
||||
)]
|
||||
])
|
||||
|
||||
await callback.message.edit_text(
|
||||
@@ -115,3 +243,20 @@ def register_handlers(dp: Dispatcher):
|
||||
create_invite_message,
|
||||
F.data == "referral_create_invite"
|
||||
)
|
||||
|
||||
dp.callback_query.register(
|
||||
show_detailed_referral_list,
|
||||
F.data == "referral_list"
|
||||
)
|
||||
|
||||
dp.callback_query.register(
|
||||
show_referral_analytics,
|
||||
F.data == "referral_analytics"
|
||||
)
|
||||
|
||||
dp.callback_query.register(
|
||||
lambda callback, db_user, db: show_detailed_referral_list(
|
||||
callback, db_user, db, int(callback.data.split('_')[-1])
|
||||
),
|
||||
F.data.startswith("referral_list_page_")
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from aiogram import Dispatcher, types, F
|
||||
from aiogram import Dispatcher, types, F, Bot
|
||||
from aiogram.filters import Command, StateFilter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -18,6 +18,7 @@ from app.localization.texts import get_texts
|
||||
from app.services.referral_service import process_referral_registration
|
||||
from app.utils.user_utils import generate_unique_referral_code
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -386,7 +387,6 @@ async def complete_registration_from_callback(
|
||||
state: FSMContext,
|
||||
db: AsyncSession
|
||||
):
|
||||
|
||||
logger.info(f"🏁 COMPLETE: Завершение регистрации для пользователя {callback.from_user.id}")
|
||||
|
||||
existing_user = await get_user_by_telegram_id(db, callback.from_user.id)
|
||||
@@ -497,9 +497,8 @@ async def complete_registration_from_callback(
|
||||
|
||||
if referrer_id:
|
||||
try:
|
||||
await process_referral_registration(db, user.id, referrer_id)
|
||||
bonus_message = f"🎉 Вы получили {settings.REFERRED_USER_REWARD/100}₽ за регистрацию по реферальной ссылке!"
|
||||
await callback.message.answer(bonus_message)
|
||||
await process_referral_registration(db, user.id, referrer_id, callback.bot)
|
||||
logger.info(f"✅ Реферальная регистрация обработана для {user.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке реферальной регистрации: {e}")
|
||||
|
||||
@@ -557,13 +556,11 @@ async def complete_registration_from_callback(
|
||||
|
||||
logger.info(f"✅ Регистрация завершена для пользователя: {user_telegram_id}")
|
||||
|
||||
|
||||
async def complete_registration(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
db: AsyncSession
|
||||
):
|
||||
|
||||
logger.info(f"🏁 COMPLETE: Завершение регистрации для пользователя {message.from_user.id}")
|
||||
|
||||
existing_user = await get_user_by_telegram_id(db, message.from_user.id)
|
||||
@@ -674,9 +671,8 @@ async def complete_registration(
|
||||
|
||||
if referrer_id:
|
||||
try:
|
||||
await process_referral_registration(db, user.id, referrer_id)
|
||||
bonus_message = f"🎉 Вы получили {settings.REFERRED_USER_REWARD/100}₽ за регистрацию по реферальной ссылке!"
|
||||
await message.answer(bonus_message)
|
||||
await process_referral_registration(db, user.id, referrer_id, message.bot)
|
||||
logger.info(f"✅ Реферальная регистрация обработана для {user.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке реферальной регистрации: {e}")
|
||||
|
||||
|
||||
@@ -525,14 +525,35 @@ def get_subscription_expiring_keyboard(subscription_id: int, language: str = "ru
|
||||
|
||||
def get_referral_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
|
||||
texts = get_texts(language)
|
||||
return InlineKeyboardMarkup(inline_keyboard=[
|
||||
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(text=texts.CREATE_INVITE, callback_data="referral_create_invite")
|
||||
InlineKeyboardButton(
|
||||
text="📝 Создать приглашение",
|
||||
callback_data="referral_create_invite"
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text=texts.BACK, callback_data="back_to_menu")
|
||||
InlineKeyboardButton(
|
||||
text="👥 Список рефералов",
|
||||
callback_data="referral_list"
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="📊 Аналитика",
|
||||
callback_data="referral_analytics"
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=texts.BACK,
|
||||
callback_data="back_to_menu"
|
||||
)
|
||||
]
|
||||
])
|
||||
]
|
||||
|
||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||
|
||||
|
||||
def get_support_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
|
||||
|
||||
@@ -67,7 +67,6 @@ class PaymentService:
|
||||
rubles_amount = TelegramStarsService.calculate_rubles_from_stars(stars_amount)
|
||||
amount_kopeks = int(rubles_amount * 100)
|
||||
|
||||
# Создаем транзакцию
|
||||
transaction = await create_transaction(
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
@@ -97,7 +96,7 @@ class PaymentService:
|
||||
logger.info(f"📞 Вызов process_referral_topup для пользователя {user_id}")
|
||||
try:
|
||||
from app.services.referral_service import process_referral_topup
|
||||
await process_referral_topup(db, user_id, amount_kopeks)
|
||||
await process_referral_topup(db, user_id, amount_kopeks, self.bot)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки реферального пополнения: {e}")
|
||||
else:
|
||||
@@ -268,7 +267,18 @@ class PaymentService:
|
||||
|
||||
user = await get_user_by_id(db, updated_payment.user_id)
|
||||
if user:
|
||||
await add_user_balance(db, user, updated_payment.amount_kopeks, f"Пополнение YooKassa: {updated_payment.amount_kopeks/100:.2f}₽")
|
||||
old_balance = user.balance_kopeks
|
||||
user.balance_kopeks += updated_payment.amount_kopeks
|
||||
user.updated_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
try:
|
||||
from app.services.referral_service import process_referral_topup
|
||||
await process_referral_topup(db, user.id, updated_payment.amount_kopeks, self.bot)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки реферального пополнения YooKassa: {e}")
|
||||
|
||||
if self.bot:
|
||||
try:
|
||||
@@ -276,7 +286,7 @@ class PaymentService:
|
||||
user.telegram_id,
|
||||
f"✅ <b>Пополнение успешно!</b>\n\n"
|
||||
f"💰 Сумма: {settings.format_price(updated_payment.amount_kopeks)}\n"
|
||||
f"🦐 Способ: Банковская карта\n"
|
||||
f"🏦 Способ: Банковская карта\n"
|
||||
f"🆔 Транзакция: {yookassa_payment_id[:8]}...\n\n"
|
||||
f"Баланс пополнен автоматически!",
|
||||
parse_mode="HTML"
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
import logging
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete
|
||||
from aiogram import Bot
|
||||
|
||||
from app.config import settings
|
||||
from app.database.crud.user import add_user_balance, get_user_by_id
|
||||
from app.database.crud.referral import create_referral_earning
|
||||
from app.database.models import TransactionType
|
||||
from app.database.models import TransactionType, ReferralEarning
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def send_referral_notification(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
message: str
|
||||
):
|
||||
try:
|
||||
await bot.send_message(user_id, message, parse_mode="HTML")
|
||||
logger.info(f"✅ Уведомление отправлено пользователю {user_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка отправки уведомления пользователю {user_id}: {e}")
|
||||
|
||||
|
||||
async def process_referral_registration(
|
||||
db: AsyncSession,
|
||||
new_user_id: int,
|
||||
referrer_id: int
|
||||
referrer_id: int,
|
||||
bot: Bot = None
|
||||
):
|
||||
try:
|
||||
new_user = await get_user_by_id(db, new_user_id)
|
||||
@@ -34,6 +49,25 @@ async def process_referral_registration(
|
||||
reason="referral_registration_pending"
|
||||
)
|
||||
|
||||
if bot:
|
||||
referral_notification = (
|
||||
f"🎉 <b>Добро пожаловать!</b>\n\n"
|
||||
f"Вы перешли по реферальной ссылке пользователя <b>{referrer.full_name}</b>!\n\n"
|
||||
f"💰 При первом пополнении от {settings.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS)} "
|
||||
f"вы получите бонус {settings.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS)}!\n\n"
|
||||
f"🎁 Ваш реферер также получит награду за ваше первое пополнение."
|
||||
)
|
||||
await send_referral_notification(bot, new_user.telegram_id, referral_notification)
|
||||
|
||||
inviter_notification = (
|
||||
f"👥 <b>Новый реферал!</b>\n\n"
|
||||
f"По вашей ссылке зарегистрировался пользователь <b>{new_user.full_name}</b>!\n\n"
|
||||
f"💰 Когда он пополнит баланс от {settings.format_price(settings.REFERRAL_MINIMUM_TOPUP_KOPEKS)}, "
|
||||
f"вы получите {settings.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)}\n\n"
|
||||
f"📈 С каждого последующего пополнения вы будете получать {settings.REFERRAL_COMMISSION_PERCENT}% комиссии."
|
||||
)
|
||||
await send_referral_notification(bot, referrer.telegram_id, inviter_notification)
|
||||
|
||||
logger.info(f"✅ Зарегистрирован реферал {new_user_id} для {referrer_id}. Бонусы будут выданы после пополнения.")
|
||||
return True
|
||||
|
||||
@@ -45,7 +79,8 @@ async def process_referral_registration(
|
||||
async def process_referral_topup(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
topup_amount_kopeks: int
|
||||
topup_amount_kopeks: int,
|
||||
bot: Bot = None
|
||||
):
|
||||
try:
|
||||
user = await get_user_by_id(db, user_id)
|
||||
@@ -66,17 +101,41 @@ async def process_referral_topup(
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
try:
|
||||
await db.execute(
|
||||
delete(ReferralEarning).where(
|
||||
ReferralEarning.user_id == referrer.id,
|
||||
ReferralEarning.referral_id == user_id,
|
||||
ReferralEarning.reason == "referral_registration_pending"
|
||||
)
|
||||
)
|
||||
await db.commit()
|
||||
logger.info(f"🗑️ Удалена запись 'ожидание пополнения' для реферала {user_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка удаления записи ожидания: {e}")
|
||||
|
||||
if settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS > 0:
|
||||
await add_user_balance(
|
||||
db, user, settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS,
|
||||
f"Бонус за первое пополнение по реферальной программе"
|
||||
f"Бонус за первое пополнение по реферальной программе",
|
||||
bot=bot
|
||||
)
|
||||
logger.info(f"💰 Реферал {user_id} получил бонус {settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS/100}₽")
|
||||
|
||||
if bot:
|
||||
bonus_notification = (
|
||||
f"🎉 <b>Бонус получен!</b>\n\n"
|
||||
f"За первое пополнение вы получили бонус "
|
||||
f"{settings.format_price(settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS)}!\n\n"
|
||||
f"💎 Средства зачислены на ваш баланс."
|
||||
)
|
||||
await send_referral_notification(bot, user.telegram_id, bonus_notification)
|
||||
|
||||
if settings.REFERRAL_INVITER_BONUS_KOPEKS > 0:
|
||||
await add_user_balance(
|
||||
db, referrer, settings.REFERRAL_INVITER_BONUS_KOPEKS,
|
||||
f"Бонус за первое пополнение реферала {user.full_name}"
|
||||
f"Бонус за первое пополнение реферала {user.full_name}",
|
||||
bot=bot
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
@@ -87,6 +146,16 @@ async def process_referral_topup(
|
||||
reason="referral_first_topup"
|
||||
)
|
||||
logger.info(f"💰 Реферер {referrer.telegram_id} получил бонус {settings.REFERRAL_INVITER_BONUS_KOPEKS/100}₽")
|
||||
|
||||
if bot:
|
||||
inviter_bonus_notification = (
|
||||
f"💰 <b>Реферальная награда!</b>\n\n"
|
||||
f"Ваш реферал <b>{user.full_name}</b> сделал первое пополнение!\n\n"
|
||||
f"🎁 Вы получили награду: {settings.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)}\n\n"
|
||||
f"📈 Теперь с каждого его пополнения вы будете получать {settings.REFERRAL_COMMISSION_PERCENT}% комиссии."
|
||||
)
|
||||
await send_referral_notification(bot, referrer.telegram_id, inviter_bonus_notification)
|
||||
|
||||
else:
|
||||
if settings.REFERRAL_COMMISSION_PERCENT > 0:
|
||||
commission_amount = int(topup_amount_kopeks * settings.REFERRAL_COMMISSION_PERCENT / 100)
|
||||
@@ -94,7 +163,8 @@ async def process_referral_topup(
|
||||
if commission_amount > 0:
|
||||
await add_user_balance(
|
||||
db, referrer, commission_amount,
|
||||
f"Комиссия {settings.REFERRAL_COMMISSION_PERCENT}% с пополнения {user.full_name}"
|
||||
f"Комиссия {settings.REFERRAL_COMMISSION_PERCENT}% с пополнения {user.full_name}",
|
||||
bot=bot
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
@@ -106,6 +176,17 @@ async def process_referral_topup(
|
||||
)
|
||||
|
||||
logger.info(f"💰 Комиссия с пополнения: {referrer.telegram_id} получил {commission_amount/100}₽")
|
||||
|
||||
if bot:
|
||||
commission_notification = (
|
||||
f"💰 <b>Реферальная комиссия!</b>\n\n"
|
||||
f"Ваш реферал <b>{user.full_name}</b> пополнил баланс на "
|
||||
f"{settings.format_price(topup_amount_kopeks)}\n\n"
|
||||
f"🎁 Ваша комиссия ({settings.REFERRAL_COMMISSION_PERCENT}%): "
|
||||
f"{settings.format_price(commission_amount)}\n\n"
|
||||
f"💎 Средства зачислены на ваш баланс."
|
||||
)
|
||||
await send_referral_notification(bot, referrer.telegram_id, commission_notification)
|
||||
|
||||
return True
|
||||
|
||||
@@ -118,7 +199,8 @@ async def process_referral_purchase(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
purchase_amount_kopeks: int,
|
||||
transaction_id: int = None
|
||||
transaction_id: int = None,
|
||||
bot: Bot = None
|
||||
):
|
||||
try:
|
||||
user = await get_user_by_id(db, user_id)
|
||||
@@ -141,7 +223,8 @@ async def process_referral_purchase(
|
||||
if commission_amount > 0:
|
||||
await add_user_balance(
|
||||
db, referrer, commission_amount,
|
||||
f"Комиссия {commission_percent}% с покупки {user.full_name}"
|
||||
f"Комиссия {commission_percent}% с покупки {user.full_name}",
|
||||
bot=bot
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
@@ -154,6 +237,17 @@ async def process_referral_purchase(
|
||||
)
|
||||
|
||||
logger.info(f"💰 Комиссия с покупки: {referrer.telegram_id} получил {commission_amount/100}₽")
|
||||
|
||||
if bot:
|
||||
purchase_commission_notification = (
|
||||
f"💰 <b>Комиссия с покупки!</b>\n\n"
|
||||
f"Ваш реферал <b>{user.full_name}</b> совершил покупку на "
|
||||
f"{settings.format_price(purchase_amount_kopeks)}\n\n"
|
||||
f"🎁 Ваша комиссия ({commission_percent}%): "
|
||||
f"{settings.format_price(commission_amount)}\n\n"
|
||||
f"💎 Средства зачислены на ваш баланс."
|
||||
)
|
||||
await send_referral_notification(bot, referrer.telegram_id, purchase_commission_notification)
|
||||
|
||||
if not user.has_had_paid_subscription:
|
||||
user.has_had_paid_subscription = True
|
||||
|
||||
@@ -1,76 +1,298 @@
|
||||
import logging
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import select, func, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.database.models import User
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.database.models import User, ReferralEarning, Transaction, TransactionType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def mark_user_as_had_paid_subscription(
|
||||
db: AsyncSession,
|
||||
user: User
|
||||
) -> None:
|
||||
if not user.has_had_paid_subscription:
|
||||
user.has_had_paid_subscription = True
|
||||
user.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
logger.info(f"🎯 Пользователь {user.telegram_id} отмечен как имевший платную подписку")
|
||||
|
||||
|
||||
async def generate_unique_referral_code(db: AsyncSession, telegram_id: int) -> str:
|
||||
max_attempts = 10
|
||||
|
||||
base_code = str(telegram_id)[-6:]
|
||||
|
||||
for attempt in range(10):
|
||||
if attempt == 0:
|
||||
referral_code = base_code
|
||||
else:
|
||||
suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=2))
|
||||
referral_code = base_code + suffix
|
||||
for _ in range(max_attempts):
|
||||
code = f"ref{''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(8))}"
|
||||
|
||||
result = await db.execute(
|
||||
select(User.id).where(User.referral_code == referral_code)
|
||||
select(User).where(User.referral_code == code)
|
||||
)
|
||||
if not result.scalar_one_or_none():
|
||||
return code
|
||||
|
||||
timestamp = str(int(datetime.utcnow().timestamp()))[-6:]
|
||||
return f"ref{timestamp}"
|
||||
|
||||
|
||||
async def mark_user_as_had_paid_subscription(db: AsyncSession, user_id: int) -> bool:
|
||||
try:
|
||||
from app.database.crud.user import get_user_by_id
|
||||
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
logger.error(f"Пользователь {user_id} не найден")
|
||||
return False
|
||||
|
||||
if not user.has_had_paid_subscription:
|
||||
user.has_had_paid_subscription = True
|
||||
user.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
logger.info(f"✅ Пользователь {user_id} отмечен как имевший платную подписку")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка отметки пользователя {user_id} как имевшего платную подписку: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def get_user_referral_summary(db: AsyncSession, user_id: int) -> Dict:
|
||||
try:
|
||||
invited_count_result = await db.execute(
|
||||
select(func.count(User.id)).where(User.referred_by_id == user_id)
|
||||
)
|
||||
invited_count = invited_count_result.scalar() or 0
|
||||
|
||||
referrals_result = await db.execute(
|
||||
select(User).where(User.referred_by_id == user_id)
|
||||
)
|
||||
referrals = referrals_result.scalars().all()
|
||||
|
||||
paid_referrals_count = sum(1 for ref in referrals if ref.has_made_first_topup)
|
||||
|
||||
total_earnings_result = await db.execute(
|
||||
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
||||
.where(ReferralEarning.user_id == user_id)
|
||||
)
|
||||
total_earned_kopeks = total_earnings_result.scalar() or 0
|
||||
|
||||
month_ago = datetime.utcnow() - timedelta(days=30)
|
||||
month_earnings_result = await db.execute(
|
||||
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
||||
.where(
|
||||
and_(
|
||||
ReferralEarning.user_id == user_id,
|
||||
ReferralEarning.created_at >= month_ago
|
||||
)
|
||||
)
|
||||
)
|
||||
month_earned_kopeks = month_earnings_result.scalar() or 0
|
||||
|
||||
recent_earnings_result = await db.execute(
|
||||
select(ReferralEarning)
|
||||
.options(selectinload(ReferralEarning.referral))
|
||||
.where(ReferralEarning.user_id == user_id)
|
||||
.order_by(ReferralEarning.created_at.desc())
|
||||
.limit(5)
|
||||
)
|
||||
recent_earnings_raw = recent_earnings_result.scalars().all()
|
||||
|
||||
recent_earnings = []
|
||||
for earning in recent_earnings_raw:
|
||||
if earning.referral:
|
||||
recent_earnings.append({
|
||||
'amount_kopeks': earning.amount_kopeks,
|
||||
'reason': earning.reason,
|
||||
'referral_name': earning.referral.full_name,
|
||||
'created_at': earning.created_at
|
||||
})
|
||||
|
||||
earnings_by_type = {}
|
||||
earnings_by_type_result = await db.execute(
|
||||
select(
|
||||
ReferralEarning.reason,
|
||||
func.count(ReferralEarning.id).label('count'),
|
||||
func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0).label('total_amount')
|
||||
)
|
||||
.where(ReferralEarning.user_id == user_id)
|
||||
.group_by(ReferralEarning.reason)
|
||||
)
|
||||
|
||||
if not result.scalar():
|
||||
return referral_code
|
||||
|
||||
import uuid
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
|
||||
async def get_user_referral_summary(db: AsyncSession, user_id: int) -> dict:
|
||||
|
||||
try:
|
||||
from app.services.referral_service import get_referral_stats_for_user
|
||||
from app.database.crud.referral import get_referral_earnings_by_user
|
||||
for row in earnings_by_type_result:
|
||||
earnings_by_type[row.reason] = {
|
||||
'count': row.count,
|
||||
'total_amount_kopeks': row.total_amount
|
||||
}
|
||||
|
||||
stats = await get_referral_stats_for_user(db, user_id)
|
||||
|
||||
recent_earnings = await get_referral_earnings_by_user(db, user_id, limit=5)
|
||||
active_referrals_count = 0
|
||||
for referral in referrals:
|
||||
if referral.last_activity and referral.last_activity >= month_ago:
|
||||
active_referrals_count += 1
|
||||
|
||||
return {
|
||||
**stats,
|
||||
"recent_earnings": [
|
||||
{
|
||||
"amount_kopeks": earning.amount_kopeks,
|
||||
"reason": earning.reason,
|
||||
"created_at": earning.created_at,
|
||||
"referral_name": earning.referral.full_name if earning.referral else "Неизвестно"
|
||||
}
|
||||
for earning in recent_earnings
|
||||
]
|
||||
'invited_count': invited_count,
|
||||
'paid_referrals_count': paid_referrals_count,
|
||||
'active_referrals_count': active_referrals_count,
|
||||
'total_earned_kopeks': total_earned_kopeks,
|
||||
'month_earned_kopeks': month_earned_kopeks,
|
||||
'recent_earnings': recent_earnings,
|
||||
'earnings_by_type': earnings_by_type,
|
||||
'conversion_rate': round((paid_referrals_count / invited_count * 100) if invited_count > 0 else 0, 1)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения сводки рефералов: {e}")
|
||||
logger.error(f"Ошибка получения статистики рефералов для пользователя {user_id}: {e}")
|
||||
return {
|
||||
"invited_count": 0,
|
||||
"paid_referrals_count": 0,
|
||||
"total_earned_kopeks": 0,
|
||||
"month_earned_kopeks": 0,
|
||||
"recent_earnings": []
|
||||
}
|
||||
'invited_count': 0,
|
||||
'paid_referrals_count': 0,
|
||||
'active_referrals_count': 0,
|
||||
'total_earned_kopeks': 0,
|
||||
'month_earned_kopeks': 0,
|
||||
'recent_earnings': [],
|
||||
'earnings_by_type': {},
|
||||
'conversion_rate': 0.0
|
||||
}
|
||||
|
||||
|
||||
async def get_detailed_referral_list(db: AsyncSession, user_id: int, limit: int = 20, offset: int = 0) -> Dict:
|
||||
try:
|
||||
referrals_result = await db.execute(
|
||||
select(User)
|
||||
.where(User.referred_by_id == user_id)
|
||||
.order_by(User.created_at.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
)
|
||||
referrals = referrals_result.scalars().all()
|
||||
|
||||
total_count_result = await db.execute(
|
||||
select(func.count(User.id)).where(User.referred_by_id == user_id)
|
||||
)
|
||||
total_count = total_count_result.scalar() or 0
|
||||
|
||||
detailed_referrals = []
|
||||
for referral in referrals:
|
||||
earnings_result = await db.execute(
|
||||
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
||||
.where(
|
||||
and_(
|
||||
ReferralEarning.user_id == user_id,
|
||||
ReferralEarning.referral_id == referral.id
|
||||
)
|
||||
)
|
||||
)
|
||||
total_earned_from_referral = earnings_result.scalar() or 0
|
||||
|
||||
topups_result = await db.execute(
|
||||
select(func.count(Transaction.id))
|
||||
.where(
|
||||
and_(
|
||||
Transaction.user_id == referral.id,
|
||||
Transaction.type == TransactionType.DEPOSIT.value,
|
||||
Transaction.is_completed == True
|
||||
)
|
||||
)
|
||||
)
|
||||
topups_count = topups_result.scalar() or 0
|
||||
|
||||
days_since_registration = (datetime.utcnow() - referral.created_at).days
|
||||
|
||||
days_since_activity = None
|
||||
if referral.last_activity:
|
||||
days_since_activity = (datetime.utcnow() - referral.last_activity).days
|
||||
|
||||
detailed_referrals.append({
|
||||
'id': referral.id,
|
||||
'telegram_id': referral.telegram_id,
|
||||
'full_name': referral.full_name,
|
||||
'username': referral.username,
|
||||
'created_at': referral.created_at,
|
||||
'last_activity': referral.last_activity,
|
||||
'has_made_first_topup': referral.has_made_first_topup,
|
||||
'balance_kopeks': referral.balance_kopeks,
|
||||
'total_earned_kopeks': total_earned_from_referral,
|
||||
'topups_count': topups_count,
|
||||
'days_since_registration': days_since_registration,
|
||||
'days_since_activity': days_since_activity,
|
||||
'status': 'active' if days_since_activity is not None and days_since_activity <= 30 else 'inactive'
|
||||
})
|
||||
|
||||
return {
|
||||
'referrals': detailed_referrals,
|
||||
'total_count': total_count,
|
||||
'has_next': offset + limit < total_count,
|
||||
'has_prev': offset > 0,
|
||||
'current_page': (offset // limit) + 1,
|
||||
'total_pages': (total_count + limit - 1) // limit
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения списка рефералов для пользователя {user_id}: {e}")
|
||||
return {
|
||||
'referrals': [],
|
||||
'total_count': 0,
|
||||
'has_next': False,
|
||||
'has_prev': False,
|
||||
'current_page': 1,
|
||||
'total_pages': 1
|
||||
}
|
||||
|
||||
|
||||
async def get_referral_analytics(db: AsyncSession, user_id: int) -> Dict:
|
||||
try:
|
||||
now = datetime.utcnow()
|
||||
periods = {
|
||||
'today': now.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
'week': now - timedelta(days=7),
|
||||
'month': now - timedelta(days=30),
|
||||
'quarter': now - timedelta(days=90)
|
||||
}
|
||||
|
||||
earnings_by_period = {}
|
||||
for period_name, start_date in periods.items():
|
||||
result = await db.execute(
|
||||
select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0))
|
||||
.where(
|
||||
and_(
|
||||
ReferralEarning.user_id == user_id,
|
||||
ReferralEarning.created_at >= start_date
|
||||
)
|
||||
)
|
||||
)
|
||||
earnings_by_period[period_name] = result.scalar() or 0
|
||||
|
||||
top_referrals_result = await db.execute(
|
||||
select(
|
||||
ReferralEarning.referral_id,
|
||||
func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0).label('total_earned'),
|
||||
func.count(ReferralEarning.id).label('earnings_count')
|
||||
)
|
||||
.where(ReferralEarning.user_id == user_id)
|
||||
.group_by(ReferralEarning.referral_id)
|
||||
.order_by(func.sum(ReferralEarning.amount_kopeks).desc())
|
||||
.limit(5)
|
||||
)
|
||||
|
||||
top_referrals = []
|
||||
for row in top_referrals_result:
|
||||
referral_result = await db.execute(
|
||||
select(User).where(User.id == row.referral_id)
|
||||
)
|
||||
referral = referral_result.scalar_one_or_none()
|
||||
if referral:
|
||||
top_referrals.append({
|
||||
'referral_name': referral.full_name,
|
||||
'total_earned_kopeks': row.total_earned,
|
||||
'earnings_count': row.earnings_count
|
||||
})
|
||||
|
||||
return {
|
||||
'earnings_by_period': earnings_by_period,
|
||||
'top_referrals': top_referrals
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения аналитики рефералов для пользователя {user_id}: {e}")
|
||||
return {
|
||||
'earnings_by_period': {
|
||||
'today': 0,
|
||||
'week': 0,
|
||||
'month': 0,
|
||||
'quarter': 0
|
||||
},
|
||||
'top_referrals': []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user