# app.py — минимальный сквозной пример приёма платежей Oblodai
import hmac, hashlib, time, json, os
import requests
from flask import Flask, request, jsonify
# --- Конфигурация (в проде — из секрет-хранилища, не из кода) ---
SECRET = os.environ["OBLODAI_SECRET"].encode() # секрет API-ключа
PUBLIC_ID = os.environ["OBLODAI_PUBLIC_ID"]
WEBHOOK_SECRET = os.environ["OBLODAI_WEBHOOK_SECRET"].encode() # секрет из /v1/webhooks
BASE = "https://api.oblodai.com"
app = Flask(__name__)
ORDERS = {} # order_id -> {"status": ..., "amount": ...} (замените на БД)
SEEN = set() # (uuid, status) — дедупликация вебхуков (замените на БД)
# --- Подписанный вызов API ---
def call(path: str, payload: dict, idempotency_key: str | None = None) -> dict:
body = json.dumps(payload, separators=(",", ":"))
ts = str(int(time.time()))
signing = f"{ts}\nPOST\n{path}\n{body}"
sig = hmac.new(SECRET, signing.encode(), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-Public-Id": PUBLIC_ID,
"X-Timestamp": ts,
"X-Signature": sig,
}
if idempotency_key:
# Защита от дублей при ретраях. В подпись НЕ входит.
headers["Idempotency-Key"] = idempotency_key
r = requests.post(BASE + path, data=body, headers=headers, timeout=30)
return r.json()
# --- 1. Оформление заказа: создаём счёт ---
@app.post("/checkout")
def checkout():
data = request.get_json()
order_id = data["order_id"] # ваш идентификатор заказа
amount = data["amount"] # сумма в USD
resp = call("/v1/payment", {
"amount": amount,
"currency": "USD", # валюта ЦЕНЫ: USD, EUR, RUB… (23 фиата) или монета
"order_id": order_id, # ваш бизнес-ключ: тоже дедуплицирует
"to_currency": "USDT",
"network": "tron",
"lifetime": 3600,
}, idempotency_key=f"checkout-{order_id}") # один ключ на действие, стабилен при ретраях
if "error" in resp:
return jsonify({"error": resp["error"]}), 400
inv = resp["result"]
ORDERS[order_id] = {"status": "pending", "amount": amount, "uuid": inv["uuid"]}
# Покупателю отдаём ссылку на hosted-страницу оплаты
return jsonify({"pay_url": inv["url"], "address": inv["address"]})
# --- 2. Вебхук о поступлении ---
@app.post("/oblodai/callback")
def callback():
raw = request.get_data() # СЫРОЕ тело — не пересериализовывать
event = request.get_json(silent=True) or {}
# Пробные тела не подписаны — просто подтверждаем приём
if event.get("is_test"):
return "ok", 200
ts = request.headers.get("X-Webhook-Timestamp", "")
sig = request.headers.get("X-Webhook-Signature", "")
expected = hmac.new(WEBHOOK_SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
return "bad signature", 403
# Идемпотентность: один вебхук может прийти несколько раз
key = (event.get("uuid"), event.get("status"))
if key in SEEN:
return "ok", 200 # уже обработано — no-op
SEEN.add(key)
# Реагируем на статус
if event.get("type") == "payment" and event.get("status") == "paid":
order_id = event.get("order_id")
if order_id in ORDERS:
ORDERS[order_id]["status"] = "paid"
# ... здесь выдаём товар/услугу (тоже идемпотентно)
return "ok", 200 # 2xx только после успешной обработки
# --- 3. Статус заказа (для наглядности) ---
@app.get("/order/<order_id>")
def order(order_id):
return jsonify(ORDERS.get(order_id, {"status": "unknown"}))
if __name__ == "__main__":
app.run(port=8000)