feat: outbound webhooks for revoke/hold/resume — only grant and payment fired before
Found while re-reading the subscription lifecycle routes: payment.paid and subscription.granted_by_admin fire a webhook, but revoke (which has existed the whole night) and the two new hold/resume routes did not. Inconsistent for anyone actually wiring this into a CRM/support tool — they'd see a subscription get granted but never find out it was later paused, resumed, or cut off entirely, since only the "gains access" side of the lifecycle was ever reported outward. Three new events, same shape and delivery as the existing ones: subscription.revoked, subscription.held, subscription.resumed. Added right where the DB/xray state change already happens in each route, so they're conditioned on the action actually succeeding (a hold attempt on an already-held/expired subscription 400s before ever reaching the webhooks.send call). Verification: webhooks.py itself is unchanged — this only adds new call sites with new event-name strings, so re-verified the exact thing the original webhook feature proved: stood up a real local HTTP server, fired all three new events through the actual webhooks.send(), and had the receiver independently recompute the HMAC from its own copy of the secret and compare against the X-Signature header it actually got, for all three — not just trusting that the sender computed something. Checked the JSON envelope and data payload match what each route sends byte for byte. api.py itself still can't be imported locally, same wall as always; the new lines were checked by reading the subscription-row shape they pull from (tg_id/node/plan are all real columns already confirmed present in every prior test this session) plus the standard py_compile + pyflakes pass, clean across the whole repo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
71c98c5032
commit
f6e4e52b65
2 changed files with 12 additions and 2 deletions
|
|
@ -611,7 +611,7 @@
|
|||
<h2>Платежи и вебхуки</h2>
|
||||
<p>Вкладка Платежи → «Настройка приёма платежей» собирает публичную оферту и политику конфиденциальности (<code>/offer</code>, <code>/privacy</code>) из введённых реквизитов — ЮKassa их спросит при регистрации магазина. Дата вступления в силу проставляется один раз, правки реквизитов её не двигают.</p>
|
||||
<p>Ключи ЮKassa проверяются вживую через их <code>/v3/me</code> перед сохранением; у Platega такого эндпоинта нет, ключи сохраняются без проверки. Оба провайдера включаются независимо.</p>
|
||||
<p>Исходящие вебхуки (Настройки → Webhook на события) — панель стучится на указанный URL при оплате (<code>payment.paid</code>) и ручной выдаче подписки админом (<code>subscription.granted_by_admin</code>), тело подписано <code>X-Signature</code> (HMAC-SHA256). Секрет выдаётся один раз и не меняется при правке URL — для интеграций со своими системами, без опроса API.</p>
|
||||
<p>Исходящие вебхуки (Настройки → Webhook на события) — панель стучится на указанный URL при оплате (<code>payment.paid</code>), ручной выдаче подписки админом (<code>subscription.granted_by_admin</code>), отзыве (<code>subscription.revoked</code>), постановке на паузу (<code>subscription.held</code>) и возобновлении (<code>subscription.resumed</code>). Тело подписано <code>X-Signature</code> (HMAC-SHA256). Секрет выдаётся один раз и не меняется при правке URL — для интеграций со своими системами, без опроса API.</p>
|
||||
<p>Вкладка Платежи → «Тарифы» — цены по срокам и общий рубильник приёма оплаты. Как и реквизиты с ключами провайдеров, это читается панелью напрямую из <code>.env</code> при каждом запросе — правки в UI применяются мгновенно везде (бот, API, проверка вебхуков), рестарт панели нигде не требуется.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -718,7 +718,7 @@
|
|||
<div style="flex:0"><label class="f"> </label><button class="btn" onclick="saveWebhookSettings()">Сохранить</button></div>
|
||||
</div>
|
||||
<p class="check-hint">
|
||||
События: <code>payment.paid</code>, <code>subscription.granted_by_admin</code>. Тело — JSON <code>{"event": "...", "data": {...}}</code>, подписано заголовком <code>X-Signature</code> (HMAC-SHA256 от тела запроса на секрете ниже) — так получатель проверяет, что запрос реально от панели.
|
||||
События: <code>payment.paid</code>, <code>subscription.granted_by_admin</code>, <code>subscription.revoked</code>, <code>subscription.held</code>, <code>subscription.resumed</code>. Тело — JSON <code>{"event": "...", "data": {...}}</code>, подписано заголовком <code>X-Signature</code> (HMAC-SHA256 от тела запроса на секрете ниже) — так получатель проверяет, что запрос реально от панели.
|
||||
Секрет для проверки: <code id="webhook-secret-display">—</code>
|
||||
</p>
|
||||
<div id="webhook-result"></div>
|
||||
|
|
|
|||
10
api.py
10
api.py
|
|
@ -887,6 +887,9 @@ def admin_revoke_subscription(uuid: str, request: Request):
|
|||
if node:
|
||||
xray_manager.remove_client_from_node(node, uuid)
|
||||
db.revoke_subscription(uuid)
|
||||
webhooks.send("subscription.revoked", {
|
||||
"tg_id": sub["tg_id"], "node": sub["node"], "plan": sub["plan"], "subscription_uuid": uuid,
|
||||
})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
|
|
@ -901,6 +904,9 @@ def admin_hold_subscription(uuid: str, request: Request):
|
|||
node = db.get_node(sub["node"])
|
||||
if node:
|
||||
xray_manager.remove_client_from_node(node, uuid)
|
||||
webhooks.send("subscription.held", {
|
||||
"tg_id": sub["tg_id"], "node": sub["node"], "plan": sub["plan"], "subscription_uuid": uuid,
|
||||
})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
|
|
@ -913,6 +919,10 @@ def admin_resume_subscription(uuid: str, request: Request):
|
|||
node = db.get_node(resumed["node"])
|
||||
if node:
|
||||
xray_manager.add_client_to_node(node, uuid, email=uuid)
|
||||
webhooks.send("subscription.resumed", {
|
||||
"tg_id": resumed["tg_id"], "node": resumed["node"], "plan": resumed["plan"],
|
||||
"subscription_uuid": uuid, "expires_at": resumed["expires_at"],
|
||||
})
|
||||
return {"ok": True, "expires_at": resumed["expires_at"]}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue