feat: add multiple platega buttons

This commit is contained in:
3252a8
2026-04-27 14:34:41 +03:00
parent 587b39de1d
commit 0610156f80
8 changed files with 117 additions and 15 deletions
+5 -1
View File
@@ -107,7 +107,11 @@ CRYPTOPAY_ASSET=RUB #
PLATEGA_BASE_URL=https://app.platega.io # Base API URL PLATEGA_BASE_URL=https://app.platega.io # Base API URL
PLATEGA_MERCHANT_ID= # Your MerchantId from Platega PLATEGA_MERCHANT_ID= # Your MerchantId from Platega
PLATEGA_SECRET= # API secret from Platega PLATEGA_SECRET= # API secret from Platega
PLATEGA_PAYMENT_METHOD=2 # Payment method ID (2=SBP QR, 10=RU cards, 12=International, 13=Crypto) PLATEGA_PAYMENT_METHOD=2 # Legacy method ID; fallback for the SBP button when PLATEGA_SBP_METHOD stays default
PLATEGA_SBP_ENABLED=False # Show a separate "Pay via SBP" Platega button
PLATEGA_CRYPTO_ENABLED=False # Show a separate "Pay with crypto" Platega button
PLATEGA_SBP_METHOD=2 # Platega method ID for SBP QR (default 2)
PLATEGA_CRYPTO_METHOD=13 # Platega method ID for crypto (default 13)
PLATEGA_RETURN_URL= # Optional: redirect after successful payment (defaults to bot link) PLATEGA_RETURN_URL= # Optional: redirect after successful payment (defaults to bot link)
PLATEGA_FAILED_URL= # Optional: redirect after failed/cancelled payment (defaults to return URL) PLATEGA_FAILED_URL= # Optional: redirect after failed/cancelled payment (defaults to return URL)
+18 -4
View File
@@ -2066,7 +2066,8 @@ def _serialize_payment_methods(
labels = { labels = {
"severpay": "SeverPay", "severpay": "SeverPay",
"freekassa": "FreeKassa / СБП", "freekassa": "FreeKassa / СБП",
"platega": "Platega", "platega_sbp": "Platega · СБП",
"platega_crypto": "Platega · Crypto",
"yookassa": "Банковская карта", "yookassa": "Банковская карта",
"stars": "Telegram Stars", "stars": "Telegram Stars",
"cryptopay": "CryptoPay", "cryptopay": "CryptoPay",
@@ -2078,7 +2079,9 @@ def _serialize_payment_methods(
methods.append({"id": method, "name": labels[method]}) methods.append({"id": method, "name": labels[method]})
elif method == "freekassa" and _service_configured(app, "freekassa_service"): elif method == "freekassa" and _service_configured(app, "freekassa_service"):
methods.append({"id": method, "name": labels[method]}) methods.append({"id": method, "name": labels[method]})
elif method == "platega" and _service_configured(app, "platega_service"): elif method == "platega_sbp" and settings.PLATEGA_SBP_ENABLED and _service_configured(app, "platega_service"):
methods.append({"id": method, "name": labels[method]})
elif method == "platega_crypto" and settings.PLATEGA_CRYPTO_ENABLED and _service_configured(app, "platega_service"):
methods.append({"id": method, "name": labels[method]}) methods.append({"id": method, "name": labels[method]})
elif method == "yookassa" and _service_configured(app, "yookassa_service"): elif method == "yookassa" and _service_configured(app, "yookassa_service"):
methods.append({"id": method, "name": labels[method]}) methods.append({"id": method, "name": labels[method]})
@@ -2116,9 +2119,9 @@ async def _create_subscription_payment(
return await _create_freekassa_payment( return await _create_freekassa_payment(
request, session, user_id, months, price, description request, session, user_id, months, price, description
) )
if method == "platega": if method in ("platega", "platega_sbp", "platega_crypto"):
return await _create_platega_payment( return await _create_platega_payment(
request, session, user_id, months, price, description request, session, user_id, months, price, description, variant=method
) )
if method == "severpay": if method == "severpay":
return await _create_severpay_payment( return await _create_severpay_payment(
@@ -2316,11 +2319,20 @@ async def _create_platega_payment(
months: int, months: int,
price: float, price: float,
description: str, description: str,
variant: str = "platega_sbp",
) -> web.Response: ) -> web.Response:
settings: Settings = request.app["settings"] settings: Settings = request.app["settings"]
service: PlategaService = request.app["platega_service"] service: PlategaService = request.app["platega_service"]
if not service or not service.configured: if not service or not service.configured:
return _json_error(400, "payment_unavailable", "Payment method unavailable") return _json_error(400, "payment_unavailable", "Payment method unavailable")
if variant == "platega_crypto":
if not settings.PLATEGA_CRYPTO_ENABLED:
return _json_error(400, "payment_unavailable", "Payment method unavailable")
platega_method_id = settings.PLATEGA_CRYPTO_METHOD
else:
if variant == "platega_sbp" and not settings.PLATEGA_SBP_ENABLED:
return _json_error(400, "payment_unavailable", "Payment method unavailable")
platega_method_id = settings.platega_sbp_method_resolved
try: try:
payment = await _create_base_payment_record( payment = await _create_base_payment_record(
@@ -2340,6 +2352,7 @@ async def _create_platega_payment(
"months": months, "months": months,
"sale_mode": "subscription", "sale_mode": "subscription",
"source": "webapp", "source": "webapp",
"platega_variant": "crypto" if variant == "platega_crypto" else "sbp",
} }
) )
success, response_data = await service.create_transaction( success, response_data = await service.create_transaction(
@@ -2350,6 +2363,7 @@ async def _create_platega_payment(
currency=settings.DEFAULT_CURRENCY_SYMBOL or "RUB", currency=settings.DEFAULT_CURRENCY_SYMBOL or "RUB",
description=description, description=description,
payload=payload, payload=payload,
payment_method=platega_method_id,
) )
payment_url = ( payment_url = (
response_data.get("redirect") response_data.get("redirect")
@@ -14,7 +14,11 @@ from db.dal import payment_dal
router = Router(name="user_subscription_payments_platega_router") router = Router(name="user_subscription_payments_platega_router")
@router.callback_query(F.data.startswith("pay_platega:")) @router.callback_query(
F.data.startswith("pay_platega_sbp:")
| F.data.startswith("pay_platega_crypto:")
| F.data.startswith("pay_platega:")
)
async def pay_platega_callback_handler( async def pay_platega_callback_handler(
callback: types.CallbackQuery, callback: types.CallbackQuery,
settings: Settings, settings: Settings,
@@ -22,6 +26,29 @@ async def pay_platega_callback_handler(
platega_service: PlategaService, platega_service: PlategaService,
session: AsyncSession, session: AsyncSession,
): ):
callback_prefix, _, _ = (callback.data or "").partition(":")
if callback_prefix == "pay_platega_crypto":
platega_method_id = settings.PLATEGA_CRYPTO_METHOD
platega_variant = "crypto"
if not settings.PLATEGA_CRYPTO_ENABLED:
try:
await callback.answer()
except Exception:
pass
return
elif callback_prefix == "pay_platega_sbp":
platega_method_id = settings.platega_sbp_method_resolved
platega_variant = "sbp"
if not settings.PLATEGA_SBP_ENABLED:
try:
await callback.answer()
except Exception:
pass
return
else:
# Legacy callback (pre-split): keep working as SBP
platega_method_id = settings.platega_sbp_method_resolved
platega_variant = "sbp"
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
get_text = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key get_text = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key
@@ -103,6 +130,7 @@ async def pay_platega_callback_handler(
"user_id": user_id, "user_id": user_id,
"months": months, "months": months,
"sale_mode": sale_mode, "sale_mode": sale_mode,
"platega_variant": platega_variant,
} }
) )
@@ -114,6 +142,7 @@ async def pay_platega_callback_handler(
currency=currency_code, currency=currency_code,
description=payment_description, description=payment_description,
payload=payload_meta, payload=payload_meta,
payment_method=platega_method_id,
) )
if success: if success:
+8 -3
View File
@@ -206,10 +206,15 @@ def get_payment_method_keyboard(months: int, price: float,
text=_("pay_with_sbp_button"), text=_("pay_with_sbp_button"),
callback_data=f"pay_fk:{value_str}:{price}{mode_suffix}", callback_data=f"pay_fk:{value_str}:{price}{mode_suffix}",
) )
elif method == "platega" and settings.PLATEGA_ENABLED: elif method == "platega_sbp" and settings.PLATEGA_ENABLED and settings.PLATEGA_SBP_ENABLED:
builder.button( builder.button(
text=_("pay_with_platega_button"), text=_("pay_with_platega_sbp_button"),
callback_data=f"pay_platega:{value_str}:{price}{mode_suffix}", callback_data=f"pay_platega_sbp:{value_str}:{price}{mode_suffix}",
)
elif method == "platega_crypto" and settings.PLATEGA_ENABLED and settings.PLATEGA_CRYPTO_ENABLED:
builder.button(
text=_("pay_with_platega_crypto_button"),
callback_data=f"pay_platega_crypto:{value_str}:{price}{mode_suffix}",
) )
elif method == "yookassa" and settings.YOOKASSA_ENABLED: elif method == "yookassa" and settings.YOOKASSA_ENABLED:
builder.button( builder.button(
+5 -1
View File
@@ -42,6 +42,8 @@ class PlategaService:
self.merchant_id = settings.PLATEGA_MERCHANT_ID self.merchant_id = settings.PLATEGA_MERCHANT_ID
self.secret = settings.PLATEGA_SECRET self.secret = settings.PLATEGA_SECRET
self.payment_method = settings.PLATEGA_PAYMENT_METHOD self.payment_method = settings.PLATEGA_PAYMENT_METHOD
self.sbp_method = settings.platega_sbp_method_resolved
self.crypto_method = settings.PLATEGA_CRYPTO_METHOD
self.return_url = settings.PLATEGA_RETURN_URL or f"https://t.me/{default_return_url}" self.return_url = settings.PLATEGA_RETURN_URL or f"https://t.me/{default_return_url}"
self.failed_url = settings.PLATEGA_FAILED_URL or self.return_url self.failed_url = settings.PLATEGA_FAILED_URL or self.return_url
@@ -77,6 +79,7 @@ class PlategaService:
currency: Optional[str], currency: Optional[str],
description: str, description: str,
payload: Optional[str] = None, payload: Optional[str] = None,
payment_method: Optional[int] = None,
) -> Tuple[bool, Dict[str, Any]]: ) -> Tuple[bool, Dict[str, Any]]:
if not self.configured: if not self.configured:
logging.error("PlategaService is not configured. Cannot create transaction.") logging.error("PlategaService is not configured. Cannot create transaction.")
@@ -85,9 +88,10 @@ class PlategaService:
session = await self._get_session() session = await self._get_session()
url = f"{self.base_url}/transaction/process" url = f"{self.base_url}/transaction/process"
currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper() currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper()
method_id = int(payment_method if payment_method is not None else self.payment_method)
body: Dict[str, Any] = { body: Dict[str, Any] = {
"paymentMethod": int(self.payment_method), "paymentMethod": method_id,
"paymentDetails": {"amount": float(amount), "currency": currency_code}, "paymentDetails": {"amount": float(amount), "currency": currency_code},
"description": description, "description": description,
"return": self.return_url, "return": self.return_url,
+47 -5
View File
@@ -44,6 +44,10 @@ class PaymentSettings(BaseModel):
platega_merchant_id: Optional[str] platega_merchant_id: Optional[str]
platega_secret: Optional[str] platega_secret: Optional[str]
platega_payment_method: int platega_payment_method: int
platega_sbp_enabled: bool
platega_crypto_enabled: bool
platega_sbp_method: int
platega_crypto_method: int
platega_return_url: Optional[str] platega_return_url: Optional[str]
platega_failed_url: Optional[str] platega_failed_url: Optional[str]
severpay_enabled: bool severpay_enabled: bool
@@ -179,7 +183,24 @@ class Settings(BaseSettings):
PLATEGA_MERCHANT_ID: Optional[str] = None PLATEGA_MERCHANT_ID: Optional[str] = None
PLATEGA_SECRET: Optional[str] = None PLATEGA_SECRET: Optional[str] = None
PLATEGA_PAYMENT_METHOD: int = Field( PLATEGA_PAYMENT_METHOD: int = Field(
default=2, description="Platega payment method ID (e.g., 2 for SBP QR)" default=2,
description="Legacy Platega payment method ID. Used as fallback for PLATEGA_SBP_METHOD when the new field is unset.",
)
PLATEGA_SBP_ENABLED: bool = Field(
default=False,
description="Show a separate Platega SBP payment button.",
)
PLATEGA_CRYPTO_ENABLED: bool = Field(
default=False,
description="Show a separate Platega crypto payment button.",
)
PLATEGA_SBP_METHOD: int = Field(
default=2,
description="Platega method ID for SBP QR (default 2).",
)
PLATEGA_CRYPTO_METHOD: int = Field(
default=13,
description="Platega method ID for crypto (default 13).",
) )
PLATEGA_RETURN_URL: Optional[str] = Field(default=None) PLATEGA_RETURN_URL: Optional[str] = Field(default=None)
PLATEGA_FAILED_URL: Optional[str] = Field(default=None) PLATEGA_FAILED_URL: Optional[str] = Field(default=None)
@@ -404,6 +425,10 @@ class Settings(BaseSettings):
platega_merchant_id=self.PLATEGA_MERCHANT_ID, platega_merchant_id=self.PLATEGA_MERCHANT_ID,
platega_secret=self.PLATEGA_SECRET, platega_secret=self.PLATEGA_SECRET,
platega_payment_method=self.PLATEGA_PAYMENT_METHOD, platega_payment_method=self.PLATEGA_PAYMENT_METHOD,
platega_sbp_enabled=self.PLATEGA_SBP_ENABLED,
platega_crypto_enabled=self.PLATEGA_CRYPTO_ENABLED,
platega_sbp_method=self.platega_sbp_method_resolved,
platega_crypto_method=self.PLATEGA_CRYPTO_METHOD,
platega_return_url=self.PLATEGA_RETURN_URL, platega_return_url=self.PLATEGA_RETURN_URL,
platega_failed_url=self.PLATEGA_FAILED_URL, platega_failed_url=self.PLATEGA_FAILED_URL,
severpay_enabled=self.SEVERPAY_ENABLED, severpay_enabled=self.SEVERPAY_ENABLED,
@@ -750,7 +775,8 @@ class Settings(BaseSettings):
""" """
default_order = [ default_order = [
"freekassa", "freekassa",
"platega", "platega_sbp",
"platega_crypto",
"severpay", "severpay",
"yookassa", "yookassa",
"stars", "stars",
@@ -758,13 +784,29 @@ class Settings(BaseSettings):
] ]
if not self.PAYMENT_METHODS_ORDER: if not self.PAYMENT_METHODS_ORDER:
return default_order return default_order
methods = [] methods: List[str] = []
for item in self.PAYMENT_METHODS_ORDER.split(","): for item in self.PAYMENT_METHODS_ORDER.split(","):
slug = item.strip().lower() slug = item.strip().lower()
if slug: if not slug:
methods.append(slug) continue
if slug == "platega":
# Legacy slug — expand to the new sub-methods preserving order
if "platega_sbp" not in methods:
methods.append("platega_sbp")
if "platega_crypto" not in methods:
methods.append("platega_crypto")
continue
methods.append(slug)
return methods or default_order return methods or default_order
@computed_field
@property
def platega_sbp_method_resolved(self) -> int:
"""SBP method ID, falling back to legacy PLATEGA_PAYMENT_METHOD when SBP-specific value is the default."""
if self.PLATEGA_SBP_METHOD != 2:
return self.PLATEGA_SBP_METHOD
return self.PLATEGA_PAYMENT_METHOD or 2
@computed_field @computed_field
@property @property
def email_auth_configured(self) -> bool: def email_auth_configured(self) -> bool:
+2
View File
@@ -49,6 +49,8 @@
"yookassa_autopay_charge_initiated": "Charge request sent to the selected card. We'll notify you once the payment completes.", "yookassa_autopay_charge_initiated": "Charge request sent to the selected card. We'll notify you once the payment completes.",
"pay_with_sbp_button": "📱 SBP", "pay_with_sbp_button": "📱 SBP",
"pay_with_platega_button": "💳 Platega (SBP/Cards)", "pay_with_platega_button": "💳 Platega (SBP/Cards)",
"pay_with_platega_sbp_button": "🏦 Pay via SBP",
"pay_with_platega_crypto_button": "🪙 Pay with crypto",
"pay_with_severpay_button": "💳 SeverPay", "pay_with_severpay_button": "💳 SeverPay",
"back_to_payment_methods_button": "⬅️ Back", "back_to_payment_methods_button": "⬅️ Back",
"pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_cryptopay_button": "💎 CryptoBot",
+2
View File
@@ -49,6 +49,8 @@
"yookassa_autopay_charge_initiated": "Запрос на списание с выбранной карты отправлен. Сообщим, как только платёж завершится.", "yookassa_autopay_charge_initiated": "Запрос на списание с выбранной карты отправлен. Сообщим, как только платёж завершится.",
"pay_with_sbp_button": "📱 СБП", "pay_with_sbp_button": "📱 СБП",
"pay_with_platega_button": "💳 Platega (СБП/карты)", "pay_with_platega_button": "💳 Platega (СБП/карты)",
"pay_with_platega_sbp_button": "🏦 Оплата через СБП",
"pay_with_platega_crypto_button": "🪙 Оплата криптой",
"pay_with_severpay_button": "💳 SeverPay", "pay_with_severpay_button": "💳 SeverPay",
"back_to_payment_methods_button": "⬅️ Назад", "back_to_payment_methods_button": "⬅️ Назад",
"pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_cryptopay_button": "💎 CryptoBot",