mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-05 13:23:48 +00:00
Update freekassa_service.py
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Сервис для работы с API Freekassa."""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, Set
|
||||
@@ -55,15 +56,31 @@ class FreekassaService:
|
||||
self._secret2 = settings.FREEKASSA_SECRET_WORD_2
|
||||
return self._secret2 or ""
|
||||
|
||||
def _generate_api_signature_hmac(self, params: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Генерирует подпись для API запроса (HMAC-SHA256).
|
||||
Используется для API методов (создание заказа и т.д.)
|
||||
"""
|
||||
# Исключаем signature из параметров и сортируем по ключу
|
||||
sign_data = {k: v for k, v in params.items() if k != "signature"}
|
||||
sorted_items = sorted(sign_data.items())
|
||||
|
||||
# Формируем строку: значения через |
|
||||
msg = "|".join(str(v) for _, v in sorted_items)
|
||||
|
||||
# HMAC-SHA256
|
||||
return hmac.new(
|
||||
self.api_key.encode("utf-8"),
|
||||
msg.encode("utf-8"),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
def _generate_api_signature(self, params: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Генерирует подпись для API запроса.
|
||||
Сортировка по ключам, конкатенация значений через |
|
||||
Для новых API методов используется HMAC-SHA256.
|
||||
"""
|
||||
sorted_keys = sorted(params.keys())
|
||||
values = [str(params[k]) for k in sorted_keys if params[k] is not None]
|
||||
sign_string = "|".join(values)
|
||||
return hashlib.md5(sign_string.encode()).hexdigest()
|
||||
return self._generate_api_signature_hmac(params)
|
||||
|
||||
def generate_form_signature(
|
||||
self, amount: float, currency: str, order_id: str
|
||||
@@ -72,7 +89,9 @@ class FreekassaService:
|
||||
Генерирует подпись для платежной формы.
|
||||
Формат: MD5(shop_id:amount:secret1:currency:order_id)
|
||||
"""
|
||||
sign_string = f"{self.shop_id}:{amount}:{self.secret1}:{currency}:{order_id}"
|
||||
# Приводим amount к int, если это целое число
|
||||
final_amount = int(amount) if float(amount).is_integer() else amount
|
||||
sign_string = f"{self.shop_id}:{final_amount}:{self.secret1}:{currency}:{order_id}"
|
||||
return hashlib.md5(sign_string.encode()).hexdigest()
|
||||
|
||||
def verify_webhook_signature(
|
||||
@@ -82,8 +101,10 @@ class FreekassaService:
|
||||
Проверяет подпись webhook уведомления.
|
||||
Формат: MD5(shop_id:amount:secret2:order_id)
|
||||
"""
|
||||
# Приводим amount к int, если это целое число
|
||||
final_amount = int(amount) if float(amount).is_integer() else amount
|
||||
expected_sign = hashlib.md5(
|
||||
f"{shop_id}:{amount}:{self.secret2}:{order_id}".encode()
|
||||
f"{shop_id}:{final_amount}:{self.secret2}:{order_id}".encode()
|
||||
).hexdigest()
|
||||
return sign.lower() == expected_sign.lower()
|
||||
|
||||
@@ -102,13 +123,16 @@ class FreekassaService:
|
||||
lang: str = "ru",
|
||||
) -> str:
|
||||
"""
|
||||
Формирует URL для перенаправления на оплату.
|
||||
Формирует URL для перенаправления на оплату (форма выбора).
|
||||
Используется когда FREEKASSA_USE_API = False.
|
||||
"""
|
||||
signature = self.generate_form_signature(amount, currency, order_id)
|
||||
# Приводим amount к int, если это целое число
|
||||
final_amount = int(amount) if float(amount).is_integer() else amount
|
||||
signature = self.generate_form_signature(final_amount, currency, order_id)
|
||||
|
||||
params = {
|
||||
"m": self.shop_id,
|
||||
"oa": amount,
|
||||
"oa": final_amount,
|
||||
"currency": currency,
|
||||
"o": order_id,
|
||||
"s": signature,
|
||||
@@ -119,8 +143,11 @@ class FreekassaService:
|
||||
params["em"] = email
|
||||
if phone:
|
||||
params["phone"] = phone
|
||||
if payment_system_id:
|
||||
params["i"] = payment_system_id
|
||||
|
||||
# Используем payment_system_id из настроек, если не передан явно
|
||||
ps_id = payment_system_id or settings.FREEKASSA_PAYMENT_SYSTEM_ID
|
||||
if ps_id:
|
||||
params["i"] = ps_id
|
||||
|
||||
query = "&".join(f"{k}={v}" for k, v in params.items())
|
||||
return f"https://pay.freekassa.ru/?{query}"
|
||||
@@ -140,27 +167,32 @@ class FreekassaService:
|
||||
"""
|
||||
Создает заказ через API Freekassa.
|
||||
POST /orders/create
|
||||
|
||||
Используется для NSPK СБП (payment_system_id=44) и других методов.
|
||||
Возвращает словарь с 'location' (ссылка на оплату).
|
||||
"""
|
||||
# Приводим amount к int, если это целое число
|
||||
final_amount = int(amount) if float(amount).is_integer() else amount
|
||||
|
||||
# Используем payment_system_id из настроек, если не передан явно
|
||||
ps_id = payment_system_id or settings.FREEKASSA_PAYMENT_SYSTEM_ID or 1
|
||||
|
||||
params = {
|
||||
"shopId": self.shop_id,
|
||||
"nonce": int(time.time() * 1000),
|
||||
"paymentId": order_id,
|
||||
"i": payment_system_id or 1,
|
||||
"nonce": int(time.time_ns()), # Наносекунды для уникальности
|
||||
"paymentId": str(order_id),
|
||||
"i": ps_id,
|
||||
"email": email or "user@example.com",
|
||||
"ip": ip or "127.0.0.1",
|
||||
"amount": amount,
|
||||
"amount": final_amount,
|
||||
"currency": currency,
|
||||
}
|
||||
|
||||
if success_url:
|
||||
params["success_url"] = success_url
|
||||
if failure_url:
|
||||
params["failure_url"] = failure_url
|
||||
if notification_url:
|
||||
params["notification_url"] = notification_url
|
||||
|
||||
# Генерируем подпись HMAC-SHA256
|
||||
params["signature"] = self._generate_api_signature(params)
|
||||
|
||||
logger.info(f"Freekassa API create_order params: {params}")
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
@@ -169,6 +201,9 @@ class FreekassaService:
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=aiohttp.ClientTimeout(total=30),
|
||||
) as response:
|
||||
text = await response.text()
|
||||
logger.info(f"Freekassa API response: {text}")
|
||||
|
||||
data = await response.json()
|
||||
|
||||
if response.status != 200 or data.get("type") == "error":
|
||||
@@ -182,6 +217,32 @@ class FreekassaService:
|
||||
logger.exception(f"Freekassa API connection error: {e}")
|
||||
raise
|
||||
|
||||
async def create_order_and_get_url(
|
||||
self,
|
||||
order_id: str,
|
||||
amount: float,
|
||||
currency: str = "RUB",
|
||||
email: Optional[str] = None,
|
||||
ip: Optional[str] = None,
|
||||
payment_system_id: Optional[int] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Создает заказ через API и возвращает URL для оплаты.
|
||||
Удобный метод для получения только ссылки.
|
||||
"""
|
||||
result = await self.create_order(
|
||||
order_id=order_id,
|
||||
amount=amount,
|
||||
currency=currency,
|
||||
email=email,
|
||||
ip=ip,
|
||||
payment_system_id=payment_system_id,
|
||||
)
|
||||
location = result.get("location")
|
||||
if not location:
|
||||
raise Exception("Freekassa API did not return payment URL (location)")
|
||||
return location
|
||||
|
||||
async def get_order_status(self, order_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Получает статус заказа.
|
||||
@@ -189,11 +250,13 @@ class FreekassaService:
|
||||
"""
|
||||
params = {
|
||||
"shopId": self.shop_id,
|
||||
"nonce": int(time.time() * 1000),
|
||||
"paymentId": order_id,
|
||||
"nonce": int(time.time_ns()),
|
||||
"paymentId": str(order_id),
|
||||
}
|
||||
params["signature"] = self._generate_api_signature(params)
|
||||
|
||||
logger.info(f"Freekassa get_order_status params: {params}")
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
@@ -202,6 +265,8 @@ class FreekassaService:
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=aiohttp.ClientTimeout(total=30),
|
||||
) as response:
|
||||
text = await response.text()
|
||||
logger.info(f"Freekassa get_order_status response: {text}")
|
||||
return await response.json()
|
||||
except aiohttp.ClientError as e:
|
||||
logger.exception(f"Freekassa API connection error: {e}")
|
||||
@@ -211,7 +276,7 @@ class FreekassaService:
|
||||
"""Получает баланс магазина."""
|
||||
params = {
|
||||
"shopId": self.shop_id,
|
||||
"nonce": int(time.time() * 1000),
|
||||
"nonce": int(time.time_ns()),
|
||||
}
|
||||
params["signature"] = self._generate_api_signature(params)
|
||||
|
||||
@@ -232,7 +297,7 @@ class FreekassaService:
|
||||
"""Получает список доступных платежных систем."""
|
||||
params = {
|
||||
"shopId": self.shop_id,
|
||||
"nonce": int(time.time() * 1000),
|
||||
"nonce": int(time.time_ns()),
|
||||
}
|
||||
params["signature"] = self._generate_api_signature(params)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user