fix: process yookassa hwid device topups

This commit is contained in:
3252a8
2026-05-23 22:28:05 +03:00
parent 31eb5c06ad
commit 0eeabc7b3a
7 changed files with 245 additions and 13 deletions
@@ -22,6 +22,7 @@ from db.models import Payment, User
from .common import Translator, format_human_units, make_translator, sale_mode_base
_TRAFFIC_MODES = {"traffic", "traffic_package", "topup", "premium_topup"}
_HWID_DEVICE_MODES = {"hwid_device", "hwid_devices"}
def is_traffic_sale_base(sale_base: str) -> bool:
@@ -103,6 +104,12 @@ def build_success_message(payload: SuccessMessage) -> str:
end_date=end_text,
config_link=payload.config_link_text,
)
if base in _HWID_DEVICE_MODES:
return _(
"payment_successful_hwid_devices_full",
count=format_human_units(payload.months),
config_link=payload.config_link_text,
)
if payload.applied_referee_bonus_days and payload.final_end_date:
base_end_text = _fmt_date(payload.base_end_date or payload.final_end_date, end_text)
return _(
+100 -13
View File
@@ -414,6 +414,51 @@ YOOKASSA_WEBHOOK_ALLOWED_IPS = [
"77.75.154.128/25",
"2a02:5180::/32",
]
HWID_DEVICE_SALE_BASES = {"hwid_device", "hwid_devices"}
def _is_hwid_device_sale_base(sale_mode_base: str) -> bool:
return sale_mode_base in HWID_DEVICE_SALE_BASES
def _metadata_value_present(value: Optional[Any]) -> bool:
return value is not None and str(value).strip() != ""
def _resolve_yookassa_activation_amounts(
*,
sale_mode_base: str,
subscription_months_raw: Optional[Any],
traffic_gb_raw: Optional[Any],
hwid_devices_raw: Optional[Any],
) -> tuple[float, float, int, int, Optional[float]]:
subscription_months = float(subscription_months_raw or 0)
traffic_amount_gb = (
float(traffic_gb_raw) if _metadata_value_present(traffic_gb_raw) else subscription_months
)
hwid_devices_count = (
int(float(hwid_devices_raw))
if _metadata_value_present(hwid_devices_raw)
else (int(subscription_months) if _is_hwid_device_sale_base(sale_mode_base) else 0)
)
if sale_mode_base == "subscription":
months_for_activation = int(subscription_months)
elif _is_hwid_device_sale_base(sale_mode_base):
months_for_activation = hwid_devices_count
else:
months_for_activation = int(traffic_amount_gb)
traffic_gb_for_activation = (
traffic_amount_gb if is_traffic_sale_base(sale_mode_base) else None
)
return (
subscription_months,
traffic_amount_gb,
hwid_devices_count,
months_for_activation,
traffic_gb_for_activation,
)
async def process_successful_payment(
@@ -431,6 +476,7 @@ async def process_successful_payment(
user_id_str = metadata.get("user_id")
subscription_months_str = metadata.get("subscription_months")
traffic_gb_str = metadata.get("traffic_gb")
hwid_devices_str = metadata.get("hwid_devices")
sale_mode = metadata.get("sale_mode") or (
"traffic" if settings.traffic_sale_mode else "subscription"
)
@@ -443,7 +489,11 @@ async def process_successful_payment(
# we will create/ensure a payment record idempotently using provider payment id.
if (
not user_id_str
or (not subscription_months_str and not traffic_gb_str)
or not (
_metadata_value_present(subscription_months_str)
or _metadata_value_present(traffic_gb_str)
or _metadata_value_present(hwid_devices_str)
)
or (not payment_db_id_str and not auto_renew_subscription_id_str)
):
logging.error(
@@ -454,8 +504,18 @@ async def process_successful_payment(
db_user = None
try:
user_id = int(user_id_str)
subscription_months = float(subscription_months_str or 0)
traffic_amount_gb = float(traffic_gb_str) if traffic_gb_str else subscription_months
(
subscription_months,
traffic_amount_gb,
hwid_devices_count,
months_for_activation,
traffic_gb_for_activation,
) = _resolve_yookassa_activation_amounts(
sale_mode_base=sale_mode_base,
subscription_months_raw=subscription_months_str,
traffic_gb_raw=traffic_gb_str,
hwid_devices_raw=hwid_devices_str,
)
payment_db_id = (
int(payment_db_id_str) if payment_db_id_str and payment_db_id_str.isdigit() else None
)
@@ -473,6 +533,21 @@ async def process_successful_payment(
payment_value = float(amount_data.get("value", 0.0))
yk_payment_id_from_hook = payment_info_from_webhook.get("id")
if _is_hwid_device_sale_base(sale_mode_base) and hwid_devices_count <= 0:
logging.error(
"YooKassa HWID payment %s has invalid device count in metadata: %s",
yk_payment_id_from_hook,
metadata,
)
if payment_db_id is not None:
await payment_dal.update_payment_status_by_db_id(
session,
payment_db_id,
"failed_metadata_error",
yk_payment_id_from_hook,
)
return
payment_record = None
# If this is an auto-renewal (no payment_db_id in metadata), ensure a payment record exists
if payment_db_id is None and auto_renew_subscription_id_str:
@@ -617,9 +692,6 @@ async def process_successful_payment(
logging.exception("Failed to persist multi-card YooKassa method from webhook")
except Exception:
logging.exception("Failed to persist YooKassa payment method from webhook")
months_for_activation = (
int(subscription_months) if sale_mode_base == "subscription" else int(traffic_amount_gb)
)
activation_details = await subscription_service.activate_subscription(
session,
user_id,
@@ -629,12 +701,12 @@ async def process_successful_payment(
promo_code_id_from_payment=promo_code_id,
provider="yookassa",
sale_mode=sale_mode,
traffic_gb=traffic_amount_gb
if sale_mode_base in {"traffic", "traffic_package", "topup", "premium_topup"}
else None,
traffic_gb=traffic_gb_for_activation,
)
if not activation_details or not activation_details.get("end_date"):
if not activation_details or (
sale_mode_base == "subscription" and not activation_details.get("end_date")
):
logging.error(
f"Failed to activate subscription for user {user_id} after payment {yk_payment_id_from_hook}" # noqa: E501
)
@@ -652,7 +724,7 @@ async def process_successful_payment(
)
raise Exception(f"DB Error: Could not update payment record {payment_db_id}")
base_subscription_end_date = activation_details["end_date"]
base_subscription_end_date = activation_details.get("end_date")
final_end_date_for_user = base_subscription_end_date
applied_promo_bonus_days = activation_details.get("applied_promo_bonus_days", 0)
@@ -687,6 +759,11 @@ async def process_successful_payment(
if not receipt_item_name:
if is_traffic_sale_base(sale_mode_base):
receipt_item_name = settings.LKNPD_RECEIPT_NAME_TRAFFIC.format(gb=traffic_label)
elif _is_hwid_device_sale_base(sale_mode_base):
receipt_item_name = _(
"payment_description_hwid_devices",
count=hwid_devices_count,
)
else:
receipt_item_name = settings.LKNPD_RECEIPT_NAME_SUBSCRIPTION.format(
months=int(subscription_months)
@@ -716,7 +793,11 @@ async def process_successful_payment(
end_date=final_end_date_for_user.strftime("%Y-%m-%d"),
)
include_keyboard = False
elif not final_end_date_for_user and not is_traffic_sale_base(sale_mode_base):
elif (
sale_mode_base == "subscription"
and not final_end_date_for_user
and not is_traffic_sale_base(sale_mode_base)
):
logging.error(
f"Critical error: final_end_date_for_user is None for user {user_id} after successful payment logic." # noqa: E501
)
@@ -733,7 +814,11 @@ async def process_successful_payment(
months=(
traffic_label
if is_traffic_sale_base(sale_mode_base)
else int(subscription_months)
else (
hwid_devices_count
if _is_hwid_device_sale_base(sale_mode_base)
else int(subscription_months)
)
),
base_end_date=base_subscription_end_date,
final_end_date=final_end_date_for_user,
@@ -1272,6 +1357,8 @@ async def _initiate_yk_payment(
}
if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"}:
yookassa_metadata["traffic_gb"] = str(months)
if sale_base in HWID_DEVICE_SALE_BASES:
yookassa_metadata["hwid_devices"] = str(months)
if payment_method_id:
yookassa_metadata["used_saved_payment_method_id"] = payment_method_id
@@ -57,6 +57,10 @@ class HwidDeviceMixin:
)
return {
"subscription_id": sub.subscription_id,
"end_date": sub.end_date,
"is_active": True,
"panel_user_uuid": db_user.panel_user_uuid,
"panel_short_uuid": getattr(sub, "panel_subscription_uuid", None),
"hwid_device_limit": 0,
"extra_hwid_devices": int(sub.extra_hwid_devices or 0),
"purchased_hwid_devices": 0,
@@ -102,6 +106,10 @@ class HwidDeviceMixin:
)
return None
final_subscription_url = updated_panel.get("subscriptionUrl")
final_panel_short_uuid = updated_panel.get(
"shortUuid", getattr(updated_sub, "panel_subscription_uuid", None)
)
await tariff_dal.create_hwid_device_purchase(
session,
subscription_id=updated_sub.subscription_id,
@@ -110,6 +118,11 @@ class HwidDeviceMixin:
)
return {
"subscription_id": updated_sub.subscription_id,
"end_date": updated_sub.end_date,
"is_active": True,
"panel_user_uuid": db_user.panel_user_uuid,
"panel_short_uuid": final_panel_short_uuid,
"subscription_url": final_subscription_url,
"hwid_device_limit": effective_hwid_limit,
"extra_hwid_devices": new_extra_devices,
"purchased_hwid_devices": purchased_devices,