From dcfcf8f650bc39334ede26f2f88d299f0fde3c83 Mon Sep 17 00:00:00 2001 From: savsis Date: Sun, 13 Sep 2026 19:50:54 +0500 Subject: [PATCH] feat: Platega credentials section in the Payments wizard, for parity with YooKassa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The setup wizard added last iteration only covered YooKassa, leaving Platega (the second provider this panel has always supported) still .env-only despite everything else in the payments flow treating both providers symmetrically. Same GET/POST shape as the YooKassa settings routes. Platega has no documented lightweight credentials-check endpoint like YooKassa's /v3/me, so this one honestly says so in the UI instead of saving through a fabricated validation call — it saves directly and a bad key will surface on the first real payment attempt instead of at save time. Co-Authored-By: Claude Sonnet 5 --- admin.html | 42 ++++++++++++++++++++++++++++++++++++++---- api.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/admin.html b/admin.html index 69cc53a..1bf799a 100644 --- a/admin.html +++ b/admin.html @@ -541,6 +541,16 @@

Панель сама постучится в ЮKassa (/v3/me) и сохранит ключи только если они рабочие. После сохранения включаются приём оплаты и вебхуки; бот перезапустится сам, для самой панели (обработка вебхуков) один раз выполни на сервере mbs restart.

+ +

Platega — ключи API

+
+
+
+
+
+
+

У Platega нет публичного эндпоинта для проверки ключей без реального платежа, так что сохраняется без предварительной проверки — если ключи неверные, это будет видно по первой неудачной оплате. Тот же рестарт нужен, что и для ЮKassa.

+
@@ -1000,9 +1010,10 @@ async function checkPayment(id, btn) { } async function loadPaymentsSettings() { - const [legalRes, ykRes] = await Promise.all([ + const [legalRes, ykRes, pgRes] = await Promise.all([ api("/admin/api/payments/legal-settings"), api("/admin/api/payments/yookassa-settings"), + api("/admin/api/payments/platega-settings"), ]); document.getElementById("legal-name").value = legalRes.LEGAL_NAME || ""; document.getElementById("legal-inn").value = legalRes.LEGAL_INN || ""; @@ -1011,11 +1022,19 @@ async function loadPaymentsSettings() { document.getElementById("legal-email").value = legalRes.SUPPORT_EMAIL || ""; document.getElementById("yk-shop-id").value = ykRes.shop_id || ""; - const status = document.getElementById("yookassa-status"); + const ykStatus = document.getElementById("yookassa-status"); if (ykRes.enabled && ykRes.has_secret) { - status.innerHTML = 'подключена shop_id: ' + esc(ykRes.shop_id); + ykStatus.innerHTML = 'подключена shop_id: ' + esc(ykRes.shop_id); } else { - status.innerHTML = 'не настроена'; + ykStatus.innerHTML = 'не настроена'; + } + + document.getElementById("pg-merchant-id").value = pgRes.merchant_id || ""; + const pgStatus = document.getElementById("platega-status"); + if (pgRes.enabled && pgRes.has_secret) { + pgStatus.innerHTML = 'подключена merchant_id: ' + esc(pgRes.merchant_id); + } else { + pgStatus.innerHTML = 'не настроена'; } } @@ -1052,6 +1071,21 @@ async function saveYookassaSettings() { } } +async function savePlategaSettings() { + const merchant_id = document.getElementById("pg-merchant-id").value.trim(); + const secret = document.getElementById("pg-secret").value.trim(); + const result = document.getElementById("platega-result"); + if (!merchant_id || !secret) return; + try { + const res = await api("/admin/api/payments/platega-settings", { method: "POST", body: JSON.stringify({ merchant_id, secret }) }); + result.innerHTML = `

Сохранено.${res.restarted_bot ? " Бот перезапущен." : " Бот сам не перезапустился — выполни mbs restart."} Для приёма вебхуков панелью выполни на сервере mbs restart.

`; + document.getElementById("pg-secret").value = ""; + loadPaymentsSettings(); + } catch (e) { + result.innerHTML = '

Не получилось: ' + esc(e.message) + '

'; + } +} + async function loadBotSettings() { const data = await api("/admin/api/settings/bot"); document.getElementById("settings-bot-username").textContent = "@" + data.username; diff --git a/api.py b/api.py index a034366..7615f2e 100644 --- a/api.py +++ b/api.py @@ -426,6 +426,36 @@ def admin_set_yookassa_settings(request: Request, body: dict = Body(...)): return {"ok": True, "restarted_bot": restarted} +@app.get("/admin/api/payments/platega-settings") +def admin_get_platega_settings(request: Request): + require_admin(request) + return { + "enabled": payments.PLATEGA_ENABLED, + "merchant_id": legal.read_env_var("PLATEGA_MERCHANT_ID", ""), + "has_secret": bool(legal.read_env_var("PLATEGA_SECRET", "")), + } + + +@app.post("/admin/api/payments/platega-settings") +def admin_set_platega_settings(request: Request, body: dict = Body(...)): + require_admin(request) + merchant_id = (body.get("merchant_id") or "").strip() + secret = (body.get("secret") or "").strip() + if not merchant_id or not secret: + raise HTTPException(400, "merchant_id и secret обязательны") + _update_env_var("PLATEGA_MERCHANT_ID", merchant_id) + _update_env_var("PLATEGA_SECRET", secret) + _update_env_var("PLATEGA_ENABLED", "true") + _update_env_var("PAYMENTS_ENABLED", "true") + restarted = False + try: + subprocess.run(["systemctl", "restart", "mbs-bot"], check=True, timeout=15) + restarted = True + except Exception: + restarted = False + return {"ok": True, "restarted_bot": restarted} + + @app.post("/payments/webhook/yookassa") async def yookassa_webhook(request: Request): body = await request.json()