fix: реферальный бонус инвайтера — сумма вместо максимума, защита флага первого пополнения
- referral_service: inviter_bonus = fixed + commission вместо max(fixed, commission) - 13 платёжных провайдеров: has_made_first_topup ставится только для нереферальных юзеров - riopay: критический фикс — флаг ставился до вызова referral_service - Обновлены уведомления с разбивкой бонуса - Исправлен и дополнен тест referral_service
This commit is contained in:
@@ -347,7 +347,7 @@ class CloudPaymentsPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения CloudPayments', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
@@ -338,7 +338,7 @@ class CryptoBotPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения CryptoBot', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ class FreekassaPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения Freekassa', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -391,7 +391,7 @@ class HeleketPaymentMixin:
|
||||
except Exception as error: # pragma: no cover - defensive
|
||||
logger.error('Ошибка реферального начисления Heleket', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
@@ -335,7 +335,7 @@ class KassaAiPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения KassaAI', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ class MulenPayPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения', display_name=display_name, error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ class Pal24PaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения Pal24', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ class PlategaPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения Platega', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
@@ -316,7 +316,7 @@ class RioPayPaymentMixin:
|
||||
UserModel.balance_kopeks: UserModel.balance_kopeks + payment.amount_kopeks,
|
||||
UserModel.updated_at: datetime.now(UTC),
|
||||
}
|
||||
if was_first_topup:
|
||||
if was_first_topup and not user.referred_by_id:
|
||||
update_values[UserModel.has_made_first_topup] = True
|
||||
|
||||
await db.execute(update(UserModel).where(UserModel.id == user.id).values(update_values))
|
||||
|
||||
@@ -442,7 +442,7 @@ class TelegramStarsMixin:
|
||||
"❌ Описание '' не подходит для реферальной логики", description_for_referral=description_for_referral
|
||||
)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ class WataPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения WATA', error=error)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
@@ -819,7 +819,7 @@ class YooKassaPaymentMixin:
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки реферального пополнения YooKassa', error=error)
|
||||
|
||||
if was_first_topup and not getattr(user, 'has_made_first_topup', False):
|
||||
if was_first_topup and not getattr(user, 'has_made_first_topup', False) and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await db.commit()
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ async def process_referral_registration(db: AsyncSession, new_user_id: int, refe
|
||||
)
|
||||
if settings.REFERRAL_INVITER_BONUS_KOPEKS > 0:
|
||||
inviter_notification += (
|
||||
f'вы получите минимум {settings.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)} или '
|
||||
f'{commission_percent}% от суммы (что больше).\n\n'
|
||||
f'вы получите {settings.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)} + '
|
||||
f'{commission_percent}% от суммы пополнения.\n\n'
|
||||
)
|
||||
else:
|
||||
inviter_notification += f'вы получите {commission_percent}% от суммы.\n\n'
|
||||
@@ -304,7 +304,7 @@ async def process_referral_topup(db: AsyncSession, user_id: int, topup_amount_ko
|
||||
)
|
||||
|
||||
commission_amount = int(topup_amount_kopeks * commission_percent / 100)
|
||||
inviter_bonus = max(settings.REFERRAL_INVITER_BONUS_KOPEKS, commission_amount)
|
||||
inviter_bonus = settings.REFERRAL_INVITER_BONUS_KOPEKS + commission_amount
|
||||
|
||||
if inviter_bonus > 0:
|
||||
balance_ok = await add_user_balance(
|
||||
@@ -332,10 +332,22 @@ async def process_referral_topup(db: AsyncSession, user_id: int, topup_amount_ko
|
||||
)
|
||||
|
||||
if bot:
|
||||
bonus_parts = []
|
||||
if settings.REFERRAL_INVITER_BONUS_KOPEKS > 0:
|
||||
bonus_parts.append(
|
||||
f'фикс. бонус {settings.format_price(settings.REFERRAL_INVITER_BONUS_KOPEKS)}'
|
||||
)
|
||||
if commission_amount > 0:
|
||||
bonus_parts.append(
|
||||
f'комиссия {commission_percent}% = {settings.format_price(commission_amount)}'
|
||||
)
|
||||
bonus_breakdown = ' + '.join(bonus_parts)
|
||||
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'Ваш реферал <b>{user.full_name}</b> сделал первое пополнение '
|
||||
f'на {settings.format_price(topup_amount_kopeks)}!\n\n'
|
||||
f'🎁 Ваша награда: {settings.format_price(inviter_bonus)}'
|
||||
f' ({bonus_breakdown})\n\n'
|
||||
f'📈 Теперь с каждого его пополнения вы будете получать {commission_percent}% комиссии.'
|
||||
)
|
||||
await send_referral_notification(
|
||||
|
||||
@@ -166,7 +166,7 @@ class TributeService:
|
||||
except Exception as e:
|
||||
logger.error('Ошибка обработки реферального пополнения Tribute', error=e)
|
||||
|
||||
if was_first_topup and not user.has_made_first_topup:
|
||||
if was_first_topup and not user.has_made_first_topup and not user.referred_by_id:
|
||||
user.has_made_first_topup = True
|
||||
await session.commit()
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ async def test_commission_accrues_before_minimum_first_topup(monkeypatch):
|
||||
monkeypatch.setattr(referral_service, 'add_user_balance', add_user_balance_mock)
|
||||
create_referral_earning_mock = AsyncMock()
|
||||
monkeypatch.setattr(referral_service, 'create_referral_earning', create_referral_earning_mock)
|
||||
monkeypatch.setattr(referral_service, 'get_user_campaign_id', AsyncMock(return_value=None))
|
||||
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_MINIMUM_TOPUP_KOPEKS', 20000)
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_FIRST_TOPUP_BONUS_KOPEKS', 5000)
|
||||
@@ -61,5 +62,58 @@ async def test_commission_accrues_before_minimum_first_topup(monkeypatch):
|
||||
assert earning_call.kwargs['amount_kopeks'] == 3750
|
||||
assert earning_call.kwargs['reason'] == 'referral_commission_topup'
|
||||
|
||||
db.commit.assert_not_awaited()
|
||||
db.execute.assert_not_awaited()
|
||||
|
||||
async def test_first_topup_inviter_gets_fixed_plus_commission(monkeypatch):
|
||||
"""Inviter bonus should be fixed bonus + commission, not max(fixed, commission)."""
|
||||
user = SimpleNamespace(
|
||||
id=1,
|
||||
telegram_id=101,
|
||||
full_name='Test User',
|
||||
referred_by_id=2,
|
||||
has_made_first_topup=False,
|
||||
)
|
||||
referrer = SimpleNamespace(
|
||||
id=2,
|
||||
telegram_id=202,
|
||||
full_name='Referrer',
|
||||
email=None,
|
||||
)
|
||||
|
||||
db = SimpleNamespace(
|
||||
commit=AsyncMock(),
|
||||
execute=AsyncMock(),
|
||||
)
|
||||
|
||||
get_user_mock = AsyncMock(side_effect=[user, referrer])
|
||||
monkeypatch.setattr(referral_service, 'get_user_by_id', get_user_mock)
|
||||
add_user_balance_mock = AsyncMock(return_value=True)
|
||||
monkeypatch.setattr(referral_service, 'add_user_balance', add_user_balance_mock)
|
||||
create_referral_earning_mock = AsyncMock()
|
||||
monkeypatch.setattr(referral_service, 'create_referral_earning', create_referral_earning_mock)
|
||||
monkeypatch.setattr(referral_service, 'get_commission_payment_count', AsyncMock(return_value=0))
|
||||
monkeypatch.setattr(referral_service, 'get_user_campaign_id', AsyncMock(return_value=None))
|
||||
monkeypatch.setattr(referral_service, 'get_effective_referral_commission_percent', lambda u: 15)
|
||||
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_MINIMUM_TOPUP_KOPEKS', 10000)
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_FIRST_TOPUP_BONUS_KOPEKS', 5000)
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_INVITER_BONUS_KOPEKS', 5000) # 50 rub
|
||||
monkeypatch.setattr(referral_service.settings, 'REFERRAL_COMMISSION_PERCENT', 15)
|
||||
|
||||
topup_amount = 50000 # 500 rub
|
||||
|
||||
result = await referral_service.process_referral_topup(db, user.id, topup_amount)
|
||||
|
||||
assert result is True
|
||||
assert user.has_made_first_topup is True
|
||||
|
||||
# add_user_balance called twice: first for referral's own bonus, then for inviter bonus
|
||||
assert add_user_balance_mock.await_count == 2
|
||||
|
||||
# Second call is the inviter bonus: fixed 5000 + commission 15% of 50000 = 7500 → total 12500
|
||||
inviter_call = add_user_balance_mock.await_args_list[1]
|
||||
expected_commission = int(50000 * 15 / 100) # 7500
|
||||
expected_inviter_bonus = 5000 + expected_commission # 12500
|
||||
assert inviter_call.args[2] == expected_inviter_bonus
|
||||
|
||||
# With old max() logic, this would have been max(5000, 7500) = 7500 — wrong!
|
||||
assert expected_inviter_bonus == 12500
|
||||
|
||||
Reference in New Issue
Block a user