106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
"""Heleket payment integration."""
|
|
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import aiohttp
|
|
|
|
from config.settings import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _make_sign(body_bytes: bytes, api_key: str) -> str:
|
|
"""Генерирует подпись запроса: md5(base64(body) + api_key)."""
|
|
b64 = base64.b64encode(body_bytes).decode("utf-8")
|
|
return hashlib.md5((b64 + api_key).encode("utf-8")).hexdigest()
|
|
|
|
|
|
class HelketPayment:
|
|
"""Heleket payment gateway. Принимает сумму в рублях напрямую."""
|
|
|
|
BASE_URL = "https://api.heleket.com/v1"
|
|
|
|
def __init__(self):
|
|
self.api_key = settings.HELEKET_API_KEY
|
|
|
|
def _post_headers(self, body_bytes: bytes) -> dict:
|
|
"""Заголовки для POST-запроса с подписью."""
|
|
return {
|
|
"merchant": self.api_key,
|
|
"sign": _make_sign(body_bytes, self.api_key),
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
def _get_headers(self) -> dict:
|
|
"""Заголовки для GET-запроса (тело пустое)."""
|
|
return {
|
|
"merchant": self.api_key,
|
|
"sign": _make_sign(b"", self.api_key),
|
|
}
|
|
|
|
async def create_invoice(self, amount_rub: int, order_id: str) -> Optional[dict]:
|
|
"""
|
|
Создаёт счёт на сумму в рублях.
|
|
Возвращает dict: payment_id, address, url. None при ошибке.
|
|
"""
|
|
if not self.api_key:
|
|
logger.error("HELEKET_API_KEY не задан")
|
|
return None
|
|
|
|
payload = {
|
|
"amount": str(amount_rub),
|
|
"currency": "RUB",
|
|
"order_id": order_id,
|
|
"url_callback": settings.HELEKET_WEBHOOK_URL,
|
|
}
|
|
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
|
|
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{self.BASE_URL}/payment",
|
|
data=body,
|
|
headers=self._post_headers(body),
|
|
timeout=aiohttp.ClientTimeout(total=15),
|
|
) as resp:
|
|
data = await resp.json()
|
|
if data.get("state") == 0:
|
|
r = data.get("result", {})
|
|
return {
|
|
"payment_id": r.get("uuid"),
|
|
"address": r.get("address"),
|
|
"url": r.get("url"),
|
|
"mock": False,
|
|
}
|
|
logger.error(f"Heleket create error: {data}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Heleket request error: {e}")
|
|
return None
|
|
|
|
async def check_status(self, payment_id: str) -> str:
|
|
"""Возвращает: 'paid' | 'pending' | 'expired' | 'error'"""
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(
|
|
f"{self.BASE_URL}/payment/info/{payment_id}",
|
|
headers=self._get_headers(),
|
|
timeout=aiohttp.ClientTimeout(total=15),
|
|
) as resp:
|
|
data = await resp.json()
|
|
if data.get("state") == 0:
|
|
status = data.get("result", {}).get("status", "")
|
|
if status in ("paid", "paid_over"):
|
|
return "paid"
|
|
if status in ("cancel", "expired", "fail", "wrong_amount"):
|
|
return "expired"
|
|
return "pending"
|
|
return "error"
|
|
except Exception as e:
|
|
logger.error(f"Heleket check error: {e}")
|
|
return "error"
|