fix: bind HWID top-ups to subscription periods
This commit is contained in:
@@ -3,6 +3,190 @@ from ._runtime import * # noqa: F403,F405
|
||||
|
||||
|
||||
class HwidDeviceMixin:
|
||||
@staticmethod
|
||||
def _as_aware_utc(value: Optional[datetime]) -> Optional[datetime]:
|
||||
if value is None:
|
||||
return None
|
||||
if value.tzinfo is None:
|
||||
return value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
async def _active_hwid_extra_devices_for_sub(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
sub: Subscription,
|
||||
*,
|
||||
at: Optional[datetime] = None,
|
||||
) -> int:
|
||||
try:
|
||||
return await tariff_dal.sum_active_hwid_devices(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
at=at or datetime.now(timezone.utc),
|
||||
)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Failed to recalculate active HWID devices for subscription %s",
|
||||
getattr(sub, "subscription_id", None),
|
||||
)
|
||||
return int(getattr(sub, "extra_hwid_devices", 0) or 0)
|
||||
|
||||
async def _hwid_topup_validity_window(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
sub: Subscription,
|
||||
*,
|
||||
renewal: bool,
|
||||
now: datetime,
|
||||
) -> Optional[Tuple[datetime, datetime, Dict[str, Any]]]:
|
||||
valid_until = self._as_aware_utc(getattr(sub, "end_date", None))
|
||||
if not valid_until or valid_until <= now:
|
||||
return None
|
||||
|
||||
summary = await tariff_dal.get_hwid_device_entitlement_summary(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
at=now,
|
||||
)
|
||||
valid_from = now
|
||||
if renewal:
|
||||
active_until = self._as_aware_utc(summary.get("active_until"))
|
||||
if active_until and now < active_until < valid_until:
|
||||
valid_from = active_until
|
||||
elif active_until and active_until >= valid_until:
|
||||
return None
|
||||
return valid_from, valid_until, summary
|
||||
|
||||
@staticmethod
|
||||
def _round_hwid_price(value: float, *, currency: str) -> float:
|
||||
if value <= 0:
|
||||
return 0.0
|
||||
if currency == "stars":
|
||||
return float(math.ceil(value))
|
||||
return math.ceil(float(value) * 100) / 100
|
||||
|
||||
@staticmethod
|
||||
def _find_hwid_package(tariff: Tariff, device_count: int, currency: str) -> Optional[Any]:
|
||||
package_set = tariff.hwid_device_packages
|
||||
if not package_set:
|
||||
return None
|
||||
packages = package_set.for_currency("stars" if currency == "stars" else "rub")
|
||||
return next((pkg for pkg in packages if int(pkg.count) == int(device_count)), None)
|
||||
|
||||
def _quote_hwid_package_price(
|
||||
self,
|
||||
*,
|
||||
sub: Subscription,
|
||||
package: Any,
|
||||
valid_from: datetime,
|
||||
valid_until: datetime,
|
||||
now: datetime,
|
||||
currency: str,
|
||||
) -> Dict[str, Any]:
|
||||
period_months = max(1, int(getattr(sub, "duration_months", None) or 1))
|
||||
full_price = float(package.price_for_period(period_months))
|
||||
period_start = self._as_aware_utc(getattr(sub, "start_date", None))
|
||||
period_end = self._as_aware_utc(getattr(sub, "end_date", None)) or valid_until
|
||||
if not period_start or period_start >= period_end:
|
||||
period_start = valid_from
|
||||
period_end = valid_until
|
||||
|
||||
basis_seconds = max(1.0, (period_end - period_start).total_seconds())
|
||||
billable_start = max(now, valid_from)
|
||||
billable_seconds = max(0.0, (valid_until - billable_start).total_seconds())
|
||||
ratio = billable_seconds / basis_seconds
|
||||
raw_price = full_price * ratio
|
||||
price = self._round_hwid_price(raw_price, currency=currency)
|
||||
min_price = getattr(package, "min_price", None)
|
||||
if raw_price > 0 and min_price is not None:
|
||||
price = max(price, self._round_hwid_price(float(min_price), currency=currency))
|
||||
if currency == "stars":
|
||||
price = float(int(math.ceil(price)))
|
||||
|
||||
return {
|
||||
"price": price,
|
||||
"full_price": full_price,
|
||||
"pricing_period_months": period_months,
|
||||
"proration_ratio": ratio,
|
||||
"valid_from": valid_from,
|
||||
"valid_until": valid_until,
|
||||
"billable_seconds": billable_seconds,
|
||||
"period_seconds": basis_seconds,
|
||||
"currency": currency,
|
||||
}
|
||||
|
||||
async def quote_hwid_device_topup(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
*,
|
||||
user_id: int,
|
||||
device_count: int,
|
||||
tariff_key: Optional[str] = None,
|
||||
renewal: bool = False,
|
||||
currency: str = "rub",
|
||||
now: Optional[datetime] = None,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
purchased_devices = int(device_count)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if purchased_devices <= 0:
|
||||
return None
|
||||
|
||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||
if not db_user or not db_user.panel_user_uuid:
|
||||
return None
|
||||
sub = await subscription_dal.get_active_subscription_by_user_id(
|
||||
session, user_id, db_user.panel_user_uuid
|
||||
)
|
||||
if not sub:
|
||||
return None
|
||||
|
||||
tariff = self._resolve_tariff(tariff_key or sub.tariff_key)
|
||||
if not tariff or tariff.billing_model != "period":
|
||||
return None
|
||||
base_hwid_limit = (
|
||||
int(sub.hwid_device_limit)
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
if base_hwid_limit == 0:
|
||||
return None
|
||||
|
||||
package = self._find_hwid_package(tariff, purchased_devices, currency)
|
||||
if not package:
|
||||
return None
|
||||
|
||||
now = now or datetime.now(timezone.utc)
|
||||
window = await self._hwid_topup_validity_window(
|
||||
session,
|
||||
sub,
|
||||
renewal=renewal,
|
||||
now=now,
|
||||
)
|
||||
if not window:
|
||||
return None
|
||||
valid_from, valid_until, entitlement_summary = window
|
||||
quote = self._quote_hwid_package_price(
|
||||
sub=sub,
|
||||
package=package,
|
||||
valid_from=valid_from,
|
||||
valid_until=valid_until,
|
||||
now=now,
|
||||
currency="stars" if currency == "stars" else "rub",
|
||||
)
|
||||
quote.update(
|
||||
{
|
||||
"subscription_id": sub.subscription_id,
|
||||
"tariff_key": tariff.key,
|
||||
"device_count": purchased_devices,
|
||||
"renewal": renewal,
|
||||
"active_extra_devices": int(entitlement_summary.get("active_devices") or 0),
|
||||
"active_until": entitlement_summary.get("active_until"),
|
||||
}
|
||||
)
|
||||
return quote
|
||||
|
||||
async def activate_hwid_device_topup(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
@@ -12,6 +196,7 @@ class HwidDeviceMixin:
|
||||
payment_db_id: int,
|
||||
provider: str = "yookassa",
|
||||
tariff_key: Optional[str] = None,
|
||||
renewal: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
purchased_devices = int(device_count)
|
||||
@@ -33,6 +218,14 @@ class HwidDeviceMixin:
|
||||
tariff = None
|
||||
if self._tariffs_config():
|
||||
tariff = self._resolve_tariff(tariff_key or sub.tariff_key)
|
||||
if tariff.billing_model != "period":
|
||||
logging.info(
|
||||
"Skipping HWID top-up for user %s because tariff %s is %s",
|
||||
user_id,
|
||||
tariff.key,
|
||||
tariff.billing_model,
|
||||
)
|
||||
return None
|
||||
packages = (
|
||||
[*tariff.hwid_device_packages.rub, *tariff.hwid_device_packages.stars]
|
||||
if tariff.hwid_device_packages
|
||||
@@ -66,14 +259,60 @@ class HwidDeviceMixin:
|
||||
"purchased_hwid_devices": 0,
|
||||
}
|
||||
|
||||
new_extra_devices = int(sub.extra_hwid_devices or 0) + purchased_devices
|
||||
now = datetime.now(timezone.utc)
|
||||
payment = await payment_dal.get_payment_by_db_id(session, payment_db_id)
|
||||
entitlement_summary = await tariff_dal.get_hwid_device_entitlement_summary(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
at=now,
|
||||
)
|
||||
valid_from = self._as_aware_utc(getattr(payment, "hwid_valid_from", None))
|
||||
valid_until = self._as_aware_utc(getattr(payment, "hwid_valid_until", None))
|
||||
if valid_from and valid_until:
|
||||
if valid_until <= now or valid_from >= valid_until:
|
||||
logging.error(
|
||||
"Frozen HWID quote is no longer valid for user %s "
|
||||
"(payment_id=%s, valid_from=%s, valid_until=%s)",
|
||||
user_id,
|
||||
payment_db_id,
|
||||
valid_from,
|
||||
valid_until,
|
||||
)
|
||||
return None
|
||||
else:
|
||||
window = await self._hwid_topup_validity_window(
|
||||
session,
|
||||
sub,
|
||||
renewal=renewal,
|
||||
now=now,
|
||||
)
|
||||
if window:
|
||||
valid_from, valid_until, entitlement_summary = window
|
||||
if not valid_from or not valid_until:
|
||||
logging.error(
|
||||
"HWID top-up has no valid subscription window for user %s "
|
||||
"(subscription_id=%s, renewal=%s)",
|
||||
user_id,
|
||||
sub.subscription_id,
|
||||
renewal,
|
||||
)
|
||||
return None
|
||||
|
||||
active_extra_devices = int(entitlement_summary.get("active_devices") or 0)
|
||||
starts_now = valid_from <= now < valid_until
|
||||
new_extra_devices = active_extra_devices + (purchased_devices if starts_now else 0)
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, new_extra_devices)
|
||||
await self._record_payment_context(
|
||||
session,
|
||||
payment_db_id,
|
||||
sale_mode="hwid_devices",
|
||||
sale_mode="hwid_devices_renewal" if renewal else "hwid_devices",
|
||||
tariff_key=tariff.key if tariff else sub.tariff_key,
|
||||
purchased_hwid_devices=purchased_devices,
|
||||
hwid_valid_from=valid_from,
|
||||
hwid_valid_until=valid_until,
|
||||
hwid_pricing_period_months=getattr(payment, "hwid_pricing_period_months", None),
|
||||
hwid_proration_ratio=getattr(payment, "hwid_proration_ratio", None),
|
||||
hwid_full_price=getattr(payment, "hwid_full_price", None),
|
||||
)
|
||||
updated_sub = await subscription_dal.update_subscription(
|
||||
session,
|
||||
@@ -115,6 +354,8 @@ class HwidDeviceMixin:
|
||||
subscription_id=updated_sub.subscription_id,
|
||||
payment_id=payment_db_id,
|
||||
purchased_devices=purchased_devices,
|
||||
valid_from=valid_from,
|
||||
valid_until=valid_until,
|
||||
)
|
||||
return {
|
||||
"subscription_id": updated_sub.subscription_id,
|
||||
@@ -127,4 +368,7 @@ class HwidDeviceMixin:
|
||||
"extra_hwid_devices": new_extra_devices,
|
||||
"purchased_hwid_devices": purchased_devices,
|
||||
"tariff_key": tariff.key if tariff else sub.tariff_key,
|
||||
"hwid_devices_valid_from": valid_from,
|
||||
"hwid_devices_valid_until": valid_until,
|
||||
"hwid_devices_renewal": renewal,
|
||||
}
|
||||
|
||||
@@ -23,8 +23,15 @@ class SubscriptionLifecycleMixin:
|
||||
if not sub:
|
||||
return None
|
||||
before_tariff_key = sub.tariff_key
|
||||
options = self.calculate_tariff_switch_options(sub, target)
|
||||
now = datetime.now(timezone.utc)
|
||||
options = await self.calculate_tariff_switch_options_with_hwid(session, sub, target)
|
||||
converted_hwid_purchase_ids = list(options.get("convertible_hwid_purchase_ids") or [])
|
||||
if converted_hwid_purchase_ids:
|
||||
await tariff_dal.expire_hwid_device_purchases(
|
||||
session,
|
||||
purchase_ids=converted_hwid_purchase_ids,
|
||||
at=now,
|
||||
)
|
||||
premium_topup_balance = int(sub.premium_topup_balance_bytes or 0)
|
||||
premium_topup_used = int(getattr(sub, "premium_topup_used_bytes", 0) or 0)
|
||||
premium_baseline = target.premium_monthly_bytes
|
||||
@@ -44,8 +51,20 @@ class SubscriptionLifecycleMixin:
|
||||
}
|
||||
converted_bytes = None
|
||||
base_hwid_limit = self._base_hwid_limit_for_tariff(target)
|
||||
extra_hwid_devices = int(sub.extra_hwid_devices or 0)
|
||||
try:
|
||||
extra_hwid_devices = await tariff_dal.sum_active_hwid_devices(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
at=now,
|
||||
)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Failed to recalculate HWID devices during tariff switch for user %s",
|
||||
user_id,
|
||||
)
|
||||
extra_hwid_devices = int(sub.extra_hwid_devices or 0)
|
||||
update_data["hwid_device_limit"] = base_hwid_limit
|
||||
update_data["extra_hwid_devices"] = extra_hwid_devices
|
||||
|
||||
if target.billing_model == "period":
|
||||
update_data["tier_baseline_bytes"] = target.monthly_bytes
|
||||
@@ -154,6 +173,8 @@ class SubscriptionLifecycleMixin:
|
||||
if updated.end_date and target.billing_model == "period"
|
||||
else None,
|
||||
"converted_bytes": converted_bytes,
|
||||
"converted_hwid_value_rub": options.get("converted_hwid_value_rub"),
|
||||
"converted_hwid_days": options.get("converted_hwid_days"),
|
||||
"eff_price_before": sub.effective_monthly_price_rub,
|
||||
"eff_price_after": updated.effective_monthly_price_rub,
|
||||
},
|
||||
@@ -236,7 +257,7 @@ class SubscriptionLifecycleMixin:
|
||||
payment_db_id=payment_db_id,
|
||||
provider=provider,
|
||||
)
|
||||
if sale_mode_base in {"hwid_device", "hwid_devices"}:
|
||||
if sale_mode_base in {"hwid_device", "hwid_devices", "hwid_devices_renewal"}:
|
||||
target_devices = int(traffic_gb if traffic_gb is not None else months)
|
||||
return await self.activate_hwid_device_topup(
|
||||
session=session,
|
||||
@@ -246,6 +267,7 @@ class SubscriptionLifecycleMixin:
|
||||
payment_db_id=payment_db_id,
|
||||
provider=provider,
|
||||
tariff_key=tariff_key,
|
||||
renewal=sale_mode_base == "hwid_devices_renewal",
|
||||
)
|
||||
if sale_mode_base == "tariff_upgrade":
|
||||
if not tariff_key:
|
||||
@@ -378,7 +400,25 @@ class SubscriptionLifecycleMixin:
|
||||
)
|
||||
|
||||
topup_balance_bytes = int(getattr(current_active_sub, "topup_balance_bytes", 0) or 0)
|
||||
extra_hwid_devices = int(getattr(current_active_sub, "extra_hwid_devices", 0) or 0)
|
||||
extra_hwid_devices = 0
|
||||
hwid_devices_valid_until = None
|
||||
if current_active_sub:
|
||||
try:
|
||||
hwid_summary = await tariff_dal.get_hwid_device_entitlement_summary(
|
||||
session,
|
||||
subscription_id=current_active_sub.subscription_id,
|
||||
at=datetime.now(timezone.utc),
|
||||
)
|
||||
extra_hwid_devices = int(hwid_summary.get("active_devices") or 0)
|
||||
hwid_devices_valid_until = hwid_summary.get("active_until")
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Failed to recalculate active HWID devices for renewal of user %s",
|
||||
user_id,
|
||||
)
|
||||
extra_hwid_devices = int(
|
||||
getattr(current_active_sub, "extra_hwid_devices", 0) or 0
|
||||
)
|
||||
premium_topup_balance_bytes = int(
|
||||
getattr(current_active_sub, "premium_topup_balance_bytes", 0) or 0
|
||||
)
|
||||
@@ -497,6 +537,8 @@ class SubscriptionLifecycleMixin:
|
||||
"subscription_url": final_subscription_url,
|
||||
"applied_promo_bonus_days": applied_promo_bonus_days,
|
||||
"tariff_key": tariff.key if tariff else None,
|
||||
"hwid_devices_renewal_recommended_count": extra_hwid_devices,
|
||||
"hwid_devices_valid_until": hwid_devices_valid_until,
|
||||
}
|
||||
|
||||
async def extend_active_subscription_days(
|
||||
@@ -760,6 +802,43 @@ class SubscriptionLifecycleMixin:
|
||||
if local_active_sub
|
||||
else False
|
||||
)
|
||||
hwid_entitlement_summary: Dict[str, Any] = {}
|
||||
active_extra_hwid_devices = (
|
||||
int(local_active_sub.extra_hwid_devices or 0) if local_active_sub else 0
|
||||
)
|
||||
if local_active_sub:
|
||||
try:
|
||||
hwid_entitlement_summary = await tariff_dal.get_hwid_device_entitlement_summary(
|
||||
session,
|
||||
subscription_id=local_active_sub.subscription_id,
|
||||
at=datetime.now(timezone.utc),
|
||||
)
|
||||
active_extra_hwid_devices = int(
|
||||
hwid_entitlement_summary.get("active_devices") or 0
|
||||
)
|
||||
if active_extra_hwid_devices != int(local_active_sub.extra_hwid_devices or 0):
|
||||
await subscription_dal.update_subscription(
|
||||
session,
|
||||
local_active_sub.subscription_id,
|
||||
{"extra_hwid_devices": active_extra_hwid_devices},
|
||||
)
|
||||
local_active_sub.extra_hwid_devices = active_extra_hwid_devices
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Failed to load HWID entitlement summary for subscription %s",
|
||||
local_active_sub.subscription_id,
|
||||
)
|
||||
base_hwid_limit_for_payload = (
|
||||
local_active_sub.hwid_device_limit
|
||||
if local_active_sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
expected_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit_for_payload,
|
||||
active_extra_hwid_devices,
|
||||
)
|
||||
if expected_hwid_limit is not None:
|
||||
hwid_limit = expected_hwid_limit
|
||||
|
||||
return {
|
||||
"user_id": panel_user_data.get("uuid"),
|
||||
@@ -820,9 +899,11 @@ class SubscriptionLifecycleMixin:
|
||||
"base_hwid_device_limit": local_active_sub.hwid_device_limit
|
||||
if local_active_sub
|
||||
else None,
|
||||
"extra_hwid_devices": int(local_active_sub.extra_hwid_devices or 0)
|
||||
if local_active_sub
|
||||
else 0,
|
||||
"extra_hwid_devices": active_extra_hwid_devices,
|
||||
"extra_hwid_devices_valid_until": hwid_entitlement_summary.get("active_until"),
|
||||
"extra_hwid_devices_next_valid_from": hwid_entitlement_summary.get(
|
||||
"next_valid_from"
|
||||
),
|
||||
"user_bot_username": db_user.username,
|
||||
"is_panel_data": True,
|
||||
"max_devices": hwid_limit,
|
||||
|
||||
@@ -26,6 +26,11 @@ class PaymentContextMixin:
|
||||
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:
|
||||
@@ -34,6 +39,16 @@ class PaymentContextMixin:
|
||||
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:
|
||||
|
||||
@@ -370,3 +370,91 @@ class TariffMixin:
|
||||
}
|
||||
|
||||
return {"mode": "traffic_to_period", "remaining_days": remaining_days}
|
||||
|
||||
@staticmethod
|
||||
def _aware_utc(value: Optional[datetime]) -> Optional[datetime]:
|
||||
if value is None:
|
||||
return None
|
||||
if value.tzinfo is None:
|
||||
return value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
async def _hwid_conversion_credit(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
sub: Subscription,
|
||||
*,
|
||||
at: datetime,
|
||||
) -> Dict[str, Any]:
|
||||
entries = await tariff_dal.get_hwid_device_value_entries(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
at=at,
|
||||
)
|
||||
value_rub = 0.0
|
||||
purchase_ids: List[int] = []
|
||||
skipped_devices = 0
|
||||
for entry in entries:
|
||||
currency = str(entry.get("currency") or "").upper()
|
||||
if currency in {"XTR", "STARS", "STAR"}:
|
||||
skipped_devices += int(entry.get("purchased_devices") or 0)
|
||||
continue
|
||||
amount = float(entry.get("amount") or 0)
|
||||
if amount <= 0:
|
||||
continue
|
||||
valid_from = (
|
||||
self._aware_utc(entry.get("valid_from"))
|
||||
or self._aware_utc(entry.get("created_at"))
|
||||
or at
|
||||
)
|
||||
valid_until = self._aware_utc(entry.get("valid_until"))
|
||||
if not valid_until or valid_until <= at or valid_from >= valid_until:
|
||||
continue
|
||||
total_seconds = max(1.0, (valid_until - valid_from).total_seconds())
|
||||
remaining_start = max(at, valid_from)
|
||||
remaining_seconds = max(0.0, (valid_until - remaining_start).total_seconds())
|
||||
if remaining_seconds <= 0:
|
||||
continue
|
||||
value_rub += amount * (remaining_seconds / total_seconds)
|
||||
purchase_ids.append(int(entry["purchase_id"]))
|
||||
return {
|
||||
"value_rub": value_rub,
|
||||
"purchase_ids": purchase_ids,
|
||||
"skipped_devices": skipped_devices,
|
||||
}
|
||||
|
||||
async def calculate_tariff_switch_options_with_hwid(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
sub: Subscription,
|
||||
target_tariff: Tariff,
|
||||
) -> Dict[str, Any]:
|
||||
options = dict(self.calculate_tariff_switch_options(sub, target_tariff))
|
||||
now = datetime.now(timezone.utc)
|
||||
credit = await self._hwid_conversion_credit(session, sub, at=now)
|
||||
value_rub = float(credit.get("value_rub") or 0)
|
||||
options["converted_hwid_value_rub"] = round(value_rub, 2)
|
||||
options["convertible_hwid_purchase_ids"] = list(credit.get("purchase_ids") or [])
|
||||
options["nonconverted_hwid_devices"] = int(credit.get("skipped_devices") or 0)
|
||||
if value_rub <= 0:
|
||||
return options
|
||||
|
||||
if options.get("mode") == "period_to_period":
|
||||
target_monthly = float(options.get("target_monthly_rub") or 0)
|
||||
hwid_days = (
|
||||
math.floor((value_rub / target_monthly) * 30) if target_monthly > 0 else 0
|
||||
)
|
||||
options["converted_hwid_days"] = max(0, hwid_days)
|
||||
options["recalc_days"] = int(options.get("recalc_days") or 0) + max(0, hwid_days)
|
||||
options["paid_diff_rub"] = max(
|
||||
0,
|
||||
math.ceil(float(options.get("paid_diff_rub") or 0) - value_rub),
|
||||
)
|
||||
return options
|
||||
|
||||
if options.get("mode") == "period_to_traffic":
|
||||
rub_per_gb = float(options.get("rub_per_gb") or 0)
|
||||
hwid_gb = math.floor(value_rub / rub_per_gb) if rub_per_gb > 0 else 0
|
||||
options["converted_hwid_gb"] = max(0, hwid_gb)
|
||||
options["converted_gb"] = int(options.get("converted_gb") or 0) + max(0, hwid_gb)
|
||||
return options
|
||||
|
||||
@@ -53,7 +53,11 @@ class TrafficMixin:
|
||||
current_used = active_sub.traffic_used_bytes
|
||||
|
||||
purchase_bytes = self.gb_to_bytes(traffic_gb)
|
||||
extra_hwid_devices = int(getattr(active_sub, "extra_hwid_devices", 0) or 0)
|
||||
extra_hwid_devices = (
|
||||
await self._active_hwid_extra_devices_for_sub(session, active_sub)
|
||||
if active_sub
|
||||
else 0
|
||||
)
|
||||
base_hwid_limit = self._base_hwid_limit_for_tariff(tariff)
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, extra_hwid_devices)
|
||||
remaining_bytes = max(0, int(current_limit or 0) - int(current_used or 0))
|
||||
@@ -222,10 +226,8 @@ class TrafficMixin:
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
effective_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit,
|
||||
int(sub.extra_hwid_devices or 0),
|
||||
)
|
||||
extra_hwid_devices = await self._active_hwid_extra_devices_for_sub(session, sub)
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, extra_hwid_devices)
|
||||
updated_sub = await subscription_dal.update_subscription(
|
||||
session,
|
||||
sub.subscription_id,
|
||||
@@ -235,6 +237,7 @@ class TrafficMixin:
|
||||
"is_throttled": False,
|
||||
"tariff_key": tariff.key,
|
||||
"hwid_device_limit": base_hwid_limit,
|
||||
"extra_hwid_devices": extra_hwid_devices,
|
||||
},
|
||||
)
|
||||
panel_payload = self._build_panel_update_payload(
|
||||
@@ -499,10 +502,8 @@ class TrafficMixin:
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
effective_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit,
|
||||
int(sub.extra_hwid_devices or 0),
|
||||
)
|
||||
extra_hwid_devices = await self._active_hwid_extra_devices_for_sub(session, sub)
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, extra_hwid_devices)
|
||||
updated_sub = await subscription_dal.update_subscription(
|
||||
session,
|
||||
sub.subscription_id,
|
||||
@@ -511,6 +512,7 @@ class TrafficMixin:
|
||||
"traffic_limit_bytes": new_limit,
|
||||
"is_throttled": False,
|
||||
"hwid_device_limit": base_hwid_limit,
|
||||
"extra_hwid_devices": extra_hwid_devices,
|
||||
},
|
||||
)
|
||||
panel_payload = self._build_panel_update_payload(
|
||||
@@ -580,10 +582,9 @@ class TrafficMixin:
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
effective_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit,
|
||||
int(sub.extra_hwid_devices or 0),
|
||||
)
|
||||
extra_hwid_devices = await self._active_hwid_extra_devices_for_sub(session, sub)
|
||||
sub.extra_hwid_devices = extra_hwid_devices
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, extra_hwid_devices)
|
||||
panel_payload = self._build_panel_update_payload(
|
||||
panel_user_uuid=db_user.panel_user_uuid,
|
||||
expire_at=sub.end_date,
|
||||
|
||||
Reference in New Issue
Block a user