Add files via upload

This commit is contained in:
Egor
2026-01-06 20:56:44 +03:00
committed by GitHub
parent 7c2408209d
commit 53a10c0640
6 changed files with 834 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
"""Сервис для работы с API Freekassa."""
import hashlib
import time
import logging
from typing import Optional, Dict, Any, Set
import aiohttp
from app.config import settings
logger = logging.getLogger(__name__)
# IP-адреса Freekassa для проверки webhook
FREEKASSA_IPS: Set[str] = {
"168.119.157.136",
"168.119.60.227",
"178.154.197.79",
"51.250.54.238",
}
API_BASE_URL = "https://api.fk.life/v1"
class FreekassaService:
"""Сервис для работы с API Freekassa."""
def __init__(self):
self._shop_id: Optional[int] = None
self._api_key: Optional[str] = None
self._secret1: Optional[str] = None
self._secret2: Optional[str] = None
@property
def shop_id(self) -> int:
if self._shop_id is None:
self._shop_id = settings.FREEKASSA_SHOP_ID
return self._shop_id or 0
@property
def api_key(self) -> str:
if self._api_key is None:
self._api_key = settings.FREEKASSA_API_KEY
return self._api_key or ""
@property
def secret1(self) -> str:
if self._secret1 is None:
self._secret1 = settings.FREEKASSA_SECRET_WORD_1
return self._secret1 or ""
@property
def secret2(self) -> str:
if self._secret2 is None:
self._secret2 = settings.FREEKASSA_SECRET_WORD_2
return self._secret2 or ""
def _generate_api_signature(self, params: Dict[str, Any]) -> str:
"""
Генерирует подпись для API запроса.
Сортировка по ключам, конкатенация значений через |
"""
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()
def generate_form_signature(
self, amount: float, currency: str, order_id: str
) -> str:
"""
Генерирует подпись для платежной формы.
Формат: MD5(shop_id:amount:secret1:currency:order_id)
"""
sign_string = f"{self.shop_id}:{amount}:{self.secret1}:{currency}:{order_id}"
return hashlib.md5(sign_string.encode()).hexdigest()
def verify_webhook_signature(
self, shop_id: int, amount: float, order_id: str, sign: str
) -> bool:
"""
Проверяет подпись webhook уведомления.
Формат: MD5(shop_id:amount:secret2:order_id)
"""
expected_sign = hashlib.md5(
f"{shop_id}:{amount}:{self.secret2}:{order_id}".encode()
).hexdigest()
return sign.lower() == expected_sign.lower()
def verify_webhook_ip(self, ip: str) -> bool:
"""Проверяет, что IP входит в разрешенный список Freekassa."""
return ip in FREEKASSA_IPS
def build_payment_url(
self,
order_id: str,
amount: float,
currency: str = "RUB",
email: Optional[str] = None,
phone: Optional[str] = None,
payment_system_id: Optional[int] = None,
lang: str = "ru",
) -> str:
"""
Формирует URL для перенаправления на оплату.
"""
signature = self.generate_form_signature(amount, currency, order_id)
params = {
"m": self.shop_id,
"oa": amount,
"currency": currency,
"o": order_id,
"s": signature,
"lang": lang,
}
if email:
params["em"] = email
if phone:
params["phone"] = phone
if payment_system_id:
params["i"] = payment_system_id
query = "&".join(f"{k}={v}" for k, v in params.items())
return f"https://pay.freekassa.ru/?{query}"
async def create_order(
self,
order_id: str,
amount: float,
currency: str = "RUB",
email: Optional[str] = None,
ip: Optional[str] = None,
payment_system_id: Optional[int] = None,
success_url: Optional[str] = None,
failure_url: Optional[str] = None,
notification_url: Optional[str] = None,
) -> Dict[str, Any]:
"""
Создает заказ через API Freekassa.
POST /orders/create
"""
params = {
"shopId": self.shop_id,
"nonce": int(time.time() * 1000),
"paymentId": order_id,
"i": payment_system_id or 1,
"email": email or "user@example.com",
"ip": ip or "127.0.0.1",
"amount": 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
params["signature"] = self._generate_api_signature(params)
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{API_BASE_URL}/orders/create",
json=params,
headers={"Content-Type": "application/json"},
timeout=aiohttp.ClientTimeout(total=30),
) as response:
data = await response.json()
if response.status != 200 or data.get("type") == "error":
logger.error(f"Freekassa create_order error: {data}")
raise Exception(
f"Freekassa API error: {data.get('message', 'Unknown error')}"
)
return data
except aiohttp.ClientError as e:
logger.exception(f"Freekassa API connection error: {e}")
raise
async def get_order_status(self, order_id: str) -> Dict[str, Any]:
"""
Получает статус заказа.
POST /orders
"""
params = {
"shopId": self.shop_id,
"nonce": int(time.time() * 1000),
"paymentId": order_id,
}
params["signature"] = self._generate_api_signature(params)
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{API_BASE_URL}/orders",
json=params,
headers={"Content-Type": "application/json"},
timeout=aiohttp.ClientTimeout(total=30),
) as response:
return await response.json()
except aiohttp.ClientError as e:
logger.exception(f"Freekassa API connection error: {e}")
raise
async def get_balance(self) -> Dict[str, Any]:
"""Получает баланс магазина."""
params = {
"shopId": self.shop_id,
"nonce": int(time.time() * 1000),
}
params["signature"] = self._generate_api_signature(params)
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{API_BASE_URL}/balance",
json=params,
headers={"Content-Type": "application/json"},
timeout=aiohttp.ClientTimeout(total=30),
) as response:
return await response.json()
except aiohttp.ClientError as e:
logger.exception(f"Freekassa API connection error: {e}")
raise
async def get_payment_systems(self) -> Dict[str, Any]:
"""Получает список доступных платежных систем."""
params = {
"shopId": self.shop_id,
"nonce": int(time.time() * 1000),
}
params["signature"] = self._generate_api_signature(params)
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{API_BASE_URL}/currencies",
json=params,
headers={"Content-Type": "application/json"},
timeout=aiohttp.ClientTimeout(total=30),
) as response:
return await response.json()
except aiohttp.ClientError as e:
logger.exception(f"Freekassa API connection error: {e}")
raise
# Singleton instance
freekassa_service = FreekassaService()
+2
View File
@@ -15,6 +15,7 @@ from .pal24 import Pal24PaymentMixin
from .platega import PlategaPaymentMixin
from .wata import WataPaymentMixin
from .cloudpayments import CloudPaymentsPaymentMixin
from .freekassa import FreekassaPaymentMixin
__all__ = [
"PaymentCommonMixin",
@@ -28,4 +29,5 @@ __all__ = [
"PlategaPaymentMixin",
"WataPaymentMixin",
"CloudPaymentsPaymentMixin",
"FreekassaPaymentMixin",
]
+516
View File
@@ -0,0 +1,516 @@
"""Mixin для интеграции с Freekassa."""
from __future__ import annotations
import json
import uuid
import logging
from datetime import datetime, timedelta
from importlib import import_module
from typing import Any, Dict, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.freekassa_service import freekassa_service
from app.services.subscription_auto_purchase_service import (
auto_activate_subscription_after_topup,
auto_purchase_saved_cart_after_topup,
)
from app.utils.user_utils import format_referrer_info
from app.utils.payment_logger import payment_logger as logger
class FreekassaPaymentMixin:
"""Mixin для работы с платежами Freekassa."""
async def create_freekassa_payment(
self,
db: AsyncSession,
*,
user_id: int,
amount_kopeks: int,
description: str = "Пополнение баланса",
email: Optional[str] = None,
language: str = "ru",
) -> Optional[Dict[str, Any]]:
"""
Создает платеж Freekassa.
Args:
db: Сессия БД
user_id: ID пользователя
amount_kopeks: Сумма в копейках
description: Описание платежа
email: Email пользователя
language: Язык интерфейса
Returns:
Словарь с данными платежа или None при ошибке
"""
if not settings.is_freekassa_enabled():
logger.error("Freekassa не настроен")
return None
# Валидация лимитов
if amount_kopeks < settings.FREEKASSA_MIN_AMOUNT_KOPEKS:
logger.warning(
"Freekassa: сумма %s меньше минимальной %s",
amount_kopeks,
settings.FREEKASSA_MIN_AMOUNT_KOPEKS,
)
return None
if amount_kopeks > settings.FREEKASSA_MAX_AMOUNT_KOPEKS:
logger.warning(
"Freekassa: сумма %s больше максимальной %s",
amount_kopeks,
settings.FREEKASSA_MAX_AMOUNT_KOPEKS,
)
return None
# Генерируем уникальный order_id
order_id = f"fk_{user_id}_{uuid.uuid4().hex[:12]}"
amount_rubles = amount_kopeks / 100
currency = settings.FREEKASSA_CURRENCY
# Срок действия платежа
expires_at = datetime.utcnow() + timedelta(
seconds=settings.FREEKASSA_PAYMENT_TIMEOUT_SECONDS
)
# Метаданные
metadata = {
"user_id": user_id,
"amount_kopeks": amount_kopeks,
"description": description,
"language": language,
"type": "balance_topup",
}
try:
# Генерируем URL для оплаты
payment_url = freekassa_service.build_payment_url(
order_id=order_id,
amount=amount_rubles,
currency=currency,
email=email,
lang=language,
)
# Импортируем CRUD модуль
freekassa_crud = import_module("app.database.crud.freekassa")
# Сохраняем в БД
local_payment = await freekassa_crud.create_freekassa_payment(
db=db,
user_id=user_id,
order_id=order_id,
amount_kopeks=amount_kopeks,
currency=currency,
description=description,
payment_url=payment_url,
expires_at=expires_at,
metadata_json=json.dumps(metadata, ensure_ascii=False),
)
logger.info(
"Freekassa: создан платеж order_id=%s, user_id=%s, amount=%s %s",
order_id,
user_id,
amount_rubles,
currency,
)
return {
"order_id": order_id,
"amount_kopeks": amount_kopeks,
"amount_rubles": amount_rubles,
"currency": currency,
"payment_url": payment_url,
"expires_at": expires_at.isoformat(),
"local_payment_id": local_payment.id,
}
except Exception as e:
logger.exception("Freekassa: ошибка создания платежа: %s", e)
return None
async def process_freekassa_webhook(
self,
db: AsyncSession,
*,
merchant_id: int,
amount: float,
order_id: str,
sign: str,
intid: str,
cur_id: Optional[int] = None,
client_ip: str,
) -> bool:
"""
Обрабатывает webhook от Freekassa.
Args:
db: Сессия БД
merchant_id: ID магазина (MERCHANT_ID)
amount: Сумма платежа (AMOUNT)
order_id: Номер заказа (MERCHANT_ORDER_ID)
sign: Подпись (SIGN)
intid: ID транзакции Freekassa
cur_id: ID валюты/платежной системы (CUR_ID)
client_ip: IP клиента
Returns:
True если платеж успешно обработан
"""
try:
# Проверка IP
if not freekassa_service.verify_webhook_ip(client_ip):
logger.warning("Freekassa webhook: недоверенный IP %s", client_ip)
return False
# Проверка подписи
if not freekassa_service.verify_webhook_signature(
merchant_id, amount, order_id, sign
):
logger.warning(
"Freekassa webhook: неверная подпись для order_id=%s", order_id
)
return False
# Импортируем CRUD модуль
freekassa_crud = import_module("app.database.crud.freekassa")
# Получаем платеж из БД
payment = await freekassa_crud.get_freekassa_payment_by_order_id(
db, order_id
)
if not payment:
logger.warning(
"Freekassa webhook: платеж не найден order_id=%s", order_id
)
return False
# Проверка дублирования
if payment.is_paid:
logger.info(
"Freekassa webhook: платеж уже обработан order_id=%s", order_id
)
return True
# Проверка суммы
expected_amount = payment.amount_kopeks / 100
if abs(amount - expected_amount) > 0.01:
logger.warning(
"Freekassa webhook: несоответствие суммы ожидалось=%s, получено=%s",
expected_amount,
amount,
)
return False
# Обновляем статус платежа
callback_payload = {
"merchant_id": merchant_id,
"amount": amount,
"order_id": order_id,
"intid": intid,
"cur_id": cur_id,
}
payment = await freekassa_crud.update_freekassa_payment_status(
db=db,
payment=payment,
status="success",
is_paid=True,
freekassa_order_id=intid,
payment_system_id=cur_id,
callback_payload=callback_payload,
)
# Финализируем платеж (начисляем баланс, создаем транзакцию)
return await self._finalize_freekassa_payment(
db, payment, intid=intid, trigger="webhook"
)
except Exception as e:
logger.exception("Freekassa webhook: ошибка обработки: %s", e)
return False
async def _finalize_freekassa_payment(
self,
db: AsyncSession,
payment: Any,
*,
intid: Optional[str],
trigger: str,
) -> bool:
"""Создаёт транзакцию, начисляет баланс и отправляет уведомления."""
payment_module = import_module("app.services.payment_service")
if payment.transaction_id:
logger.info(
"Freekassa платеж %s уже привязан к транзакции (trigger=%s)",
payment.order_id,
trigger,
)
return True
# Получаем пользователя
user = await payment_module.get_user_by_id(db, payment.user_id)
if not user:
logger.error(
"Пользователь %s не найден для Freekassa платежа %s (trigger=%s)",
payment.user_id,
payment.order_id,
trigger,
)
return False
# Создаем транзакцию
transaction = await payment_module.create_transaction(
db,
user_id=payment.user_id,
type=TransactionType.DEPOSIT,
amount_kopeks=payment.amount_kopeks,
description=f"Пополнение через Freekassa (#{intid or payment.order_id})",
payment_method=PaymentMethod.FREEKASSA,
external_id=str(intid) if intid else payment.order_id,
is_completed=True,
)
# Связываем платеж с транзакцией
freekassa_crud = import_module("app.database.crud.freekassa")
await freekassa_crud.update_freekassa_payment_status(
db=db,
payment=payment,
status=payment.status,
transaction_id=transaction.id,
)
old_balance = user.balance_kopeks
was_first_topup = not user.has_made_first_topup
# Начисляем баланс
user.balance_kopeks += payment.amount_kopeks
user.updated_at = datetime.utcnow()
promo_group = user.get_primary_promo_group()
subscription = getattr(user, "subscription", None)
referrer_info = format_referrer_info(user)
topup_status = "Первое пополнение" if was_first_topup else "Пополнение"
await db.commit()
# Обработка реферального пополнения
try:
from app.services.referral_service import process_referral_topup
await process_referral_topup(
db, user.id, payment.amount_kopeks, getattr(self, "bot", None)
)
except Exception as error:
logger.error(
"Ошибка обработки реферального пополнения Freekassa: %s", error
)
if was_first_topup and not user.has_made_first_topup:
user.has_made_first_topup = True
await db.commit()
await db.refresh(user)
await db.refresh(payment)
# Отправка уведомления админам
if getattr(self, "bot", None):
try:
from app.services.admin_notification_service import (
AdminNotificationService,
)
notification_service = AdminNotificationService(self.bot)
await notification_service.send_balance_topup_notification(
user,
transaction,
old_balance,
topup_status=topup_status,
referrer_info=referrer_info,
subscription=subscription,
promo_group=promo_group,
db=db,
)
except Exception as error:
logger.error(
"Ошибка отправки админ уведомления Freekassa: %s", error
)
# Отправка уведомления пользователю
if getattr(self, "bot", None):
try:
keyboard = await self.build_topup_success_keyboard(user)
display_name = settings.get_freekassa_display_name()
await self.bot.send_message(
user.telegram_id,
(
"✅ <b>Пополнение успешно!</b>\n\n"
f"💰 Сумма: {settings.format_price(payment.amount_kopeks)}\n"
f"💳 Способ: {display_name}\n"
f"🆔 Транзакция: {transaction.id}\n\n"
"Баланс пополнен автоматически!"
),
parse_mode="HTML",
reply_markup=keyboard,
)
except Exception as error:
logger.error(
"Ошибка отправки уведомления пользователю Freekassa: %s", error
)
# Автопокупка подписки
try:
from app.services.user_cart_service import user_cart_service
from aiogram import types
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, "bot", None),
)
except Exception as auto_error:
logger.error(
"Ошибка автоматической покупки подписки для пользователя %s: %s",
user.id,
auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
# Умная автоактивация если автопокупка не сработала
if not auto_purchase_success:
try:
await auto_activate_subscription_after_topup(db, user)
except Exception as auto_activate_error:
logger.error(
"Ошибка умной автоактивации для пользователя %s: %s",
user.id,
auto_activate_error,
exc_info=True,
)
if has_saved_cart and getattr(self, "bot", None):
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
"BALANCE_TOPUP_CART_REMINDER",
"У вас есть незавершенное оформление подписки. Вернуться?",
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t(
"BALANCE_TOPUP_CART_BUTTON",
"🛒 Продолжить оформление",
),
callback_data="return_to_saved_cart",
)
],
[
types.InlineKeyboardButton(
text="🏠 Главное меню",
callback_data="back_to_menu",
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=(
"✅ Баланс пополнен на "
f"{settings.format_price(payment.amount_kopeks)}!\n\n"
f"{cart_message}"
),
reply_markup=keyboard,
)
except Exception as error:
logger.error(
"Ошибка при работе с сохраненной корзиной для пользователя %s: %s",
user.id,
error,
exc_info=True,
)
logger.info(
"✅ Обработан Freekassa платеж %s для пользователя %s (trigger=%s)",
payment.order_id,
payment.user_id,
trigger,
)
return True
async def check_freekassa_payment_status(
self,
db: AsyncSession,
order_id: str,
) -> Optional[Dict[str, Any]]:
"""
Проверяет статус платежа через API.
Args:
db: Сессия БД
order_id: Номер заказа
Returns:
Данные о статусе платежа
"""
try:
status_data = await freekassa_service.get_order_status(order_id)
return status_data
except Exception as e:
logger.exception("Freekassa: ошибка проверки статуса: %s", e)
return None
async def get_freekassa_payment_status(
self,
db: AsyncSession,
local_payment_id: int,
) -> Optional[Dict[str, Any]]:
"""
Проверяет статус платежа Freekassa по локальному ID.
Freekassa не предоставляет API для проверки статуса платежа,
поэтому возвращаем текущее состояние из БД.
Args:
db: Сессия БД
local_payment_id: Внутренний ID платежа
Returns:
Dict с информацией о платеже или None если не найден
"""
freekassa_crud = import_module("app.database.crud.freekassa")
payment = await freekassa_crud.get_freekassa_payment_by_id(db, local_payment_id)
if not payment:
logger.warning("Freekassa payment not found: id=%s", local_payment_id)
return None
# Freekassa не имеет API для проверки статуса,
# информация приходит только через webhook
return {
"payment": payment,
"status": payment.status or "pending",
"is_paid": payment.is_paid,
}
+2
View File
@@ -29,6 +29,7 @@ from app.services.payment import (
WataPaymentMixin,
)
from app.services.payment.cloudpayments import CloudPaymentsPaymentMixin
from app.services.payment.freekassa import FreekassaPaymentMixin
from app.services.yookassa_service import YooKassaService
from app.services.wata_service import WataService
from app.services.cloudpayments_service import CloudPaymentsService
@@ -297,6 +298,7 @@ class PaymentService(
PlategaPaymentMixin,
WataPaymentMixin,
CloudPaymentsPaymentMixin,
FreekassaPaymentMixin,
):
"""Основной интерфейс платежей, делегирующий работу специализированным mixin-ам."""
@@ -19,6 +19,7 @@ from app.database.database import AsyncSessionLocal
from app.database.models import (
CloudPaymentsPayment,
CryptoBotPayment,
FreekassaPayment,
HeleketPayment,
MulenPayPayment,
Pal24Payment,
@@ -66,6 +67,7 @@ SUPPORTED_MANUAL_CHECK_METHODS: frozenset[PaymentMethod] = frozenset(
PaymentMethod.CRYPTOBOT,
PaymentMethod.PLATEGA,
PaymentMethod.CLOUDPAYMENTS,
PaymentMethod.FREEKASSA,
}
)
@@ -79,6 +81,7 @@ SUPPORTED_AUTO_CHECK_METHODS: frozenset[PaymentMethod] = frozenset(
PaymentMethod.CRYPTOBOT,
PaymentMethod.PLATEGA,
PaymentMethod.CLOUDPAYMENTS,
PaymentMethod.FREEKASSA,
}
)
@@ -100,6 +103,8 @@ def method_display_name(method: PaymentMethod) -> str:
return "Heleket"
if method == PaymentMethod.CLOUDPAYMENTS:
return "CloudPayments"
if method == PaymentMethod.FREEKASSA:
return "Freekassa"
if method == PaymentMethod.TELEGRAM_STARS:
return "Telegram Stars"
return method.value
@@ -122,6 +127,8 @@ def _method_is_enabled(method: PaymentMethod) -> bool:
return settings.is_heleket_enabled()
if method == PaymentMethod.CLOUDPAYMENTS:
return settings.is_cloudpayments_enabled()
if method == PaymentMethod.FREEKASSA:
return settings.is_freekassa_enabled()
return False
@@ -362,6 +369,13 @@ def _is_cloudpayments_pending(payment: CloudPaymentsPayment) -> bool:
return status in {"pending", "authorized"}
def _is_freekassa_pending(payment: FreekassaPayment) -> bool:
if payment.is_paid:
return False
status = (payment.status or "").lower()
return status in {"pending", "created", "processing"}
def _parse_cryptobot_amount_kopeks(payment: CryptoBotPayment) -> int:
payload = payment.payload or ""
match = re.search(r"_(\d+)$", payload)
@@ -621,6 +635,31 @@ async def _fetch_cloudpayments_payments(db: AsyncSession, cutoff: datetime) -> L
return records
async def _fetch_freekassa_payments(db: AsyncSession, cutoff: datetime) -> List[PendingPayment]:
stmt = (
select(FreekassaPayment)
.options(selectinload(FreekassaPayment.user))
.where(FreekassaPayment.created_at >= cutoff)
.order_by(desc(FreekassaPayment.created_at))
)
result = await db.execute(stmt)
records: List[PendingPayment] = []
for payment in result.scalars().all():
if not _is_freekassa_pending(payment):
continue
record = _build_record(
PaymentMethod.FREEKASSA,
payment,
identifier=payment.order_id,
amount_kopeks=payment.amount_kopeks,
status=payment.status or "",
is_paid=bool(payment.is_paid),
)
if record:
records.append(record)
return records
async def _fetch_stars_transactions(db: AsyncSession, cutoff: datetime) -> List[PendingPayment]:
stmt = (
select(Transaction)
@@ -666,6 +705,7 @@ async def list_recent_pending_payments(
await _fetch_heleket_payments(db, cutoff),
await _fetch_cryptobot_payments(db, cutoff),
await _fetch_cloudpayments_payments(db, cutoff),
await _fetch_freekassa_payments(db, cutoff),
await _fetch_stars_transactions(db, cutoff),
)
@@ -806,6 +846,20 @@ async def get_payment_record(
is_paid=bool(payment.is_paid),
)
if method == PaymentMethod.FREEKASSA:
payment = await db.get(FreekassaPayment, local_payment_id)
if not payment:
return None
await db.refresh(payment, attribute_names=["user"])
return _build_record(
method,
payment,
identifier=payment.order_id,
amount_kopeks=payment.amount_kopeks,
status=payment.status or "",
is_paid=bool(payment.is_paid),
)
if method == PaymentMethod.TELEGRAM_STARS:
transaction = await db.get(Transaction, local_payment_id)
if not transaction:
@@ -860,6 +914,9 @@ async def run_manual_check(
elif method == PaymentMethod.CLOUDPAYMENTS:
result = await payment_service.get_cloudpayments_payment_status(db, local_payment_id)
payment = result.get("payment") if result else None
elif method == PaymentMethod.FREEKASSA:
result = await payment_service.get_freekassa_payment_status(db, local_payment_id)
payment = result.get("payment") if result else None
else:
logger.warning("Manual check requested for unsupported method %s", method)
return None
+3
View File
@@ -84,6 +84,7 @@ class BotConfigurationService:
"CRYPTOBOT": "🪙 CryptoBot",
"HELEKET": "🪙 Heleket",
"CLOUDPAYMENTS": "💳 CloudPayments",
"FREEKASSA": "💳 Freekassa",
"YOOKASSA": "🟣 YooKassa",
"PLATEGA": "💳 {platega_name}",
"TRIBUTE": "🎁 Tribute",
@@ -140,6 +141,7 @@ class BotConfigurationService:
"CRYPTOBOT": "CryptoBot и криптоплатежи через Telegram.",
"HELEKET": "Heleket: криптоплатежи, ключи мерчанта и вебхуки.",
"CLOUDPAYMENTS": "CloudPayments: оплата банковскими картами, Public ID, API Secret и вебхуки.",
"FREEKASSA": "Freekassa: ID магазина, API ключ, секретные слова и вебхуки.",
"PLATEGA": "{platega_name}: merchant ID, секрет, ссылки возврата и методы оплаты.",
"MULENPAY": "Платежи {mulenpay_name} и параметры магазина.",
"PAL24": "PAL24 / PayPalych подключения и лимиты.",
@@ -313,6 +315,7 @@ class BotConfigurationService:
"CRYPTOBOT_": "CRYPTOBOT",
"HELEKET_": "HELEKET",
"CLOUDPAYMENTS_": "CLOUDPAYMENTS",
"FREEKASSA_": "FREEKASSA",
"PLATEGA_": "PLATEGA",
"MULENPAY_": "MULENPAY",
"PAL24_": "PAL24",