Revert "Update miniapp purchase UI to reflect promo pricing"

This commit is contained in:
Egor
2025-10-10 10:50:20 +03:00
committed by GitHub
parent a9d4bea9de
commit 18ec1c1f5f
6 changed files with 1 additions and 1473 deletions

View File

@@ -57,11 +57,6 @@ from app.services.payment_service import PaymentService
from app.services.promo_offer_service import promo_offer_service
from app.services.promocode_service import PromoCodeService
from app.services.subscription_service import SubscriptionService
from app.services.subscription_purchase_service import (
purchase_service,
PurchaseBalanceError,
PurchaseValidationError,
)
from app.services.tribute_service import TributeService
from app.utils.currency_converter import currency_converter
from app.utils.subscription_utils import get_happ_cryptolink_redirect_link
@@ -131,12 +126,6 @@ from ..schemas.miniapp import (
MiniAppSubscriptionTrafficUpdateRequest,
MiniAppSubscriptionDevicesUpdateRequest,
MiniAppSubscriptionUpdateResponse,
MiniAppSubscriptionPurchaseOptionsRequest,
MiniAppSubscriptionPurchaseOptionsResponse,
MiniAppSubscriptionPurchasePreviewRequest,
MiniAppSubscriptionPurchasePreviewResponse,
MiniAppSubscriptionPurchaseRequest,
MiniAppSubscriptionPurchaseResponse,
)
@@ -243,45 +232,6 @@ def _build_balance_invoice_payload(user_id: int, amount_kopeks: int) -> str:
return f"balance_{user_id}_{amount_kopeks}_{suffix}"
def _merge_purchase_selection_from_request(
payload: Union[
"MiniAppSubscriptionPurchasePreviewRequest",
"MiniAppSubscriptionPurchaseRequest",
]
) -> Dict[str, Any]:
base: Dict[str, Any] = {}
if payload.selection:
base.update(payload.selection)
def _maybe_set(key: str, value: Any) -> None:
if value is None:
return
if key not in base:
base[key] = value
_maybe_set("period_id", getattr(payload, "period_id", None))
_maybe_set("period_days", getattr(payload, "period_days", None))
_maybe_set("traffic_value", getattr(payload, "traffic_value", None))
_maybe_set("traffic", getattr(payload, "traffic", None))
_maybe_set("traffic_gb", getattr(payload, "traffic_gb", None))
servers = getattr(payload, "servers", None)
if servers is not None and "servers" not in base:
base["servers"] = servers
countries = getattr(payload, "countries", None)
if countries is not None and "countries" not in base:
base["countries"] = countries
server_uuids = getattr(payload, "server_uuids", None)
if server_uuids is not None and "server_uuids" not in base:
base["server_uuids"] = server_uuids
_maybe_set("devices", getattr(payload, "devices", None))
_maybe_set("device_limit", getattr(payload, "device_limit", None))
return base
def _parse_client_timestamp(value: Optional[Union[str, int, float]]) -> Optional[datetime]:
if value is None:
return None
@@ -3144,113 +3094,6 @@ async def _build_subscription_settings(
return settings_payload
@router.post(
"/subscription/purchase/options",
response_model=MiniAppSubscriptionPurchaseOptionsResponse,
)
async def get_subscription_purchase_options_endpoint(
payload: MiniAppSubscriptionPurchaseOptionsRequest,
db: AsyncSession = Depends(get_db_session),
) -> MiniAppSubscriptionPurchaseOptionsResponse:
user = await _authorize_miniapp_user(payload.init_data, db)
context = await purchase_service.build_options(db, user)
data_payload = dict(context.payload)
data_payload.setdefault("currency", context.currency)
data_payload.setdefault("balance_kopeks", context.balance_kopeks)
data_payload.setdefault("balanceKopeks", context.balance_kopeks)
data_payload.setdefault("balance_label", settings.format_price(context.balance_kopeks))
data_payload.setdefault("balanceLabel", settings.format_price(context.balance_kopeks))
return MiniAppSubscriptionPurchaseOptionsResponse(
currency=context.currency,
balance_kopeks=context.balance_kopeks,
balance_label=settings.format_price(context.balance_kopeks),
subscription_id=data_payload.get("subscription_id") or data_payload.get("subscriptionId"),
data=data_payload,
)
@router.post(
"/subscription/purchase/preview",
response_model=MiniAppSubscriptionPurchasePreviewResponse,
)
async def subscription_purchase_preview_endpoint(
payload: MiniAppSubscriptionPurchasePreviewRequest,
db: AsyncSession = Depends(get_db_session),
) -> MiniAppSubscriptionPurchasePreviewResponse:
user = await _authorize_miniapp_user(payload.init_data, db)
context = await purchase_service.build_options(db, user)
selection_payload = _merge_purchase_selection_from_request(payload)
try:
selection = purchase_service.parse_selection(context, selection_payload)
except PurchaseValidationError as error:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
detail={"code": error.code, "message": str(error)},
) from error
pricing = await purchase_service.calculate_pricing(db, context, selection)
preview_payload = purchase_service.build_preview_payload(context, pricing)
balance_label = settings.format_price(getattr(user, "balance_kopeks", 0))
return MiniAppSubscriptionPurchasePreviewResponse(
preview=preview_payload,
balance_kopeks=user.balance_kopeks,
balance_label=balance_label,
)
@router.post(
"/subscription/purchase",
response_model=MiniAppSubscriptionPurchaseResponse,
)
async def subscription_purchase_endpoint(
payload: MiniAppSubscriptionPurchaseRequest,
db: AsyncSession = Depends(get_db_session),
) -> MiniAppSubscriptionPurchaseResponse:
user = await _authorize_miniapp_user(payload.init_data, db)
context = await purchase_service.build_options(db, user)
selection_payload = _merge_purchase_selection_from_request(payload)
try:
selection = purchase_service.parse_selection(context, selection_payload)
except PurchaseValidationError as error:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
detail={"code": error.code, "message": str(error)},
) from error
pricing = await purchase_service.calculate_pricing(db, context, selection)
try:
result = await purchase_service.submit_purchase(db, context, pricing)
except PurchaseBalanceError as error:
raise HTTPException(
status.HTTP_402_PAYMENT_REQUIRED,
detail={"code": "insufficient_funds", "message": str(error)},
) from error
except PurchaseValidationError as error:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
detail={"code": error.code, "message": str(error)},
) from error
await db.refresh(user)
subscription = result.get("subscription")
balance_label = settings.format_price(getattr(user, "balance_kopeks", 0))
return MiniAppSubscriptionPurchaseResponse(
message=result.get("message"),
balance_kopeks=user.balance_kopeks,
balance_label=balance_label,
subscription_id=getattr(subscription, "id", None),
)
@router.post(
"/subscription/settings",
response_model=MiniAppSubscriptionSettingsResponse,

View File

@@ -524,88 +524,3 @@ class MiniAppSubscriptionUpdateResponse(BaseModel):
success: bool = True
message: Optional[str] = None
class MiniAppSubscriptionPurchaseOptionsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseOptionsResponse(BaseModel):
success: bool = True
currency: str
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
data: Dict[str, Any] = Field(default_factory=dict)
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchasePreviewRequest(BaseModel):
init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
selection: Optional[Dict[str, Any]] = None
period_id: Optional[str] = Field(default=None, alias="periodId")
period_days: Optional[int] = Field(default=None, alias="periodDays")
period: Optional[str] = None
traffic_value: Optional[int] = Field(default=None, alias="trafficValue")
traffic: Optional[int] = None
traffic_gb: Optional[int] = Field(default=None, alias="trafficGb")
servers: Optional[List[str]] = None
countries: Optional[List[str]] = None
server_uuids: Optional[List[str]] = Field(default=None, alias="serverUuids")
devices: Optional[int] = None
device_limit: Optional[int] = Field(default=None, alias="deviceLimit")
model_config = ConfigDict(populate_by_name=True)
@model_validator(mode="before")
@classmethod
def _merge_selection(cls, values: Any) -> Any:
if not isinstance(values, dict):
return values
selection = values.get("selection")
if isinstance(selection, dict):
merged = {**selection, **values}
else:
merged = dict(values)
aliases = {
"period_id": ("periodId", "period", "code"),
"period_days": ("periodDays",),
"traffic_value": ("trafficValue", "traffic", "trafficGb"),
"servers": ("countries", "server_uuids", "serverUuids"),
"devices": ("deviceLimit",),
}
for target, sources in aliases.items():
if merged.get(target) is not None:
continue
for source in sources:
if source in merged and merged[source] is not None:
merged[target] = merged[source]
break
return merged
class MiniAppSubscriptionPurchasePreviewResponse(BaseModel):
success: bool = True
preview: Dict[str, Any] = Field(default_factory=dict)
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseRequest(MiniAppSubscriptionPurchasePreviewRequest):
pass
class MiniAppSubscriptionPurchaseResponse(BaseModel):
success: bool = True
message: Optional[str] = None
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
model_config = ConfigDict(populate_by_name=True)