156 lines
6.5 KiB
Python
156 lines
6.5 KiB
Python
# ruff: noqa: F401,F403,F405,I001
|
|
from ._runtime import * # noqa: F403,F405
|
|
|
|
|
|
class PaymentContextMixin:
|
|
# Human-readable provider names rendered in payment-success emails.
|
|
# Keys are the lowercased value persisted in ``subscriptions.provider``
|
|
# (see the call sites in lifecycle.py / traffic.py); missing keys produce
|
|
# no row in the email rather than raising.
|
|
_PROVIDER_LABELS = {
|
|
"yookassa": "YooKassa",
|
|
"freekassa": "FreeKassa",
|
|
"platega": "Platega",
|
|
"severpay": "SeverPay",
|
|
"wata": "Wata",
|
|
"cryptopay": "Crypto Pay",
|
|
"paykilla": "PayKilla",
|
|
"telegram_stars": "Telegram Stars",
|
|
}
|
|
|
|
async def _record_payment_context(
|
|
self,
|
|
session: AsyncSession,
|
|
payment_db_id: int,
|
|
*,
|
|
sale_mode: str,
|
|
tariff_key: Optional[str],
|
|
purchased_gb: Optional[float] = None,
|
|
purchased_hwid_devices: Optional[int] = None,
|
|
hwid_valid_from: Optional[datetime] = None,
|
|
hwid_valid_until: Optional[datetime] = None,
|
|
hwid_pricing_period_months: Optional[int] = None,
|
|
hwid_proration_ratio: Optional[float] = None,
|
|
hwid_full_price: Optional[float] = None,
|
|
) -> None:
|
|
payment = await payment_dal.get_payment_by_db_id(session, payment_db_id)
|
|
if not payment:
|
|
return
|
|
payment.sale_mode = sale_mode
|
|
payment.tariff_key = tariff_key
|
|
payment.purchased_gb = purchased_gb
|
|
if purchased_hwid_devices is not None:
|
|
payment.purchased_hwid_devices = purchased_hwid_devices
|
|
if hwid_valid_from is not None:
|
|
payment.hwid_valid_from = hwid_valid_from
|
|
if hwid_valid_until is not None:
|
|
payment.hwid_valid_until = hwid_valid_until
|
|
if hwid_pricing_period_months is not None:
|
|
payment.hwid_pricing_period_months = hwid_pricing_period_months
|
|
if hwid_proration_ratio is not None:
|
|
payment.hwid_proration_ratio = hwid_proration_ratio
|
|
if hwid_full_price is not None:
|
|
payment.hwid_full_price = hwid_full_price
|
|
await session.flush()
|
|
|
|
async def get_user_language(self, session: AsyncSession, user_id: int) -> str:
|
|
user_record = await user_dal.get_user_by_id(session, user_id)
|
|
return (
|
|
user_record.language_code
|
|
if user_record and user_record.language_code
|
|
else self.settings.DEFAULT_LANGUAGE
|
|
)
|
|
|
|
async def has_had_any_subscription(self, session: AsyncSession, user_id: int) -> bool:
|
|
return await subscription_dal.has_any_subscription_for_user(session, user_id)
|
|
|
|
async def has_trial_blocking_subscription(self, session: AsyncSession, user_id: int) -> bool:
|
|
return await subscription_dal.has_trial_blocking_subscription_for_user(session, user_id)
|
|
|
|
async def has_active_subscription(self, session: AsyncSession, user_id: int) -> bool:
|
|
"""Return True if user currently has an active subscription (end_date in future)."""
|
|
try:
|
|
user_record = await user_dal.get_user_by_id(session, user_id)
|
|
if not user_record or not user_record.panel_user_uuid:
|
|
return False
|
|
active_sub = await subscription_dal.get_active_subscription_by_user_id(
|
|
session, user_id, user_record.panel_user_uuid
|
|
)
|
|
if not active_sub or not active_sub.end_date:
|
|
return False
|
|
from datetime import datetime, timezone
|
|
|
|
return active_sub.is_active and active_sub.end_date > datetime.now(timezone.utc)
|
|
except Exception:
|
|
return False
|
|
|
|
async def _send_payment_success_email(
|
|
self,
|
|
*,
|
|
db_user: User,
|
|
sale_mode: str,
|
|
months: int,
|
|
traffic_gb: Optional[float],
|
|
payment_amount: float,
|
|
end_date: Optional[datetime],
|
|
provider: str,
|
|
) -> None:
|
|
"""Best-effort branded email confirming the payment. No-op if SMTP or
|
|
the user's email aren't set. Failures are logged and swallowed so the
|
|
payment flow is never blocked by mail delivery."""
|
|
if not getattr(self.settings, "email_auth_configured", False):
|
|
return
|
|
recipient = (db_user.email or "").strip() if db_user else ""
|
|
if not recipient:
|
|
return
|
|
|
|
end_date_text = end_date.strftime("%Y-%m-%d") if end_date else ""
|
|
try:
|
|
from bot.payment_providers import provider_label_map
|
|
|
|
provider_label = provider_label_map(
|
|
self.settings,
|
|
db_user.language_code or self.settings.DEFAULT_LANGUAGE,
|
|
).get((provider or "").lower())
|
|
except Exception:
|
|
provider_label = self._PROVIDER_LABELS.get((provider or "").lower())
|
|
dashboard_url = (self.settings.SUBSCRIPTION_MINI_APP_URL or "").strip() or None
|
|
i18n = getattr(self, "i18n", None)
|
|
|
|
try:
|
|
content = render_payment_success(
|
|
self.settings,
|
|
language_code=db_user.language_code or self.settings.DEFAULT_LANGUAGE,
|
|
sale_mode=sale_mode,
|
|
months=int(months or 0),
|
|
traffic_gb=traffic_gb,
|
|
amount=float(payment_amount or 0),
|
|
currency=default_payment_currency_code_for_settings(self.settings),
|
|
end_date_text=end_date_text,
|
|
dashboard_url=dashboard_url,
|
|
provider_label=provider_label,
|
|
i18n=i18n,
|
|
)
|
|
email_service = EmailAuthService(self.settings, i18n)
|
|
await email_service.send_rendered_email(email=recipient, content=content)
|
|
except Exception:
|
|
logging.exception("Failed to send payment success email to user %s", db_user.user_id)
|
|
|
|
async def update_last_notification_sent(
|
|
self, session: AsyncSession, user_id: int, subscription_end_date: datetime
|
|
):
|
|
sub_to_update = await subscription_dal.find_subscription_for_notification_update(
|
|
session, user_id, subscription_end_date
|
|
)
|
|
if sub_to_update:
|
|
await subscription_dal.update_subscription_notification_time(
|
|
session, sub_to_update.subscription_id, datetime.now(timezone.utc)
|
|
)
|
|
logging.info(
|
|
f"Updated last_notification_sent for user {user_id}, sub_id {sub_to_update.subscription_id}" # noqa: E501
|
|
)
|
|
else:
|
|
logging.warning(
|
|
f"Could not find subscription for user {user_id} ending at {subscription_end_date.isoformat()} to update notification time." # noqa: E501
|
|
)
|