feat: add wata payment provider

This commit is contained in:
3252a8
2026-05-17 23:09:04 +03:00
parent 11429e887e
commit fff7e90e14
26 changed files with 996 additions and 6 deletions
@@ -13,6 +13,7 @@ from bot.services.referral_service import ReferralService
from bot.services.severpay_service import SeverPayService
from bot.services.stars_service import StarsService
from bot.services.subscription_service import SubscriptionService
from bot.services.wata_service import WataService
from bot.services.yookassa_service import YooKassaService
from config.settings import Settings
@@ -65,6 +66,15 @@ def build_core_services(
referral_service=referral_service,
default_return_url=bot_username_for_default_return,
)
wata_service = WataService(
bot=bot,
settings=settings,
i18n=i18n,
async_session_factory=async_session_factory,
subscription_service=subscription_service,
referral_service=referral_service,
default_return_url=bot_username_for_default_return,
)
panel_webhook_service = PanelWebhookService(
bot, settings, i18n, async_session_factory, panel_service
)
@@ -101,4 +111,5 @@ def build_core_services(
"lknpd_service": lknpd_service,
"platega_service": platega_service,
"severpay_service": severpay_service,
"wata_service": wata_service,
}
@@ -295,6 +295,60 @@ SETTINGS_MANIFEST: List[SettingField] = [
min=30,
max=4320,
),
# Wata
SettingField("WATA_ENABLED", "bool", "payments", "Enabled", subsection="Wata"),
SettingField(
"WATA_API_TOKEN",
"string",
"payments",
"API token",
subsection="Wata",
secret=True,
),
SettingField(
"WATA_BASE_URL",
"url",
"payments",
"Base URL",
placeholder="https://api.wata.pro/api/h2h",
subsection="Wata",
),
SettingField("WATA_RETURN_URL", "url", "payments", "Return URL", subsection="Wata"),
SettingField("WATA_FAILED_URL", "url", "payments", "Failed URL", subsection="Wata"),
SettingField(
"WATA_PAYMENT_LINK_TTL_DAYS",
"int",
"payments",
"Payment link lifetime (days)",
"1..30; Wata defaults to 3 days and allows up to 30 days.",
subsection="Wata",
min=1,
max=30,
),
SettingField(
"WATA_WEBHOOK_VERIFY_SIGNATURE",
"bool",
"payments",
"Verify webhook signature",
subsection="Wata",
),
SettingField(
"WATA_PUBLIC_KEY",
"text",
"payments",
"Webhook public key",
"Optional. If empty, the backend fetches it from Wata.",
subsection="Wata",
secret=True,
),
SettingField(
"WATA_TRUSTED_IPS",
"string",
"payments",
"Trusted IPs",
"Comma-separated IP addresses accepted for Wata webhooks.",
subsection="Wata",
),
# CryptoPay
SettingField("CRYPTOPAY_ENABLED", "bool", "payments", "Включена", subsection="CryptoPay"),
SettingField(
+7
View File
@@ -41,6 +41,7 @@ def _inject_shared_instances(
"panel_webhook_service",
"platega_service",
"severpay_service",
"wata_service",
):
if hasattr(dp, "workflow_data") and key in dp.workflow_data: # type: ignore
app[key] = dp.workflow_data[key] # type: ignore
@@ -96,6 +97,7 @@ async def build_and_start_web_app(
from bot.services.panel_webhook_service import panel_webhook_route
from bot.services.platega_service import platega_webhook_route
from bot.services.severpay_service import severpay_webhook_route
from bot.services.wata_service import wata_webhook_route
cp_path = settings.cryptopay_webhook_path
if cp_path.startswith("/"):
@@ -117,6 +119,11 @@ async def build_and_start_web_app(
app.router.add_post(sp_path, severpay_webhook_route)
logging.info(f"SeverPay webhook route configured at: [POST] {sp_path}")
wata_path = settings.wata_webhook_path
if wata_path.startswith("/"):
app.router.add_post(wata_path, wata_webhook_route)
logging.info(f"Wata webhook route configured at: [POST] {wata_path}")
# YooKassa webhook (register only when base URL present and path configured)
yk_path = settings.yookassa_webhook_path
if settings.WEBHOOK_BASE_URL and yk_path and yk_path.startswith("/"):
+1
View File
@@ -52,6 +52,7 @@ from bot.services.promo_code_service import PromoCodeService
from bot.services.referral_service import ReferralService
from bot.services.severpay_service import SeverPayService
from bot.services.subscription_service import SubscriptionService
from bot.services.wata_service import WataService
from bot.services.yookassa_service import YooKassaService
from bot.utils.config_link import prepare_config_links
from bot.utils.request_security import parse_ip_entries, request_client_ip
@@ -45,6 +45,7 @@ def create_subscription_webapp_application(
"cryptopay_service",
"platega_service",
"severpay_service",
"wata_service",
"promo_code_service",
"referral_service",
"panel_service",
+84
View File
@@ -708,6 +708,19 @@ async def _create_subscription_payment(
sale_mode=sale_mode,
traffic_gb=traffic_gb,
)
if method == "wata":
if not settings.WATA_ENABLED:
return _json_error(400, "payment_unavailable", "Payment method unavailable")
return await _create_wata_payment(
request,
session,
user_id,
months,
price,
description,
sale_mode=sale_mode,
traffic_gb=traffic_gb,
)
if method == "cryptopay":
service: CryptoPayService = request.app["cryptopay_service"]
if not settings.CRYPTOPAY_ENABLED or not service or not service.configured:
@@ -1118,6 +1131,77 @@ async def _create_severpay_payment(
return _json_error(502, "payment_failed", "Failed to create payment")
async def _create_wata_payment(
request: web.Request,
session: AsyncSession,
user_id: int,
months: Any,
price: float,
description: str,
*,
sale_mode: str = "subscription",
traffic_gb: Optional[float] = None,
) -> web.Response:
settings: Settings = request.app["settings"]
service: WataService = request.app["wata_service"]
if not service or not service.configured:
return _json_error(400, "payment_unavailable", "Payment method unavailable")
try:
traffic_sale = _sale_mode_is_traffic(sale_mode)
hwid_devices_sale = _sale_mode_is_hwid_devices(sale_mode)
payment = await _create_base_payment_record(
session,
user_id=user_id,
amount=price,
currency=settings.DEFAULT_CURRENCY_SYMBOL or "RUB",
status="pending_wata",
description=description,
months=int(float(months)) if not traffic_sale else int(float(traffic_gb or months)),
provider="wata",
sale_mode=sale_mode,
tariff_key=_sale_mode_tariff_key(sale_mode),
purchased_gb=float(traffic_gb or months) if traffic_sale else None,
purchased_hwid_devices=int(float(months)) if hwid_devices_sale else None,
)
success, response_data = await service.create_payment_link(
payment_db_id=payment.payment_id,
amount=price,
currency=settings.DEFAULT_CURRENCY_SYMBOL or "RUB",
description=description,
)
payment_url = response_data.get("url") if success else None
provider_id = response_data.get("id")
if provider_id:
await payment_dal.update_provider_payment_and_status(
session,
payment.payment_id,
str(provider_id),
payment.status,
)
await session.commit()
if not payment_url:
await payment_dal.update_payment_status_by_db_id(
session,
payment.payment_id,
"failed_creation",
)
await session.commit()
return _json_error(502, "payment_failed", "Failed to create payment")
return web.json_response(
{
"ok": True,
"action": "open_link",
"payment_url": payment_url,
"payment_id": payment.payment_id,
}
)
except Exception:
await session.rollback()
logger.exception("Wata WebApp payment failed")
return _json_error(502, "payment_failed", "Failed to create payment")
async def _create_stars_payment(
request: web.Request,
session: AsyncSession,
@@ -568,6 +568,7 @@ def _serialize_payment_methods(
app: web.Application,
) -> List[Dict[str, Any]]:
labels = {
"wata": "Wata",
"severpay": "SeverPay",
"freekassa": "FreeKassa / СБП",
"platega_sbp": "Platega · СБП",
@@ -605,6 +606,12 @@ def _serialize_payment_methods(
and _service_configured(app, "platega_service")
):
methods.append({"id": method, "name": labels[method]})
elif (
method == "wata"
and settings.WATA_ENABLED
and _service_configured(app, "wata_service")
):
methods.append({"id": method, "name": labels[method]})
elif (
method == "yookassa"
and settings.YOOKASSA_ENABLED