diff --git a/app/database/crud/promo_offer_template.py b/app/database/crud/promo_offer_template.py
index 9ebdd5fd..3f289b78 100644
--- a/app/database/crud/promo_offer_template.py
+++ b/app/database/crud/promo_offer_template.py
@@ -10,6 +10,36 @@ from app.config import settings
from app.database.models import PromoOfferTemplate
+UPDATED_TEMPLATE_MESSAGES = {
+ "extend_discount": (
+ "π ΠΠΊΠΎΠ½ΠΎΠΌΠΈΡ {discount_percent}% ΠΏΡΠΈ ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΠΈ\n\n"
+ "Π‘ΠΊΠΈΠ΄ΠΊΠ° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠΉ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
+ "Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΡ β {valid_hours} Ρ."
+ ),
+ "purchase_discount": (
+ "π― ΠΠ΅ΡΠ½ΠΈΡΠ΅ΡΡ ΡΠΎ ΡΠΊΠΈΠ΄ΠΊΠΎΠΉ {discount_percent}%\n\n"
+ "Π‘ΠΊΠΈΠ΄ΠΊΠ° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠΉ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
+ "ΠΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ {valid_hours} Ρ."
+ ),
+}
+
+
+LEGACY_TEMPLATE_MESSAGES = {
+ "extend_discount": (
+ "π ΠΠΊΠΎΠ½ΠΎΠΌΠΈΡ {discount_percent}% ΠΏΡΠΈ ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΠΈ\n\n"
+ "ΠΠΊΡΠΈΠ²ΠΈΡΡΠΉΡΠ΅ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈ ΠΏΠΎΠ»ΡΡΠΈΡΠ΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ ΡΠΊΠΈΠ΄ΠΊΡ Π½Π° ΠΎΠΏΠ»Π°ΡΡ ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΡ. "
+ "ΠΠ½Π° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ Π²Π°ΡΠΈΠΌΠΈ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠ²ΡΠΌΠΈ ΡΠΊΠΈΠ΄ΠΊΠ°ΠΌΠΈ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
+ "Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΡ β {valid_hours} Ρ."
+ ),
+ "purchase_discount": (
+ "π― ΠΠ΅ΡΠ½ΠΈΡΠ΅ΡΡ ΡΠΎ ΡΠΊΠΈΠ΄ΠΊΠΎΠΉ {discount_percent}%\n\n"
+ "ΠΠΎΡΠ»Π΅ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΈ ΠΌΡ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ ΡΠΊΠΈΠ΄ΠΊΡ ΠΊ Π²Π°ΡΠ΅ΠΉ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΉ ΠΎΠΏΠ»Π°ΡΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ. "
+ "Π‘ΠΊΠΈΠ΄ΠΊΠ° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠΉ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
+ "ΠΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ {valid_hours} Ρ."
+ ),
+}
+
+
DEFAULT_TEMPLATES: tuple[dict, ...] = (
{
"offer_type": "test_access",
@@ -29,12 +59,7 @@ DEFAULT_TEMPLATES: tuple[dict, ...] = (
{
"offer_type": "extend_discount",
"name": "Π‘ΠΊΠΈΠ΄ΠΊΠ° Π½Π° ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΠ΅",
- "message_text": (
- "π ΠΠΊΠΎΠ½ΠΎΠΌΠΈΡ {discount_percent}% ΠΏΡΠΈ ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΠΈ\n\n"
- "ΠΠΊΡΠΈΠ²ΠΈΡΡΠΉΡΠ΅ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈ ΠΏΠΎΠ»ΡΡΠΈΡΠ΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ ΡΠΊΠΈΠ΄ΠΊΡ Π½Π° ΠΎΠΏΠ»Π°ΡΡ ΠΏΡΠΎΠ΄Π»Π΅Π½ΠΈΡ. "
- "ΠΠ½Π° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ Π²Π°ΡΠΈΠΌΠΈ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠ²ΡΠΌΠΈ ΡΠΊΠΈΠ΄ΠΊΠ°ΠΌΠΈ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
- "Π‘ΡΠΎΠΊ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΡ β {valid_hours} Ρ."
- ),
+ "message_text": UPDATED_TEMPLATE_MESSAGES["extend_discount"],
"button_text": "π ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠΊΠΈΠ΄ΠΊΡ",
"valid_hours": 24,
"discount_percent": 20,
@@ -45,12 +70,7 @@ DEFAULT_TEMPLATES: tuple[dict, ...] = (
{
"offer_type": "purchase_discount",
"name": "Π‘ΠΊΠΈΠ΄ΠΊΠ° Π½Π° ΠΏΠΎΠΊΡΠΏΠΊΡ",
- "message_text": (
- "π― ΠΠ΅ΡΠ½ΠΈΡΠ΅ΡΡ ΡΠΎ ΡΠΊΠΈΠ΄ΠΊΠΎΠΉ {discount_percent}%\n\n"
- "ΠΠΎΡΠ»Π΅ Π°ΠΊΡΠΈΠ²Π°ΡΠΈΠΈ ΠΌΡ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ ΡΠΊΠΈΠ΄ΠΊΡ ΠΊ Π²Π°ΡΠ΅ΠΉ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΉ ΠΎΠΏΠ»Π°ΡΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ. "
- "Π‘ΠΊΠΈΠ΄ΠΊΠ° ΡΡΠΌΠΌΠΈΡΡΠ΅ΡΡΡ Ρ ΠΏΡΠΎΠΌΠΎΠ³ΡΡΠΏΠΏΠΎΠΉ ΠΈ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π·.\n"
- "ΠΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π΄Π΅ΠΉΡΡΠ²ΡΠ΅Ρ {valid_hours} Ρ."
- ),
+ "message_text": UPDATED_TEMPLATE_MESSAGES["purchase_discount"],
"button_text": "π ΠΠ°Π±ΡΠ°ΡΡ ΡΠΊΠΈΠ΄ΠΊΡ",
"valid_hours": 48,
"discount_percent": 25,
@@ -80,6 +100,21 @@ async def ensure_default_templates(db: AsyncSession, *, created_by: Optional[int
)
existing = result.scalars().first()
if existing:
+ new_message = UPDATED_TEMPLATE_MESSAGES.get(template_data["offer_type"])
+ legacy_message = LEGACY_TEMPLATE_MESSAGES.get(template_data["offer_type"])
+ should_update = False
+
+ if new_message and legacy_message and existing.message_text == legacy_message:
+ should_update = True
+ elif new_message and (
+ "{bonus_amount" in existing.message_text or "ΠΡ Π½Π°ΡΠΈΡΠ»ΠΈΠΌ" in existing.message_text
+ ):
+ should_update = True
+
+ if should_update and new_message:
+ existing.message_text = new_message
+ existing.updated_at = datetime.utcnow()
+ await db.flush()
templates.append(existing)
continue
diff --git a/app/database/crud/user.py b/app/database/crud/user.py
index 147c8fd3..9dacb13b 100644
--- a/app/database/crud/user.py
+++ b/app/database/crud/user.py
@@ -273,6 +273,8 @@ async def subtract_user_balance(
description: str,
create_transaction: bool = False,
payment_method: Optional[PaymentMethod] = None,
+ *,
+ consume_promo_offer: bool = False,
) -> bool:
logger.error(f"πΈ ΠΠ’ΠΠΠΠΠ subtract_user_balance:")
logger.error(f" π€ User ID: {user.id} (TG: {user.telegram_id})")
@@ -287,6 +289,11 @@ async def subtract_user_balance(
try:
old_balance = user.balance_kopeks
user.balance_kopeks -= amount_kopeks
+
+ if consume_promo_offer and getattr(user, "promo_offer_discount_percent", 0):
+ user.promo_offer_discount_percent = 0
+ user.promo_offer_discount_source = None
+
user.updated_at = datetime.utcnow()
await db.commit()
diff --git a/app/handlers/admin/promo_offers.py b/app/handlers/admin/promo_offers.py
index bac92821..1f308312 100644
--- a/app/handlers/admin/promo_offers.py
+++ b/app/handlers/admin/promo_offers.py
@@ -113,7 +113,7 @@ def _build_offer_detail_keyboard(template: PromoOfferTemplate, language: str) ->
InlineKeyboardButton(text="π¬ ΠΡΠΏΡΠ°Π²ΠΈΡΡ", callback_data=f"promo_offer_send_menu_{template.id}"),
])
rows.append([
- InlineKeyboardButton(text=texts.BACK, callback_data="admin_messages"),
+ InlineKeyboardButton(text=texts.BACK, callback_data="admin_promo_offers"),
])
return InlineKeyboardMarkup(inline_keyboard=rows)
diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py
index ec511aac..2e5280bd 100644
--- a/app/handlers/subscription.py
+++ b/app/handlers/subscription.py
@@ -133,18 +133,6 @@ def _apply_promo_offer_discount(user: Optional[User], amount: int) -> Dict[str,
return {"discounted": discounted, "discount": discount_value, "percent": percent}
-async def _consume_promo_offer_discount(db: AsyncSession, user: User) -> None:
- if _get_promo_offer_discount_percent(user) <= 0:
- return
-
- user.promo_offer_discount_percent = 0
- user.promo_offer_discount_source = None
- user.updated_at = datetime.utcnow()
-
- await db.commit()
- await db.refresh(user)
-
-
def _get_period_hint_from_subscription(subscription: Optional[Subscription]) -> Optional[int]:
if not subscription:
return None
@@ -2891,8 +2879,11 @@ async def confirm_extend_subscription(
try:
success = await subtract_user_balance(
- db, db_user, price,
- f"ΠΡΠΎΠ΄Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° {days} Π΄Π½Π΅ΠΉ"
+ db,
+ db_user,
+ price,
+ f"ΠΡΠΎΠ΄Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° {days} Π΄Π½Π΅ΠΉ",
+ consume_promo_offer=promo_component["discount"] > 0,
)
if not success:
@@ -2989,9 +2980,6 @@ async def confirm_extend_subscription(
f" -{texts.format_price(promo_component['discount'])})"
)
- if promo_component["discount"] > 0:
- await _consume_promo_offer_discount(db, db_user)
-
await callback.message.edit_text(
success_message,
reply_markup=get_back_keyboard(db_user.language)
@@ -3760,8 +3748,11 @@ async def confirm_purchase(
try:
success = await subtract_user_balance(
- db, db_user, final_price,
- f"ΠΠΎΠΊΡΠΏΠΊΠ° ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° {data['period_days']} Π΄Π½Π΅ΠΉ"
+ db,
+ db_user,
+ final_price,
+ f"ΠΠΎΠΊΡΠΏΠΊΠ° ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π½Π° {data['period_days']} Π΄Π½Π΅ΠΉ",
+ consume_promo_offer=promo_offer_discount_value > 0,
)
if not success:
@@ -4043,9 +4034,6 @@ async def confirm_purchase(
callback_data="back_to_menu")],
])
- if promo_offer_discount_value > 0:
- await _consume_promo_offer_discount(db, db_user)
-
await callback.message.edit_text(
success_text,
reply_markup=connect_keyboard,
@@ -4055,9 +4043,6 @@ async def confirm_purchase(
purchase_text = texts.SUBSCRIPTION_PURCHASED
if discount_note:
purchase_text = f"{purchase_text}\n\n{discount_note}"
- if promo_offer_discount_value > 0:
- await _consume_promo_offer_discount(db, db_user)
-
await callback.message.edit_text(
texts.t(
"SUBSCRIPTION_LINK_GENERATING_NOTICE",