Initial commit: VPN Telegram bot (sanitized defaults)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Telegram Stars: курс, пакеты и создание инвойсов."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from aiogram.types import LabeledPrice
|
||||
|
||||
from services.db import Database
|
||||
|
||||
# Суммы пополнения в рублях (как у крипты)
|
||||
STAR_TOPUP_AMOUNTS_RUB: tuple[int, ...] = (100, 200, 500, 1000, 2000)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class StarsQuote:
|
||||
amount_rub: float
|
||||
stars: int
|
||||
rub_per_star: float
|
||||
|
||||
@property
|
||||
def stars_label(self) -> str:
|
||||
return f"{self.stars} ⭐"
|
||||
|
||||
@property
|
||||
def rub_label(self) -> str:
|
||||
if self.amount_rub == int(self.amount_rub):
|
||||
return f"{int(self.amount_rub)} ₽"
|
||||
return f"{self.amount_rub:.2f} ₽"
|
||||
|
||||
|
||||
def rub_to_stars(amount_rub: float, *, rub_per_star: float) -> int:
|
||||
rate = float(rub_per_star)
|
||||
if rate <= 0:
|
||||
raise ValueError("rub_per_star must be > 0")
|
||||
stars = int(math.ceil(float(amount_rub) / rate))
|
||||
return max(1, stars)
|
||||
|
||||
|
||||
def quote_topup(amount_rub: float, *, rub_per_star: float) -> StarsQuote:
|
||||
amount = float(amount_rub)
|
||||
if amount <= 0:
|
||||
raise ValueError("amount_rub must be > 0")
|
||||
stars = rub_to_stars(amount, rub_per_star=rub_per_star)
|
||||
return StarsQuote(amount_rub=amount, stars=stars, rub_per_star=float(rub_per_star))
|
||||
|
||||
|
||||
def parse_stars_payload(payload: str) -> tuple[int, int, float] | None:
|
||||
"""
|
||||
payload: stars:{invoice_id}:{telegram_id}:{amount_rub}
|
||||
→ (invoice_id, telegram_id, amount_rub)
|
||||
"""
|
||||
parts = (payload or "").split(":")
|
||||
if len(parts) != 4 or parts[0] != "stars":
|
||||
return None
|
||||
try:
|
||||
return int(parts[1]), int(parts[2]), float(parts[3])
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def build_stars_payload(*, invoice_id: int, telegram_id: int, amount_rub: float) -> str:
|
||||
return f"stars:{invoice_id}:{telegram_id}:{int(amount_rub)}"
|
||||
|
||||
|
||||
def packs_payload(*, rub_per_star: float) -> list[dict[str, Any]]:
|
||||
out: list[dict[str, Any]] = []
|
||||
for amount in STAR_TOPUP_AMOUNTS_RUB:
|
||||
q = quote_topup(amount, rub_per_star=rub_per_star)
|
||||
out.append(
|
||||
{
|
||||
"amount_rub": q.amount_rub,
|
||||
"stars": q.stars,
|
||||
"rub_label": q.rub_label,
|
||||
"stars_label": q.stars_label,
|
||||
"title": f"{q.rub_label} · {q.stars_label}",
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
async def create_stars_topup(
|
||||
*,
|
||||
bot: Any,
|
||||
db: Database,
|
||||
telegram_id: int,
|
||||
amount_rub: float,
|
||||
rub_per_star: float,
|
||||
) -> dict[str, Any]:
|
||||
"""Создать локальный инвойс + invoice link для Mini App / Telegram Web."""
|
||||
if telegram_id <= 0:
|
||||
raise ValueError("Stars доступны только для Telegram-аккаунта")
|
||||
quote = quote_topup(amount_rub, rub_per_star=rub_per_star)
|
||||
local_id = db.next_local_invoice_id()
|
||||
payload = build_stars_payload(
|
||||
invoice_id=local_id,
|
||||
telegram_id=telegram_id,
|
||||
amount_rub=quote.amount_rub,
|
||||
)
|
||||
invoice_link = await bot.create_invoice_link(
|
||||
title="Пополнение баланса",
|
||||
description=(
|
||||
f"Зачисление {quote.rub_label} на баланс VPN Service "
|
||||
f"({quote.stars_label})"
|
||||
),
|
||||
payload=payload,
|
||||
currency="XTR",
|
||||
prices=[
|
||||
LabeledPrice(
|
||||
label=f"Баланс {quote.rub_label}",
|
||||
amount=quote.stars,
|
||||
)
|
||||
],
|
||||
provider_token="",
|
||||
)
|
||||
db.create_invoice_record(
|
||||
telegram_id=telegram_id,
|
||||
invoice_id=local_id,
|
||||
kind="topup",
|
||||
amount_rub=quote.amount_rub,
|
||||
pay_url=invoice_link,
|
||||
payload=payload,
|
||||
provider="stars",
|
||||
)
|
||||
return {
|
||||
"invoice_id": local_id,
|
||||
"invoice_link": invoice_link,
|
||||
"amount_rub": quote.amount_rub,
|
||||
"stars": quote.stars,
|
||||
"rub_label": quote.rub_label,
|
||||
"stars_label": quote.stars_label,
|
||||
"payload": payload,
|
||||
}
|
||||
Reference in New Issue
Block a user