Merge pull request #759 from Fr1ngg/bedolaga/fix-promo-discount-handling-and-messages

Fix promo discount consumption race and refresh promo offer texts
This commit is contained in:
Egor
2025-10-04 12:25:22 +03:00
committed by GitHub
4 changed files with 65 additions and 38 deletions

View File

@@ -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": (
"💎 <b>Экономия {discount_percent}% при продлении</b>\n\n"
"Активируйте предложение и получите дополнительную скидку на оплату продления. "
"Она суммируется с вашими промогрупповыми скидками и действует один раз.\n"
"Срок действия предложения — {valid_hours} ч."
),
"purchase_discount": (
"🎯 <b>Вернитесь со скидкой {discount_percent}%</b>\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": (
"💎 <b>Экономия {discount_percent}% при продлении</b>\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": (
"🎯 <b>Вернитесь со скидкой {discount_percent}%</b>\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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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",