Files
remnawave-minishop/backend/bot/services/subscription_service_impl/payments.py
T

151 lines
6.2 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",
"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
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_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=self.settings.DEFAULT_CURRENCY_SYMBOL,
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
)