fix: separate HWID device renewal flows

Keep one-off device top-ups scoped to the active subscription term and move device renewal into subscription checkout.

Carry HWID renewal metadata through provider callbacks and webhooks, including YooKassa saved-card flows.

Add admin extension controls, docs, demo data, and regression coverage.
This commit is contained in:
3252a8
2026-06-03 23:51:46 +03:00
parent a06884d816
commit fbb89793cb
48 changed files with 2410 additions and 224 deletions
+15 -3
View File
@@ -100,6 +100,11 @@ def build_payment_record_payload(
base = sale_mode_base(sale_mode)
is_traffic = sale_mode_is_traffic(sale_mode)
is_hwid = sale_mode_is_hwid_devices(sale_mode)
hwid_devices = int(float(months)) if is_hwid else None
if hwid_quote:
quote_devices = parse_positive_int_units(hwid_quote.get("device_count"))
if quote_devices is not None:
hwid_devices = quote_devices
payload = {
"user_id": user_id,
"amount": amount,
@@ -111,9 +116,9 @@ def build_payment_record_payload(
"sale_mode": sale_mode,
"tariff_key": sale_mode_tariff_key(sale_mode),
"purchased_gb": float(months) if is_traffic else None,
"purchased_hwid_devices": int(float(months)) if is_hwid else None,
"purchased_hwid_devices": hwid_devices,
}
if hwid_quote and is_hwid:
if hwid_quote and hwid_devices is not None:
payload.update(
{
"hwid_valid_from": hwid_quote.get("valid_from"),
@@ -164,14 +169,20 @@ def payment_record_amounts(
months: Any,
sale_mode: str,
traffic_gb: Optional[float] = None,
hwid_device_count: Optional[int] = None,
) -> PaymentRecordAmounts:
traffic_sale = sale_mode_is_traffic(sale_mode)
hwid_devices_sale = sale_mode_is_hwid_devices(sale_mode)
units = traffic_gb if traffic_sale and traffic_gb is not None else months
purchased_hwid_devices = int(float(months)) if hwid_devices_sale else None
if not hwid_devices_sale and hwid_device_count is not None:
parsed_hwid_devices = parse_positive_int_units(hwid_device_count)
if parsed_hwid_devices is not None:
purchased_hwid_devices = parsed_hwid_devices
return PaymentRecordAmounts(
months=int(float(units)) if traffic_sale else int(float(months)),
purchased_gb=float(units) if traffic_sale else None,
purchased_hwid_devices=int(float(months)) if hwid_devices_sale else None,
purchased_hwid_devices=purchased_hwid_devices,
tariff_key=sale_mode_tariff_key(sale_mode),
traffic_sale=traffic_sale,
hwid_devices_sale=hwid_devices_sale,
@@ -281,6 +292,7 @@ async def create_webapp_payment_record(
months=ctx.months,
sale_mode=ctx.sale_mode,
traffic_gb=ctx.traffic_gb,
hwid_device_count=ctx.hwid_device_count,
)
return await create_base_payment_record(
ctx.session,