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": "📈 Доходы по типам:",