diff --git a/app/database/crud/promocode.py b/app/database/crud/promocode.py index cbe3c3a0..d67415bb 100644 --- a/app/database/crud/promocode.py +++ b/app/database/crud/promocode.py @@ -22,6 +22,25 @@ async def get_promocode_by_code(db: AsyncSession, code: str) -> Optional[PromoCo return result.scalar_one_or_none() +async def check_promocode_validity(db: AsyncSession, code: str) -> dict: + """ + Проверяет существование и валидность промокода без активации. + Возвращает словарь с информацией о промокоде. + """ + promocode = await get_promocode_by_code(db, code) + + if not promocode: + return {"valid": False, "error": "not_found", "promocode": None} + + if not promocode.is_valid: + if promocode.current_uses >= promocode.max_uses: + return {"valid": False, "error": "used", "promocode": None} + else: + return {"valid": False, "error": "expired", "promocode": None} + + return {"valid": True, "error": None, "promocode": promocode} + + async def create_promocode( db: AsyncSession, code: str, diff --git a/app/handlers/promocode.py b/app/handlers/promocode.py index 949f3b9e..38ed58ad 100644 --- a/app/handlers/promocode.py +++ b/app/handlers/promocode.py @@ -1,9 +1,8 @@ import logging -from aiogram import Dispatcher, types, F +from aiogram import Dispatcher, types, F, Bot from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession -from app.config import settings from app.states import PromoCodeStates from app.database.models import User from app.keyboards.inline import get_back_keyboard @@ -32,6 +31,45 @@ async def show_promocode_menu( await callback.answer() +async def activate_promocode_for_registration( + db: AsyncSession, + user_id: int, + code: str, + bot: Bot = None +) -> dict: + """ + Активирует промокод для пользователя во время регистрации. + Возвращает результат активации без отправки сообщений. + """ + promocode_service = PromoCodeService() + result = await promocode_service.activate_promocode(db, user_id, code) + + if result["success"]: + logger.info(f"✅ Пользователь {user_id} активировал промокод {code} при регистрации") + + # Отправляем уведомление админу, если бот доступен + if bot: + try: + from app.database.crud.user import get_user_by_id + user = await get_user_by_id(db, user_id) + if user: + notification_service = AdminNotificationService(bot) + await notification_service.send_promocode_activation_notification( + db, + user, + result.get("promocode", {"code": code}), + result["description"], + ) + except Exception as notify_error: + logger.error( + "Ошибка отправки админ уведомления об активации промокода %s: %s", + code, + notify_error, + ) + + return result + + @error_handler async def process_promocode( message: types.Message, @@ -40,9 +78,9 @@ async def process_promocode( db: AsyncSession ): texts = get_texts(db_user.language) - + code = message.text.strip() - + if not code: await message.answer( texts.t( @@ -52,31 +90,14 @@ async def process_promocode( reply_markup=get_back_keyboard(db_user.language) ) return - - promocode_service = PromoCodeService() - result = await promocode_service.activate_promocode(db, db_user.id, code) - + + result = await activate_promocode_for_registration(db, db_user.id, code, message.bot) + if result["success"]: await message.answer( texts.PROMOCODE_SUCCESS.format(description=result["description"]), reply_markup=get_back_keyboard(db_user.language) ) - logger.info(f"✅ Пользователь {db_user.telegram_id} активировал промокод {code}") - - try: - notification_service = AdminNotificationService(message.bot) - await notification_service.send_promocode_activation_notification( - db, - db_user, - result.get("promocode", {"code": code}), - result["description"], - ) - except Exception as notify_error: - logger.error( - "Ошибка отправки админ уведомления об активации промокода %s: %s", - code, - notify_error, - ) else: error_messages = { "not_found": texts.PROMOCODE_INVALID, @@ -85,13 +106,13 @@ async def process_promocode( "already_used_by_user": texts.PROMOCODE_USED, "server_error": texts.ERROR } - + error_text = error_messages.get(result["error"], texts.PROMOCODE_INVALID) await message.answer( error_text, reply_markup=get_back_keyboard(db_user.language) ) - + await state.clear() diff --git a/app/handlers/start.py b/app/handlers/start.py index abeeb10a..16a171e2 100644 --- a/app/handlers/start.py +++ b/app/handlers/start.py @@ -100,15 +100,15 @@ async def handle_potential_referral_code( db: AsyncSession ): current_state = await state.get_state() - logger.info(f"🔍 REFERRAL CHECK: Проверка сообщения '{message.text}' в состоянии {current_state}") - + logger.info(f"🔍 REFERRAL/PROMO CHECK: Проверка сообщения '{message.text}' в состоянии {current_state}") + if current_state not in [ RegistrationStates.waiting_for_rules_accept.state, RegistrationStates.waiting_for_referral_code.state, - None + None ]: return False - + user = await get_user_by_telegram_id(db, message.from_user.id) if user and user.status == UserStatus.ACTIVE.value: return False @@ -125,37 +125,73 @@ async def handle_potential_referral_code( if len(potential_code) < 4 or len(potential_code) > 20: return False + # Сначала проверяем реферальный код referrer = await get_user_by_referral_code(db, potential_code) - if not referrer: - await message.answer(texts.t( - "REFERRAL_CODE_INVALID_HELP", - "❌ Неверный реферальный код.\n\n" - "💡 Если у вас есть реферальный код, убедитесь что он введен правильно.\n" - "⏭️ Для продолжения регистрации без реферального кода используйте команду /start", - )) + if referrer: + data['referral_code'] = potential_code + data['referrer_id'] = referrer.id + await state.set_data(data) + + await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) + logger.info(f"✅ Реферальный код {potential_code} применен для пользователя {message.from_user.id}") + + if current_state != RegistrationStates.waiting_for_referral_code.state: + language = data.get('language', DEFAULT_LANGUAGE) + texts = get_texts(language) + + rules_text = await get_rules(language) + await message.answer( + rules_text, + reply_markup=get_rules_keyboard(language) + ) + await state.set_state(RegistrationStates.waiting_for_rules_accept) + logger.info("📋 Правила отправлены после ввода реферального кода") + else: + await complete_registration(message, state, db) + return True - data['referral_code'] = potential_code - data['referrer_id'] = referrer.id - await state.set_data(data) + # Если реферальный код не найден, проверяем промокод + from app.database.crud.promocode import check_promocode_validity + + promocode_check = await check_promocode_validity(db, potential_code) + + if promocode_check["valid"]: + # Промокод валиден - сохраняем его в state для активации после создания пользователя + data['promocode'] = potential_code + await state.set_data(data) - await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) - logger.info(f"✅ Реферальный код {potential_code} применен для пользователя {message.from_user.id}") - - if current_state != RegistrationStates.waiting_for_referral_code.state: - language = data.get('language', DEFAULT_LANGUAGE) - texts = get_texts(language) - - rules_text = await get_rules(language) await message.answer( - rules_text, - reply_markup=get_rules_keyboard(language) + texts.t( + "PROMOCODE_ACCEPTED_WILL_ACTIVATE", + "✅ Промокод принят! Он будет активирован после завершения регистрации." + ) ) - await state.set_state(RegistrationStates.waiting_for_rules_accept) - logger.info("📋 Правила отправлены после ввода реферального кода") - else: - await complete_registration(message, state, db) - + logger.info(f"✅ Промокод {potential_code} сохранен для активации для пользователя {message.from_user.id}") + + if current_state != RegistrationStates.waiting_for_referral_code.state: + language = data.get('language', DEFAULT_LANGUAGE) + texts = get_texts(language) + + rules_text = await get_rules(language) + await message.answer( + rules_text, + reply_markup=get_rules_keyboard(language) + ) + await state.set_state(RegistrationStates.waiting_for_rules_accept) + logger.info("📋 Правила отправлены после принятия промокода") + else: + await complete_registration(message, state, db) + + return True + + # Ни реферальный код, ни промокод не найдены + await message.answer(texts.t( + "REFERRAL_OR_PROMO_CODE_INVALID_HELP", + "❌ Неверный реферальный код или промокод.\n\n" + "💡 Если у вас есть реферальный код или промокод, убедитесь что он введен правильно.\n" + "⏭️ Для продолжения регистрации без кода используйте команду /start", + )) return True @@ -692,31 +728,57 @@ async def process_rules_accept( async def process_referral_code_input( - message: types.Message, - state: FSMContext, + message: types.Message, + state: FSMContext, db: AsyncSession ): - - logger.info(f"🎫 REFERRAL: Обработка реферального кода: {message.text}") - + + logger.info(f"🎫 REFERRAL/PROMO: Обработка кода: {message.text}") + data = await state.get_data() or {} language = data.get('language', DEFAULT_LANGUAGE) texts = get_texts(language) - referral_code = message.text.strip() + code = message.text.strip() - referrer = await get_user_by_referral_code(db, referral_code) + # Сначала проверяем, является ли это реферальным кодом + referrer = await get_user_by_referral_code(db, code) if referrer: data['referrer_id'] = referrer.id await state.set_data(data) await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) - logger.info(f"✅ Реферальный код применен") - else: - await message.answer(texts.t("REFERRAL_CODE_INVALID", "❌ Неверный реферальный код")) - logger.info(f"❌ Неверный реферальный код") + logger.info(f"✅ Реферальный код применен: {code}") + await complete_registration(message, state, db) return - - await complete_registration(message, state, db) + + # Если реферальный код не найден, проверяем промокод + from app.database.crud.promocode import check_promocode_validity + + promocode_check = await check_promocode_validity(db, code) + + if promocode_check["valid"]: + # Промокод валиден - сохраняем его в state для активации после создания пользователя + data['promocode'] = code + await state.set_data(data) + await message.answer( + texts.t( + "PROMOCODE_ACCEPTED_WILL_ACTIVATE", + "✅ Промокод принят! Он будет активирован после завершения регистрации." + ) + ) + logger.info(f"✅ Промокод сохранен для активации: {code}") + await complete_registration(message, state, db) + return + + # Ни реферальный код, ни промокод не найдены + await message.answer( + texts.t( + "REFERRAL_OR_PROMO_CODE_INVALID", + "❌ Неверный реферальный код или промокод" + ) + ) + logger.info(f"❌ Неверный код (ни реферальный, ни промокод): {code}") + return async def process_referral_code_skip( @@ -1157,6 +1219,29 @@ async def complete_registration( except Exception as e: logger.error(f"Ошибка при обработке реферальной регистрации: {e}") + # Активируем промокод если был сохранен в state + promocode_to_activate = data.get('promocode') + if promocode_to_activate: + try: + from app.handlers.promocode import activate_promocode_for_registration + + promocode_result = await activate_promocode_for_registration( + db, user.id, promocode_to_activate, message.bot + ) + + if promocode_result["success"]: + await message.answer( + texts.t( + "PROMOCODE_ACTIVATED_AT_REGISTRATION", + "✅ Промокод активирован!\n\n{description}" + ).format(description=promocode_result["description"]) + ) + logger.info(f"✅ Промокод {promocode_to_activate} активирован для пользователя {user.id}") + else: + logger.warning(f"⚠️ Не удалось активировать промокод {promocode_to_activate}: {promocode_result.get('error')}") + except Exception as e: + logger.error(f"❌ Ошибка при активации промокода {promocode_to_activate}: {e}") + campaign_message = await _apply_campaign_bonus_if_needed(db, user, data, texts) try: diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json index 78232619..0a92e40a 100644 --- a/app/localization/locales/en.json +++ b/app/localization/locales/en.json @@ -1133,7 +1133,11 @@ "REFERRAL_CODE_APPLIED": "🎁 Referral code applied! You will receive a bonus after the first purchase.", "REFERRAL_CODE_INVALID": "❌ Invalid referral code", "REFERRAL_CODE_INVALID_HELP": "❌ Invalid referral code.\n\n💡 If you have a referral code, please double-check the spelling.\n⏭️ To continue without a referral code, use the /start command.", - "REFERRAL_CODE_QUESTION": "\n🤝 Do you have a friend's referral code?\n\nIf you have a promo code or referral link, enter it now to receive a bonus!\n\nSend the code or tap \"Skip\":\n", + "REFERRAL_CODE_QUESTION": "\n🤝 Do you have a referral code or promo code?\n\nIf you have a promo code or referral link from a friend, enter it now to receive a bonus!\n\nSend the code or tap \"Skip\":\n", + "REFERRAL_OR_PROMO_CODE_INVALID": "❌ Invalid referral code or promo code", + "REFERRAL_OR_PROMO_CODE_INVALID_HELP": "❌ Invalid referral code or promo code.\n\n💡 If you have a referral code or promo code, please double-check the spelling.\n⏭️ To continue registration without a code, use the /start command.", + "PROMOCODE_ACTIVATED_AT_REGISTRATION": "✅ Promo code activated!\n\n{description}", + "PROMOCODE_ACCEPTED_WILL_ACTIVATE": "✅ Promo code accepted! It will be activated after registration is complete.", "REFERRAL_CODE_SKIP": "⏭️ Skip", "REFERRAL_CODE_TITLE": "🆔 Your code: {code}", "REFERRAL_EARNINGS_BY_TYPE_HEADER": "📈 Earnings by type:", diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index ad2d8e54..da49f786 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -1153,7 +1153,11 @@ "REFERRAL_CODE_APPLIED": "🎁 Реферальный код применен! Вы получите бонус после первой покупки.", "REFERRAL_CODE_INVALID": "❌ Неверный реферальный код", "REFERRAL_CODE_INVALID_HELP": "❌ Неверный реферальный код.\n\n💡 Если у вас есть реферальный код, убедитесь что он введен правильно.\n⏭️ Для продолжения регистрации без реферального кода используйте команду /start", - "REFERRAL_CODE_QUESTION": "\n🤝 У вас есть реферальный код от друга?\n\nЕсли у вас есть промокод или реферальная ссылка от друга, введите её сейчас, чтобы получить бонус!\n\nВведите код или нажмите \"Пропустить\":\n", + "REFERRAL_CODE_QUESTION": "\n🤝 У вас есть реферальный код или промокод?\n\nЕсли у вас есть промокод или реферальная ссылка от друга, введите её сейчас, чтобы получить бонус!\n\nВведите код или нажмите \"Пропустить\":\n", + "REFERRAL_OR_PROMO_CODE_INVALID": "❌ Неверный реферальный код или промокод", + "REFERRAL_OR_PROMO_CODE_INVALID_HELP": "❌ Неверный реферальный код или промокод.\n\n💡 Если у вас есть реферальный код или промокод, убедитесь что он введен правильно.\n⏭️ Для продолжения регистрации без кода используйте команду /start", + "PROMOCODE_ACTIVATED_AT_REGISTRATION": "✅ Промокод активирован!\n\n{description}", + "PROMOCODE_ACCEPTED_WILL_ACTIVATE": "✅ Промокод принят! Он будет активирован после завершения регистрации.", "REFERRAL_CODE_SKIP": "⏭️ Пропустить", "REFERRAL_CODE_TITLE": "🆔 Ваш код: {code}", "REFERRAL_EARNINGS_BY_TYPE_HEADER": "📈 Доходы по типам:",