diff --git a/app/services/payment/cloudpayments.py b/app/services/payment/cloudpayments.py
index cab418dd..3248e2c4 100644
--- a/app/services/payment/cloudpayments.py
+++ b/app/services/payment/cloudpayments.py
@@ -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)
diff --git a/app/services/payment/cryptobot.py b/app/services/payment/cryptobot.py
index b0cbf4cd..be8bacf7 100644
--- a/app/services/payment/cryptobot.py
+++ b/app/services/payment/cryptobot.py
@@ -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()
diff --git a/app/services/payment/freekassa.py b/app/services/payment/freekassa.py
index 066d3147..c49aca8e 100644
--- a/app/services/payment/freekassa.py
+++ b/app/services/payment/freekassa.py
@@ -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()
diff --git a/app/services/payment/heleket.py b/app/services/payment/heleket.py
index e50c6c96..50c51a3a 100644
--- a/app/services/payment/heleket.py
+++ b/app/services/payment/heleket.py
@@ -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)
diff --git a/app/services/payment/kassa_ai.py b/app/services/payment/kassa_ai.py
index 3c97e086..9f7273c8 100644
--- a/app/services/payment/kassa_ai.py
+++ b/app/services/payment/kassa_ai.py
@@ -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()
diff --git a/app/services/payment/mulenpay.py b/app/services/payment/mulenpay.py
index 1993ee1c..3cb241f7 100644
--- a/app/services/payment/mulenpay.py
+++ b/app/services/payment/mulenpay.py
@@ -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()
diff --git a/app/services/payment/pal24.py b/app/services/payment/pal24.py
index a5582c6e..c9b721ca 100644
--- a/app/services/payment/pal24.py
+++ b/app/services/payment/pal24.py
@@ -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()
diff --git a/app/services/payment/platega.py b/app/services/payment/platega.py
index 8a79992b..bdab8c46 100644
--- a/app/services/payment/platega.py
+++ b/app/services/payment/platega.py
@@ -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)
diff --git a/app/services/payment/riopay.py b/app/services/payment/riopay.py
index b0fbcd66..c3684426 100644
--- a/app/services/payment/riopay.py
+++ b/app/services/payment/riopay.py
@@ -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))
diff --git a/app/services/payment/stars.py b/app/services/payment/stars.py
index b95cd8b7..e29c70ca 100644
--- a/app/services/payment/stars.py
+++ b/app/services/payment/stars.py
@@ -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()
diff --git a/app/services/payment/wata.py b/app/services/payment/wata.py
index 39fd3c07..21bb7f14 100644
--- a/app/services/payment/wata.py
+++ b/app/services/payment/wata.py
@@ -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)
diff --git a/app/services/payment/yookassa.py b/app/services/payment/yookassa.py
index 9405ecc8..e95b4285 100644
--- a/app/services/payment/yookassa.py
+++ b/app/services/payment/yookassa.py
@@ -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()
diff --git a/app/services/referral_service.py b/app/services/referral_service.py
index 7b816658..c28a1cbd 100644
--- a/app/services/referral_service.py
+++ b/app/services/referral_service.py
@@ -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'💰 Реферальная награда!\n\n'
- f'Ваш реферал {user.full_name} сделал первое пополнение!\n\n'
- f'🎁 Вы получили награду: {settings.format_price(inviter_bonus)}\n\n'
+ f'Ваш реферал {user.full_name} сделал первое пополнение '
+ 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(
diff --git a/app/services/tribute_service.py b/app/services/tribute_service.py
index 003b7ee2..6e5b8a24 100644
--- a/app/services/tribute_service.py
+++ b/app/services/tribute_service.py
@@ -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()
diff --git a/tests/services/test_referral_service.py b/tests/services/test_referral_service.py
index abdb9029..216af107 100644
--- a/tests/services/test_referral_service.py
+++ b/tests/services/test_referral_service.py
@@ -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