Merge pull request #1054 from Fr1ngg/fze3ek-bedolaga/expand-api-for-subscription-purchase

Add mini app subscription purchase flow
This commit is contained in:
Egor
2025-10-10 09:02:02 +03:00
committed by GitHub
3 changed files with 1248 additions and 18 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -436,6 +436,128 @@ class MiniAppSubscriptionSettingsResponse(BaseModel):
settings: MiniAppSubscriptionSettings settings: MiniAppSubscriptionSettings
class MiniAppSubscriptionPurchasePeriod(BaseModel):
id: str
days: int
months: int
price_kopeks: int = Field(..., alias="priceKopeks")
price_label: Optional[str] = Field(default=None, alias="priceLabel")
original_price_kopeks: Optional[int] = Field(default=None, alias="originalPriceKopeks")
original_price_label: Optional[str] = Field(default=None, alias="originalPriceLabel")
per_month_price_kopeks: Optional[int] = Field(default=None, alias="perMonthPriceKopeks")
per_month_price_label: Optional[str] = Field(default=None, alias="perMonthPriceLabel")
discount_percent: Optional[int] = Field(default=None, alias="discountPercent")
description: Optional[str] = None
traffic: Optional[Dict[str, Any]] = None
servers: Optional[Dict[str, Any]] = None
devices: Optional[Dict[str, Any]] = None
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseOptions(BaseModel):
currency: str = "RUB"
balance_kopeks: int = Field(..., alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
periods: List[Dict[str, Any]] = Field(default_factory=list)
traffic: Dict[str, Any] = Field(default_factory=dict)
servers: Dict[str, Any] = Field(default_factory=dict)
devices: Dict[str, Any] = Field(default_factory=dict)
selection: Dict[str, Any] = Field(default_factory=dict)
summary: Optional[Dict[str, Any]] = None
promo: Optional[Dict[str, Any]] = None
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchaseOptionsResponse(BaseModel):
success: bool = True
data: MiniAppSubscriptionPurchaseOptions
class MiniAppSubscriptionPurchasePreview(BaseModel):
total_price_kopeks: int = Field(..., alias="totalPriceKopeks")
total_price_label: str = Field(..., alias="totalPriceLabel")
original_price_kopeks: Optional[int] = Field(default=None, alias="originalPriceKopeks")
original_price_label: Optional[str] = Field(default=None, alias="originalPriceLabel")
per_month_price_kopeks: Optional[int] = Field(default=None, alias="perMonthPriceKopeks")
per_month_price_label: Optional[str] = Field(default=None, alias="perMonthPriceLabel")
discount_percent: Optional[int] = Field(default=None, alias="discountPercent")
discount_label: Optional[str] = Field(default=None, alias="discountLabel")
discount_lines: List[str] = Field(default_factory=list, alias="discountLines")
breakdown: List[Dict[str, Any]] = Field(default_factory=list)
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
missing_amount_kopeks: Optional[int] = Field(default=None, alias="missingAmountKopeks")
missing_amount_label: Optional[str] = Field(default=None, alias="missingAmountLabel")
status_message: Optional[str] = Field(default=None, alias="statusMessage")
can_purchase: bool = Field(default=True, alias="canPurchase")
model_config = ConfigDict(populate_by_name=True)
class MiniAppSubscriptionPurchasePreviewResponse(BaseModel):
success: bool = True
preview: MiniAppSubscriptionPurchasePreview
class MiniAppSubscriptionPurchaseOptionsRequest(BaseModel):
init_data: str = Field(..., alias="initData")
class MiniAppSubscriptionPurchaseBaseRequest(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_key: Optional[str] = Field(default=None, alias="periodKey")
period_code: Optional[str] = Field(default=None, alias="periodCode")
period: Optional[str] = None
period_days: Optional[int] = Field(default=None, alias="periodDays")
period_months: Optional[int] = Field(default=None, alias="periodMonths")
months: Optional[int] = None
traffic: Optional[int] = None
traffic_value: Optional[int] = Field(default=None, alias="trafficValue")
traffic_gb: Optional[int] = Field(default=None, alias="trafficGb")
limit: Optional[int] = None
servers: Optional[List[str]] = None
countries: Optional[List[str]] = None
server_uuids: Optional[List[str]] = Field(default=None, alias="serverUuids")
squad_uuids: Optional[List[str]] = Field(default=None, alias="squadUuids")
devices: Optional[int] = None
device_limit: Optional[int] = Field(default=None, alias="deviceLimit")
model_config = ConfigDict(populate_by_name=True, extra="allow")
@model_validator(mode="before")
@classmethod
def _merge_selection(cls, values: Any) -> Any:
if isinstance(values, dict):
selection = values.get("selection")
if isinstance(selection, dict):
merged = dict(selection)
merged.update(values)
return merged
return values
class MiniAppSubscriptionPurchasePreviewRequest(MiniAppSubscriptionPurchaseBaseRequest):
pass
class MiniAppSubscriptionPurchaseSubmitRequest(MiniAppSubscriptionPurchaseBaseRequest):
pass
class MiniAppSubscriptionPurchaseSubmitResponse(BaseModel):
success: bool = True
message: Optional[str] = None
subscription_id: Optional[int] = Field(default=None, alias="subscriptionId")
balance_kopeks: Optional[int] = Field(default=None, alias="balanceKopeks")
balance_label: Optional[str] = Field(default=None, alias="balanceLabel")
class MiniAppSubscriptionSettingsRequest(BaseModel): class MiniAppSubscriptionSettingsRequest(BaseModel):
init_data: str = Field(..., alias="initData") init_data: str = Field(..., alias="initData")
subscription_id: Optional[int] = None subscription_id: Optional[int] = None

View File

@@ -9751,28 +9751,56 @@
return false; return false;
} }
const typeRaw = String(userData.subscription_type || '').toLowerCase();
if (typeRaw === 'trial') {
return false;
}
if (typeRaw === 'paid') {
return true;
}
if (userData.user.has_active_subscription === false) {
return false;
}
const statusRaw = String( const statusRaw = String(
userData.user.subscription_actual_status userData.user.subscription_actual_status
|| userData.user.subscription_status || userData.user.subscription_status
|| userData.subscription_status
|| '' || ''
).toLowerCase(); ).toLowerCase();
if (['trial', 'expired', 'disabled'].includes(statusRaw)) { const inactiveStatuses = new Set([
'trial',
'expired',
'disabled',
'inactive',
'stopped',
'ended',
'cancelled',
'canceled',
'pending',
'pending_payment',
'awaiting_payment',
'none',
]);
if (statusRaw && inactiveStatuses.has(statusRaw)) {
return false; return false;
} }
return true; const hasActiveFlag = userData.user.has_active_subscription;
if (hasActiveFlag === true) {
return true;
}
if (hasActiveFlag === false) {
return false;
}
const activeStatuses = new Set(['active', 'running', 'enabled']);
if (statusRaw && activeStatuses.has(statusRaw)) {
return true;
}
const typeRaw = String(
userData.subscription_type
|| userData.subscriptionType
|| ''
).toLowerCase();
if (['trial', 'free', 'none'].includes(typeRaw)) {
return false;
}
if (['paid', 'active'].includes(typeRaw)) {
return true;
}
return false;
} }
function normalizeServerEntry(entry) { function normalizeServerEntry(entry) {