fix: 3 bugs — notification type, referral with channel sub, BOT_USERNAME
1. Admin notification showed "renewal" instead of "first purchase" for new users because has_had_paid_subscription was set before notification. All 21 call sites now pass explicit purchase_type. 2. Partner referral not counted when mandatory channel subscription enabled. required_sub_channel_check saved campaign_id but not referrer_id from campaign.partner_user_id. Also removed duplicate DB query. 3. BOT_USERNAME auto-detection moved before web server start to close race window on /cabinet/branding/telegram-widget endpoint.
This commit is contained in:
@@ -1904,7 +1904,7 @@ async def submit_purchase(
|
||||
period_days=selection.period.days,
|
||||
was_trial_conversion=result.get('was_trial_conversion', False),
|
||||
amount_kopeks=pricing.final_total,
|
||||
purchase_type='renewal' if not is_new_subscription else None,
|
||||
purchase_type='renewal' if not is_new_subscription else 'first_purchase',
|
||||
)
|
||||
finally:
|
||||
await bot.session.close()
|
||||
@@ -2357,7 +2357,7 @@ async def purchase_tariff(
|
||||
period_days=period_days,
|
||||
was_trial_conversion=False,
|
||||
amount_kopeks=price_kopeks,
|
||||
purchase_type='renewal' if not was_new_subscription else None,
|
||||
purchase_type='renewal' if not was_new_subscription else 'first_purchase',
|
||||
)
|
||||
finally:
|
||||
await bot.session.close()
|
||||
|
||||
@@ -649,6 +649,7 @@ async def handle_simple_subscription_pay_with_balance(
|
||||
subscription_params['period_days'],
|
||||
False, # was_trial_conversion
|
||||
amount_kopeks=price_kopeks,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админам о покупке', error=e)
|
||||
@@ -2368,6 +2369,7 @@ async def confirm_simple_subscription_purchase(
|
||||
subscription_params['period_days'],
|
||||
False, # was_trial_conversion
|
||||
amount_kopeks=price_kopeks,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админам о покупке', error=e)
|
||||
|
||||
@@ -2118,14 +2118,6 @@ async def required_sub_channel_check(
|
||||
# Очищаем Redis после успешной проверки подписки
|
||||
await delete_pending_payload_from_redis(query.from_user.id)
|
||||
|
||||
# Всегда обновляем referral_code если есть новый payload
|
||||
# (исправление бага с устаревшими данными в state)
|
||||
campaign = await get_campaign_by_start_parameter(
|
||||
db,
|
||||
pending_start_payload,
|
||||
only_active=True,
|
||||
)
|
||||
|
||||
# Обрабатываем payload только если ещё не обработан
|
||||
# (проверяем по наличию referral_code или campaign_id в state)
|
||||
if not state_data.get('referral_code') and not state_data.get('campaign_id'):
|
||||
@@ -2137,7 +2129,13 @@ async def required_sub_channel_check(
|
||||
|
||||
if campaign:
|
||||
state_data['campaign_id'] = campaign.id
|
||||
logger.info('📣 CHANNEL CHECK: Кампания восстановлена из payload', campaign_id=campaign.id)
|
||||
if campaign.partner_user_id:
|
||||
state_data['referrer_id'] = campaign.partner_user_id
|
||||
logger.info(
|
||||
'📣 CHANNEL CHECK: Кампания восстановлена из payload',
|
||||
campaign_id=campaign.id,
|
||||
partner_user_id=campaign.partner_user_id,
|
||||
)
|
||||
else:
|
||||
state_data['referral_code'] = pending_start_payload
|
||||
logger.info(
|
||||
|
||||
@@ -27,6 +27,7 @@ async def send_purchase_notification(
|
||||
transaction_id: int,
|
||||
period_days: int,
|
||||
was_trial_conversion: bool = False,
|
||||
purchase_type: str | None = None,
|
||||
):
|
||||
try:
|
||||
from app.database.crud.transaction import get_transaction_by_id
|
||||
@@ -35,7 +36,8 @@ async def send_purchase_notification(
|
||||
if transaction:
|
||||
notification_service = AdminNotificationService(callback.bot)
|
||||
await notification_service.send_subscription_purchase_notification(
|
||||
db, db_user, subscription, transaction, period_days, was_trial_conversion
|
||||
db, db_user, subscription, transaction, period_days, was_trial_conversion,
|
||||
purchase_type=purchase_type,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления о покупке', error=e)
|
||||
|
||||
@@ -2821,7 +2821,8 @@ async def confirm_purchase(callback: types.CallbackQuery, state: FSMContext, db_
|
||||
try:
|
||||
notification_service = AdminNotificationService(callback.bot)
|
||||
await notification_service.send_subscription_purchase_notification(
|
||||
db, db_user, subscription, transaction, period_days, was_trial_conversion
|
||||
db, db_user, subscription, transaction, period_days, was_trial_conversion,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления о покупке', error=e)
|
||||
|
||||
@@ -909,6 +909,7 @@ async def handle_custom_confirm(
|
||||
custom_days,
|
||||
was_trial_conversion=False,
|
||||
amount_kopeks=total_price,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админу', error=e)
|
||||
@@ -1221,6 +1222,7 @@ async def confirm_tariff_purchase(
|
||||
period,
|
||||
was_trial_conversion=False,
|
||||
amount_kopeks=final_price,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админу', error=e)
|
||||
@@ -1404,6 +1406,7 @@ async def confirm_daily_tariff_purchase(
|
||||
1, # 1 день
|
||||
was_trial_conversion=False,
|
||||
amount_kopeks=daily_price,
|
||||
purchase_type='renewal' if existing_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админу', error=e)
|
||||
@@ -1765,6 +1768,7 @@ async def confirm_tariff_extend(
|
||||
period,
|
||||
was_trial_conversion=False,
|
||||
amount_kopeks=final_price,
|
||||
purchase_type='renewal',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Ошибка отправки уведомления админу', error=e)
|
||||
|
||||
@@ -401,7 +401,7 @@ class AdminNotificationService:
|
||||
period_days: int,
|
||||
was_trial_conversion: bool = False,
|
||||
amount_kopeks: int | None = None,
|
||||
purchase_type: str | None = None, # 'tariff_switch', 'renewal', None (auto)
|
||||
purchase_type: str | None = None, # 'first_purchase', 'renewal', 'tariff_switch', None (auto-detect)
|
||||
) -> bool:
|
||||
try:
|
||||
total_amount = (
|
||||
@@ -436,7 +436,10 @@ class AdminNotificationService:
|
||||
elif was_trial_conversion:
|
||||
event_title = '🔄 КОНВЕРСИЯ ИЗ ТРИАЛА'
|
||||
user_status = 'Конверсия'
|
||||
elif purchase_type == 'renewal' or user.has_had_paid_subscription:
|
||||
elif purchase_type == 'first_purchase':
|
||||
event_title = '💎 ПОКУПКА ПОДПИСКИ'
|
||||
user_status = 'Первая покупка'
|
||||
elif purchase_type == 'renewal' or (purchase_type is None and user.has_had_paid_subscription):
|
||||
event_title = '💎 ПРОДЛЕНИЕ ПОДПИСКИ'
|
||||
user_status = 'Продление'
|
||||
else:
|
||||
|
||||
@@ -345,6 +345,7 @@ class TelegramStarsMixin:
|
||||
transaction,
|
||||
period_display,
|
||||
was_trial_conversion=False,
|
||||
purchase_type='renewal' if user.has_had_paid_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as admin_error: # pragma: no cover - диагностический лог
|
||||
logger.error(
|
||||
|
||||
@@ -976,6 +976,7 @@ class YooKassaPaymentMixin:
|
||||
transaction,
|
||||
subscription_period,
|
||||
was_trial_conversion=False,
|
||||
purchase_type='renewal' if (full_user or user).has_had_paid_subscription else 'first_purchase',
|
||||
)
|
||||
except Exception as admin_error:
|
||||
logger.error(
|
||||
|
||||
@@ -825,7 +825,8 @@ async def _auto_purchase_tariff(
|
||||
|
||||
await with_admin_notification_service(
|
||||
lambda svc: svc.send_subscription_purchase_notification(
|
||||
db, user, subscription, transaction, period_days, was_trial_conversion
|
||||
db, user, subscription, transaction, period_days, was_trial_conversion,
|
||||
purchase_type='renewal',
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
@@ -1100,7 +1101,8 @@ async def _auto_purchase_daily_tariff(
|
||||
|
||||
await with_admin_notification_service(
|
||||
lambda svc: svc.send_subscription_purchase_notification(
|
||||
db, user, subscription, transaction, 1, was_trial_conversion
|
||||
db, user, subscription, transaction, 1, was_trial_conversion,
|
||||
purchase_type='renewal',
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
@@ -2398,6 +2400,7 @@ async def auto_purchase_saved_cart_after_topup(
|
||||
transaction,
|
||||
selection.period.days,
|
||||
was_trial_conversion,
|
||||
purchase_type='renewal',
|
||||
)
|
||||
except Exception as error: # pragma: no cover - defensive logging
|
||||
logger.error(
|
||||
|
||||
@@ -5670,6 +5670,7 @@ async def subscription_purchase_endpoint(
|
||||
pass
|
||||
|
||||
if subscription and transaction and period_days:
|
||||
_purchase_type = 'renewal' if context.subscription else 'first_purchase'
|
||||
await with_admin_notification_service(
|
||||
lambda service: service.send_subscription_purchase_notification(
|
||||
db,
|
||||
@@ -5678,6 +5679,7 @@ async def subscription_purchase_endpoint(
|
||||
transaction,
|
||||
period_days,
|
||||
was_trial_conversion=was_trial_conversion,
|
||||
purchase_type=_purchase_type,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -295,6 +295,11 @@ async def main():
|
||||
bot, dp = await setup_bot()
|
||||
stage.log('Кеш и FSM подготовлены')
|
||||
|
||||
bot_user = await bot.get_me()
|
||||
if bot_user.username and not settings.BOT_USERNAME:
|
||||
settings.BOT_USERNAME = bot_user.username
|
||||
logger.info('BOT_USERNAME auto-detected', bot_username=bot_user.username)
|
||||
|
||||
monitoring_service.bot = bot
|
||||
maintenance_service.set_bot(bot)
|
||||
broadcast_service.set_bot(bot)
|
||||
@@ -516,7 +521,6 @@ async def main():
|
||||
success_message='Токен внешней админки готов',
|
||||
) as stage:
|
||||
try:
|
||||
bot_user = await bot.get_me()
|
||||
token = await ensure_external_admin_token(
|
||||
bot_user.username,
|
||||
bot_user.id,
|
||||
|
||||
@@ -273,6 +273,7 @@ async def test_process_stars_payment_simple_subscription_success(
|
||||
transaction: Any,
|
||||
period_days: int,
|
||||
was_trial_conversion: bool,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
admin_calls.append(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user