mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-28 08:41:05 +00:00
fix: correct referral withdrawal balance formula and commission transaction type
The available_referral formula incorrectly treated all post-earning spending as spent from referral balance, making withdrawable balance stay at 0 even as earnings increased. Changed to min(wallet_balance, earned - withdrawn - pending). - Fix available_referral in withdrawal service and referral info endpoint - Use TransactionType.REFERRAL_REWARD for all commission/bonus balance additions - Gate create_referral_earning behind add_user_balance success check - Move notifications inside balance_ok guards to prevent false confirmations
This commit is contained in:
@@ -76,7 +76,9 @@ async def get_referral_info(
|
||||
pending_result = await db.execute(pending_query)
|
||||
pending = pending_result.scalar() or 0
|
||||
|
||||
available_balance = max(0, total_earnings - withdrawn - pending)
|
||||
# Доступный баланс: мин(кошелёк, заработано - выведено - в ожидании)
|
||||
referral_entitlement = max(0, total_earnings - withdrawn - pending)
|
||||
available_balance = min(user.balance_kopeks, referral_entitlement)
|
||||
|
||||
# Build referral link
|
||||
bot_username = settings.get_bot_username() or 'bot'
|
||||
|
||||
@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.config import settings
|
||||
from app.database.crud.referral import create_referral_earning, get_user_campaign_id
|
||||
from app.database.crud.user import add_user_balance, get_user_by_id
|
||||
from app.database.models import ReferralEarning, User
|
||||
from app.database.models import ReferralEarning, TransactionType, User
|
||||
from app.services.notification_delivery_service import (
|
||||
notification_delivery_service,
|
||||
)
|
||||
@@ -168,45 +168,53 @@ async def process_referral_topup(db: AsyncSession, user_id: int, topup_amount_ko
|
||||
)
|
||||
|
||||
if commission_amount > 0:
|
||||
await add_user_balance(
|
||||
balance_ok = await add_user_balance(
|
||||
db,
|
||||
referrer,
|
||||
commission_amount,
|
||||
f'Комиссия {commission_percent}% с пополнения {user.full_name}',
|
||||
transaction_type=TransactionType.REFERRAL_REWARD,
|
||||
bot=bot,
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=commission_amount,
|
||||
reason='referral_commission_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
'💰 Комиссия с пополнения: получил ₽ (до первого бонуса)',
|
||||
telegram_id=referrer.telegram_id,
|
||||
commission_amount=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'🎁 Ваша комиссия ({commission_percent}%): '
|
||||
f'{settings.format_price(commission_amount)}\n\n'
|
||||
f'💎 Средства зачислены на ваш баланс.'
|
||||
if balance_ok:
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=commission_amount,
|
||||
reason='referral_commission_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
commission_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=commission_amount,
|
||||
referral_name=user.full_name,
|
||||
|
||||
logger.info(
|
||||
'💰 Комиссия с пополнения: получил ₽ (до первого бонуса)',
|
||||
telegram_id=referrer.telegram_id,
|
||||
commission_amount=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'🎁 Ваша комиссия ({commission_percent}%): '
|
||||
f'{settings.format_price(commission_amount)}\n\n'
|
||||
f'💎 Средства зачислены на ваш баланс.'
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
commission_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=commission_amount,
|
||||
referral_name=user.full_name,
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
'Не удалось начислить комиссию на баланс, ReferralEarning не создан',
|
||||
referrer_id=referrer.id,
|
||||
commission_amount=commission_amount,
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -228,31 +236,39 @@ async def process_referral_topup(db: AsyncSession, user_id: int, topup_amount_ko
|
||||
logger.error('Ошибка удаления записи ожидания', error=e)
|
||||
|
||||
if settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS > 0:
|
||||
await add_user_balance(
|
||||
bonus_ok = await add_user_balance(
|
||||
db,
|
||||
user,
|
||||
settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS,
|
||||
'Бонус за первое пополнение по реферальной программе',
|
||||
transaction_type=TransactionType.REFERRAL_REWARD,
|
||||
bot=bot,
|
||||
)
|
||||
logger.info(
|
||||
'💰 Реферал получил бонус ₽',
|
||||
user_id=user.id,
|
||||
REFERRAL_FIRST_TOPUP_BONUS_KOPEKS=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'💎 Средства зачислены на ваш баланс.'
|
||||
if bonus_ok:
|
||||
logger.info(
|
||||
'💰 Реферал получил бонус ₽',
|
||||
user_id=user.id,
|
||||
REFERRAL_FIRST_TOPUP_BONUS_KOPEKS=settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS / 100,
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
user.telegram_id,
|
||||
bonus_notification,
|
||||
user=user,
|
||||
|
||||
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,
|
||||
user=user,
|
||||
bonus_kopeks=settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS,
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
'Не удалось начислить бонус за первое пополнение',
|
||||
user_id=user.id,
|
||||
bonus_kopeks=settings.REFERRAL_FIRST_TOPUP_BONUS_KOPEKS,
|
||||
)
|
||||
|
||||
@@ -260,78 +276,99 @@ async def process_referral_topup(db: AsyncSession, user_id: int, topup_amount_ko
|
||||
inviter_bonus = max(settings.REFERRAL_INVITER_BONUS_KOPEKS, commission_amount)
|
||||
|
||||
if inviter_bonus > 0:
|
||||
await add_user_balance(
|
||||
db, referrer, inviter_bonus, f'Бонус за первое пополнение реферала {user.full_name}', bot=bot
|
||||
balance_ok = await add_user_balance(
|
||||
db,
|
||||
referrer,
|
||||
inviter_bonus,
|
||||
f'Бонус за первое пополнение реферала {user.full_name}',
|
||||
transaction_type=TransactionType.REFERRAL_REWARD,
|
||||
bot=bot,
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=inviter_bonus,
|
||||
reason='referral_first_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
referrer_id = referrer.telegram_id or referrer.email or f'user#{referrer.id}'
|
||||
logger.info('💰 Реферер получил бонус ₽', referrer_id=referrer_id, inviter_bonus=inviter_bonus / 100)
|
||||
|
||||
if bot:
|
||||
inviter_bonus_notification = (
|
||||
f'💰 <b>Реферальная награда!</b>\n\n'
|
||||
f'Ваш реферал <b>{user.full_name}</b> сделал первое пополнение!\n\n'
|
||||
f'🎁 Вы получили награду: {settings.format_price(inviter_bonus)}\n\n'
|
||||
f'📈 Теперь с каждого его пополнения вы будете получать {commission_percent}% комиссии.'
|
||||
if balance_ok:
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=inviter_bonus,
|
||||
reason='referral_first_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
inviter_bonus_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=inviter_bonus,
|
||||
referral_name=user.full_name,
|
||||
|
||||
referrer_id = referrer.telegram_id or referrer.email or f'user#{referrer.id}'
|
||||
logger.info('💰 Реферер получил бонус ₽', referrer_id=referrer_id, inviter_bonus=inviter_bonus / 100)
|
||||
|
||||
if bot:
|
||||
inviter_bonus_notification = (
|
||||
f'💰 <b>Реферальная награда!</b>\n\n'
|
||||
f'Ваш реферал <b>{user.full_name}</b> сделал первое пополнение!\n\n'
|
||||
f'🎁 Вы получили награду: {settings.format_price(inviter_bonus)}\n\n'
|
||||
f'📈 Теперь с каждого его пополнения вы будете получать {commission_percent}% комиссии.'
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
inviter_bonus_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=inviter_bonus,
|
||||
referral_name=user.full_name,
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
'Не удалось начислить бонус на баланс, ReferralEarning не создан',
|
||||
referrer_id=referrer.id,
|
||||
inviter_bonus=inviter_bonus,
|
||||
)
|
||||
|
||||
elif commission_amount > 0:
|
||||
await add_user_balance(
|
||||
balance_ok = await add_user_balance(
|
||||
db,
|
||||
referrer,
|
||||
commission_amount,
|
||||
f'Комиссия {commission_percent}% с пополнения {user.full_name}',
|
||||
transaction_type=TransactionType.REFERRAL_REWARD,
|
||||
bot=bot,
|
||||
)
|
||||
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=commission_amount,
|
||||
reason='referral_commission_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
|
||||
referrer_id = referrer.telegram_id or referrer.email or f'user#{referrer.id}'
|
||||
logger.info(
|
||||
'💰 Комиссия с пополнения: получил ₽',
|
||||
referrer_id=referrer_id,
|
||||
commission_amount=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'🎁 Ваша комиссия ({commission_percent}%): '
|
||||
f'{settings.format_price(commission_amount)}\n\n'
|
||||
f'💎 Средства зачислены на ваш баланс.'
|
||||
if balance_ok:
|
||||
await create_referral_earning(
|
||||
db=db,
|
||||
user_id=referrer.id,
|
||||
referral_id=user.id,
|
||||
amount_kopeks=commission_amount,
|
||||
reason='referral_commission_topup',
|
||||
campaign_id=campaign_id,
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
commission_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=commission_amount,
|
||||
referral_name=user.full_name,
|
||||
|
||||
referrer_id = referrer.telegram_id or referrer.email or f'user#{referrer.id}'
|
||||
logger.info(
|
||||
'💰 Комиссия с пополнения: получил ₽',
|
||||
referrer_id=referrer_id,
|
||||
commission_amount=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'🎁 Ваша комиссия ({commission_percent}%): '
|
||||
f'{settings.format_price(commission_amount)}\n\n'
|
||||
f'💎 Средства зачислены на ваш баланс.'
|
||||
)
|
||||
await send_referral_notification(
|
||||
bot,
|
||||
referrer.telegram_id,
|
||||
commission_notification,
|
||||
user=referrer,
|
||||
bonus_kopeks=commission_amount,
|
||||
referral_name=user.full_name,
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
'Не удалось начислить комиссию на баланс, ReferralEarning не создан',
|
||||
referrer_id=referrer.id,
|
||||
commission_amount=commission_amount,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -118,6 +118,10 @@ class ReferralWithdrawalService:
|
||||
async def get_referral_balance_stats(self, db: AsyncSession, user_id: int) -> dict:
|
||||
"""
|
||||
Получает полную статистику реферального баланса.
|
||||
|
||||
Доступный реферальный баланс = min(баланс кошелька, заработано - выведено - в ожидании).
|
||||
Партнёр не может вывести больше, чем реально лежит в кошельке (User.balance_kopeks),
|
||||
и не больше, чем заработал минус уже выведенные/замороженные средства.
|
||||
"""
|
||||
total_earned = await self.get_total_referral_earnings(db, user_id)
|
||||
own_deposits = await self.get_user_own_deposits(db, user_id)
|
||||
@@ -126,27 +130,32 @@ class ReferralWithdrawalService:
|
||||
withdrawn = await self.get_withdrawn_amount(db, user_id)
|
||||
pending = await self.get_pending_withdrawal_amount(db, user_id)
|
||||
|
||||
# Сколько реф. баланса потрачено = мин(траты ПОСЛЕ первого начисления, реф_заработок)
|
||||
# Логика: только траты после получения реф. дохода могут быть из реф. баланса
|
||||
# Текущий баланс кошелька — реальный ограничитель вывода
|
||||
user = await db.get(User, user_id)
|
||||
user_balance = user.balance_kopeks if user else 0
|
||||
|
||||
# referral_spent — для аналитики/отображения, больше НЕ влияет на available_referral
|
||||
referral_spent = min(spending_after_earning, total_earned)
|
||||
|
||||
# Доступный реферальный баланс
|
||||
available_referral = max(0, total_earned - referral_spent - withdrawn - pending)
|
||||
# Реферальное право: сколько заработано минус выведено/заморожено
|
||||
referral_entitlement = max(0, total_earned - withdrawn - pending)
|
||||
|
||||
# Доступный реферальный баланс: мин(кошелёк, реферальное право)
|
||||
# Нельзя вывести больше, чем лежит в кошельке
|
||||
available_referral = min(user_balance, referral_entitlement)
|
||||
|
||||
# Если разрешено выводить и свой баланс
|
||||
if not settings.REFERRAL_WITHDRAWAL_ONLY_REFERRAL_BALANCE:
|
||||
# Свой остаток = пополнения - (траты - реф_потрачено)
|
||||
own_remaining = max(0, own_deposits - max(0, spending - referral_spent))
|
||||
available_total = available_referral + own_remaining
|
||||
# Весь кошелёк доступен к выводу (уже ограничен user_balance)
|
||||
available_total = user_balance
|
||||
else:
|
||||
own_remaining = 0
|
||||
available_total = available_referral
|
||||
|
||||
return {
|
||||
'total_earned': total_earned, # Всего заработано с рефералов
|
||||
'own_deposits': own_deposits, # Собственные пополнения
|
||||
'spending': spending, # Потрачено на подписки и пр.
|
||||
'referral_spent': referral_spent, # Сколько реф. баланса потрачено
|
||||
'referral_spent': referral_spent, # Сколько реф. баланса потрачено (аналитика)
|
||||
'withdrawn': withdrawn, # Уже выведено
|
||||
'pending': pending, # На рассмотрении
|
||||
'available_referral': available_referral, # Доступно реф. баланса
|
||||
|
||||
Reference in New Issue
Block a user