Enhance Freekassa payment handling and improve Docker Compose configuration

This commit is contained in:
evansvl
2026-01-11 05:45:09 +03:00
parent edb335ee52
commit b5234f3265
5 changed files with 143 additions and 52 deletions

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import json import json
@@ -9,13 +11,15 @@ from ipaddress import (
ip_address, ip_address,
ip_network, ip_network,
) )
from typing import Iterable, Optional, Dict, Any, List, Union, Tuple from typing import Iterable, Optional, Dict, Any, List, Union, Tuple, TYPE_CHECKING
from aiohttp import web from aiohttp import web
from app.config import settings from app.config import settings
from app.services.payment_service import PaymentService
from app.database.database import get_db from app.database.database import get_db
if TYPE_CHECKING:
from app.services.payment_service import PaymentService
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -135,6 +135,16 @@ def _status_info(
return "", texts.t("ADMIN_PAYMENT_STATUS_PAID", "✅ Paid") return "", texts.t("ADMIN_PAYMENT_STATUS_PAID", "✅ Paid")
return "", texts.t("ADMIN_PAYMENT_STATUS_PENDING", "⏳ Pending") return "", texts.t("ADMIN_PAYMENT_STATUS_PENDING", "⏳ Pending")
if record.method == PaymentMethod.FREEKASSA:
mapping = {
"pending": ("", texts.t("ADMIN_PAYMENT_STATUS_PENDING", "⏳ Pending")),
"success": ("", texts.t("ADMIN_PAYMENT_STATUS_PAID", "✅ Paid")),
"paid": ("", texts.t("ADMIN_PAYMENT_STATUS_PAID", "✅ Paid")),
"canceled": ("", texts.t("ADMIN_PAYMENT_STATUS_CANCELED", "❌ Cancelled")),
"error": ("", texts.t("ADMIN_PAYMENT_STATUS_FAILED", "❌ Failed")),
}
return mapping.get(status, ("", texts.t("ADMIN_PAYMENT_STATUS_UNKNOWN", "❓ Unknown")))
return "", texts.t("ADMIN_PAYMENT_STATUS_UNKNOWN", "❓ Unknown") return "", texts.t("ADMIN_PAYMENT_STATUS_UNKNOWN", "❓ Unknown")
@@ -158,6 +168,8 @@ def _is_checkable(record: PendingPayment) -> bool:
return status in {"pending", "waiting_for_capture"} return status in {"pending", "waiting_for_capture"}
if record.method == PaymentMethod.CRYPTOBOT: if record.method == PaymentMethod.CRYPTOBOT:
return status in {"active"} return status in {"active"}
if record.method == PaymentMethod.FREEKASSA:
return status in {"pending", ""}
return False return False

View File

@@ -505,17 +505,7 @@ class FreekassaPaymentMixin:
local_payment_id: int, local_payment_id: int,
) -> Optional[Dict[str, Any]]: ) -> Optional[Dict[str, Any]]:
""" """
Проверяет статус платежа Freekassa по локальному ID. Проверяет статус платежа Freekassa по локальному ID через API.
Freekassa не предоставляет API для проверки статуса платежа,
поэтому возвращаем текущее состояние из БД.
Args:
db: Сессия БД
local_payment_id: Внутренний ID платежа
Returns:
Dict с информацией о платеже или None если не найден
""" """
freekassa_crud = import_module("app.database.crud.freekassa") freekassa_crud = import_module("app.database.crud.freekassa")
@@ -524,8 +514,73 @@ class FreekassaPaymentMixin:
logger.warning("Freekassa payment not found: id=%s", local_payment_id) logger.warning("Freekassa payment not found: id=%s", local_payment_id)
return None return None
# Freekassa не имеет API для проверки статуса, if payment.is_paid:
# информация приходит только через webhook return {
"payment": payment,
"status": "success",
"is_paid": True,
}
if not settings.FREEKASSA_API_KEY:
return {
"payment": payment,
"status": payment.status or "pending",
"is_paid": payment.is_paid,
}
try:
# Запрашиваем статус заказа в Freekassa
response = await freekassa_service.get_order_status(payment.order_id)
# Freekassa возвращает список заказов
orders = response.get("orders", [])
target_order = None
# Ищем наш заказ в списке
for order in orders:
# В ответе API поле называется merchant_order_id, а не paymentId
# Поддерживаем оба варианта на всякий случай
order_key = str(order.get("merchant_order_id") or order.get("paymentId"))
if order_key == str(payment.order_id):
target_order = order
break
if target_order:
# Статус 1 = Оплачен
fk_status = int(target_order.get("status", 0))
if fk_status == 1:
logger.info("Freekassa payment %s confirmed via API", payment.order_id)
callback_payload = {
"check_source": "api",
"fk_order_data": target_order,
}
# ID заказа на стороне FK (fk_order_id или id)
fk_intid = str(target_order.get("fk_order_id") or target_order.get("id"))
# Обновляем статус
payment = await freekassa_crud.update_freekassa_payment_status(
db=db,
payment=payment,
status="success",
is_paid=True,
freekassa_order_id=fk_intid,
payment_system_id=int(target_order.get("curID")) if target_order.get("curID") else None,
callback_payload=callback_payload,
)
# Финализируем
await self._finalize_freekassa_payment(
db,
payment,
intid=fk_intid,
trigger="api_check",
)
except Exception as e:
logger.error("Error checking Freekassa payment status: %s", e)
return { return {
"payment": payment, "payment": payment,
"status": payment.status or "pending", "status": payment.status or "pending",

View File

@@ -7,14 +7,18 @@ services:
POSTGRES_DB: ${POSTGRES_DB:-remnawave_bot} POSTGRES_DB: ${POSTGRES_DB:-remnawave_bot}
POSTGRES_USER: ${POSTGRES_USER:-remnawave_user} POSTGRES_USER: ${POSTGRES_USER:-remnawave_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password_123} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password_123}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=C'
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
- bot_network - bot_network
- remnawave-network # Подключаем к сети панели - remnawave-network # Подключаем к сети панели
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-remnawave_user} -d ${POSTGRES_DB:-remnawave_bot}"] test:
[
'CMD-SHELL',
'pg_isready -U ${POSTGRES_USER:-remnawave_user} -d ${POSTGRES_DB:-remnawave_bot}',
]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -29,9 +33,9 @@ services:
- redis_data:/data - redis_data:/data
networks: networks:
- bot_network - bot_network
- remnawave-network # Подключаем к сети панели - remnawave-network # Подключаем к сети панели
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ['CMD', 'redis-cli', 'ping']
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -48,16 +52,16 @@ services:
env_file: env_file:
- .env - .env
environment: environment:
DOCKER_ENV: "true" DOCKER_ENV: 'true'
DATABASE_MODE: "auto" DATABASE_MODE: 'auto'
POSTGRES_HOST: "postgres" POSTGRES_HOST: 'postgres'
POSTGRES_PORT: "5432" POSTGRES_PORT: '5432'
POSTGRES_DB: "${POSTGRES_DB:-remnawave_bot}" POSTGRES_DB: '${POSTGRES_DB:-remnawave_bot}'
POSTGRES_USER: "${POSTGRES_USER:-remnawave_user}" POSTGRES_USER: '${POSTGRES_USER:-remnawave_user}'
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-secure_password_123}" POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-secure_password_123}'
REDIS_URL: "redis://redis:6379/0" REDIS_URL: 'redis://redis:6379/0'
TZ: "Europe/Moscow" TZ: 'Europe/Moscow'
LOCALES_PATH: "${LOCALES_PATH:-/app/locales}" LOCALES_PATH: '${LOCALES_PATH:-/app/locales}'
volumes: volumes:
- ./logs:/app/logs:rw - ./logs:/app/logs:rw
- ./data:/app/data:rw - ./data:/app/data:rw
@@ -66,12 +70,16 @@ services:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- ./vpn_logo.png:/app/vpn_logo.png:ro - ./vpn_logo.png:/app/vpn_logo.png:ro
ports: ports:
- "${WEB_API_PORT:-8080}:8080" - '${WEB_API_PORT:-8080}:8080'
networks: networks:
- bot_network - bot_network
- remnawave-network # Подключаем к сети панели - remnawave-network # Подключаем к сети панели
healthcheck: healthcheck:
test: ["CMD-SHELL", "python -c \"import requests, os; requests.get('http://localhost:8080/health', headers={'X-API-Key': os.environ.get('WEB_API_DEFAULT_TOKEN')}, timeout=5) or exit(1)\""] test:
[
'CMD-SHELL',
'python -c "import requests, os; requests.get(''http://localhost:8080/health'', headers={''X-API-Key'': os.environ.get(''WEB_API_DEFAULT_TOKEN'')}, timeout=5) or exit(1)"',
]
interval: 60s interval: 60s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -90,7 +98,9 @@ networks:
config: config:
- subnet: 172.20.0.0/16 - subnet: 172.20.0.0/16
gateway: 172.20.0.1 gateway: 172.20.0.1
driver_opts:
com.docker.network.driver.mtu: 1350
remnawave-network: remnawave-network:
name: remnawave-network name: remnawave-network
external: true # Используем существующую сеть панели external: true # Используем существующую сеть панели

View File

@@ -7,13 +7,17 @@ services:
POSTGRES_DB: ${POSTGRES_DB:-remnawave_bot} POSTGRES_DB: ${POSTGRES_DB:-remnawave_bot}
POSTGRES_USER: ${POSTGRES_USER:-remnawave_user} POSTGRES_USER: ${POSTGRES_USER:-remnawave_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password_123} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secure_password_123}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" POSTGRES_INITDB_ARGS: '--encoding=UTF8 --locale=C'
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
- bot_network - bot_network
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-remnawave_user} -d ${POSTGRES_DB:-remnawave_bot}"] test:
[
'CMD-SHELL',
'pg_isready -U ${POSTGRES_USER:-remnawave_user} -d ${POSTGRES_DB:-remnawave_bot}',
]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -29,7 +33,7 @@ services:
networks: networks:
- bot_network - bot_network
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ['CMD', 'redis-cli', 'ping']
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -46,18 +50,18 @@ services:
env_file: env_file:
- .env - .env
environment: environment:
DOCKER_ENV: "true" DOCKER_ENV: 'true'
DATABASE_MODE: "auto" DATABASE_MODE: 'auto'
POSTGRES_HOST: "postgres" POSTGRES_HOST: 'postgres'
POSTGRES_PORT: "5432" POSTGRES_PORT: '5432'
POSTGRES_DB: "${POSTGRES_DB:-remnawave_bot}" POSTGRES_DB: '${POSTGRES_DB:-remnawave_bot}'
POSTGRES_USER: "${POSTGRES_USER:-remnawave_user}" POSTGRES_USER: '${POSTGRES_USER:-remnawave_user}'
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-secure_password_123}" POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-secure_password_123}'
REDIS_URL: "redis://redis:6379/0" REDIS_URL: 'redis://redis:6379/0'
TZ: "Europe/Moscow" TZ: 'Europe/Moscow'
LOCALES_PATH: "${LOCALES_PATH:-/app/locales}" LOCALES_PATH: '${LOCALES_PATH:-/app/locales}'
volumes: volumes:
# Логи # Логи
- ./logs:/app/logs:rw - ./logs:/app/logs:rw
@@ -72,11 +76,15 @@ services:
# Логотип для сообщений # Логотип для сообщений
- ./vpn_logo.png:/app/vpn_logo.png:ro - ./vpn_logo.png:/app/vpn_logo.png:ro
ports: ports:
- "${WEB_API_PORT:-8080}:8080" - '${WEB_API_PORT:-8080}:8080'
networks: networks:
- bot_network - bot_network
healthcheck: healthcheck:
test: ["CMD-SHELL", "python -c \"import requests, os; requests.get('http://localhost:8080/health', headers={'X-API-Key': os.environ.get('WEB_API_DEFAULT_TOKEN')}, timeout=5) or exit(1)\""] test:
[
'CMD-SHELL',
'python -c "import requests, os; requests.get(''http://localhost:8080/health'', headers={''X-API-Key'': os.environ.get(''WEB_API_DEFAULT_TOKEN'')}, timeout=5) or exit(1)"',
]
interval: 60s interval: 60s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -95,3 +103,5 @@ networks:
config: config:
- subnet: 172.20.0.0/16 - subnet: 172.20.0.0/16
gateway: 172.20.0.1 gateway: 172.20.0.1
driver_opts:
com.docker.network.driver.mtu: 1350