mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-01 15:52:30 +00:00
Кнопка конкурсов + прототип налоговой. не трогайте налоговую еще!!!
This commit is contained in:
@@ -175,6 +175,7 @@ DEVICES_SELECTION_ENABLED=true
|
||||
DEVICES_SELECTION_DISABLED_AMOUNT=0
|
||||
# ===== КОНКУРСНАЯ СИСТЕМА =====
|
||||
CONTESTS_ENABLED=false
|
||||
CONTESTS_BUTTON_VISIBLE=false
|
||||
|
||||
# ===== РЕФЕРАЛЬНАЯ СИСТЕМА =====
|
||||
REFERRAL_PROGRAM_ENABLED=true
|
||||
@@ -278,6 +279,14 @@ PAYMENT_VERIFICATION_AUTO_CHECK_ENABLED=false
|
||||
# Интервал (в минутах) между автоматическими проверками пополнений
|
||||
PAYMENT_VERIFICATION_AUTO_CHECK_INTERVAL_MINUTES=10
|
||||
|
||||
# ===== НАЛОГОВАЯ СЛУЖБА (NaloGO) =====
|
||||
# Автоматическая отправка чеков в налоговую при пополнении баланса
|
||||
NALOGO_ENABLED=false
|
||||
NALOGO_INN= # ИНН самозанятого
|
||||
NALOGO_PASSWORD= # Пароль от личного кабинета налоговой
|
||||
NALOGO_DEVICE_ID= # Опционально: ID устройства для авторизации
|
||||
NALOGO_STORAGE_PATH=./nalogo_tokens.json # Путь к файлу с токенами
|
||||
|
||||
# ===== НАСТРОЙКИ ОПИСАНИЙ ПЛАТЕЖЕЙ =====
|
||||
# Эти настройки позволяют изменить описания платежей,
|
||||
# чтобы избежать блокировок платежных систем
|
||||
|
||||
@@ -158,6 +158,7 @@ class Settings(BaseSettings):
|
||||
|
||||
# Конкурсы (глобальный флаг, будет расширяться под разные типы)
|
||||
CONTESTS_ENABLED: bool = False
|
||||
CONTESTS_BUTTON_VISIBLE: bool = False
|
||||
# Для обратной совместимости со старыми конфигами
|
||||
REFERRAL_CONTESTS_ENABLED: bool = False
|
||||
|
||||
@@ -220,6 +221,12 @@ class Settings(BaseSettings):
|
||||
PAYMENT_VERIFICATION_AUTO_CHECK_ENABLED: bool = False
|
||||
PAYMENT_VERIFICATION_AUTO_CHECK_INTERVAL_MINUTES: int = 10
|
||||
|
||||
NALOGO_ENABLED: bool = False
|
||||
NALOGO_INN: Optional[str] = None
|
||||
NALOGO_PASSWORD: Optional[str] = None
|
||||
NALOGO_DEVICE_ID: Optional[str] = None
|
||||
NALOGO_STORAGE_PATH: str = "./nalogo_tokens.json"
|
||||
|
||||
AUTO_PURCHASE_AFTER_TOPUP_ENABLED: bool = False
|
||||
|
||||
# Настройки простой покупки
|
||||
@@ -993,6 +1000,11 @@ class Settings(BaseSettings):
|
||||
self.YOOKASSA_SHOP_ID is not None and
|
||||
self.YOOKASSA_SECRET_KEY is not None)
|
||||
|
||||
def is_nalogo_enabled(self) -> bool:
|
||||
return (self.NALOGO_ENABLED and
|
||||
self.NALOGO_INN is not None and
|
||||
self.NALOGO_PASSWORD is not None)
|
||||
|
||||
def is_support_topup_enabled(self) -> bool:
|
||||
return bool(self.SUPPORT_TOPUP_ENABLED)
|
||||
|
||||
|
||||
@@ -439,9 +439,10 @@ def get_main_menu_keyboard(
|
||||
)
|
||||
|
||||
# Добавляем кнопку конкурсов
|
||||
paired_buttons.append(
|
||||
InlineKeyboardButton(text=texts.t("CONTESTS_BUTTON", "🎲 Конкурсы"), callback_data="contests_menu")
|
||||
)
|
||||
if settings.CONTESTS_BUTTON_VISIBLE:
|
||||
paired_buttons.append(
|
||||
InlineKeyboardButton(text=texts.t("CONTESTS_BUTTON", "🎲 Конкурсы"), callback_data="contests_menu")
|
||||
)
|
||||
|
||||
try:
|
||||
from app.services.support_settings_service import SupportSettingsService
|
||||
|
||||
118
app/services/nalogo_service.py
Normal file
118
app/services/nalogo_service.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from decimal import Decimal
|
||||
|
||||
from nalogo import Client
|
||||
from nalogo.dto.income import IncomeClient, IncomeType
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NaloGoService:
|
||||
"""Сервис для работы с API NaloGO (налоговая служба самозанятых)."""
|
||||
|
||||
def __init__(self,
|
||||
inn: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
device_id: Optional[str] = None,
|
||||
storage_path: Optional[str] = None):
|
||||
|
||||
inn = inn or getattr(settings, 'NALOGO_INN', None)
|
||||
password = password or getattr(settings, 'NALOGO_PASSWORD', None)
|
||||
device_id = device_id or getattr(settings, 'NALOGO_DEVICE_ID', None)
|
||||
storage_path = storage_path or getattr(settings, 'NALOGO_STORAGE_PATH', './nalogo_tokens.json')
|
||||
|
||||
self.configured = False
|
||||
|
||||
if not inn or not password:
|
||||
logger.warning(
|
||||
"NaloGO INN или PASSWORD не настроены в settings. "
|
||||
"Функционал чеков будет ОТКЛЮЧЕН.")
|
||||
else:
|
||||
try:
|
||||
self.client = Client(
|
||||
base_url="https://lknpd.nalog.ru/api",
|
||||
storage_path=storage_path,
|
||||
device_id=device_id or "bot-device-123"
|
||||
)
|
||||
self.inn = inn
|
||||
self.password = password
|
||||
self.configured = True
|
||||
logger.info(f"NaloGO клиент инициализирован для ИНН: {inn[:5]}...")
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
"Ошибка инициализации NaloGO клиента: %s",
|
||||
error,
|
||||
exc_info=True,
|
||||
)
|
||||
self.configured = False
|
||||
|
||||
async def authenticate(self) -> bool:
|
||||
"""Аутентификация в сервисе NaloGO."""
|
||||
if not self.configured:
|
||||
return False
|
||||
|
||||
try:
|
||||
token = await self.client.create_new_access_token(self.inn, self.password)
|
||||
await self.client.authenticate(token)
|
||||
logger.info("Успешная аутентификация в NaloGO")
|
||||
return True
|
||||
except Exception as error:
|
||||
logger.error("Ошибка аутентификации в NaloGO: %s", error, exc_info=True)
|
||||
return False
|
||||
|
||||
async def create_receipt(self, name: str, amount: float, quantity: int = 1, client_info: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||
"""Создание чека о доходе.
|
||||
|
||||
Args:
|
||||
name: Название услуги
|
||||
amount: Сумма в рублях
|
||||
quantity: Количество
|
||||
client_info: Информация о клиенте (опционально)
|
||||
|
||||
Returns:
|
||||
UUID чека или None при ошибке
|
||||
"""
|
||||
if not self.configured:
|
||||
logger.warning("NaloGO не настроен, чек не создан")
|
||||
return None
|
||||
|
||||
try:
|
||||
# Аутентифицируемся, если нужно
|
||||
if not hasattr(self.client, '_access_token') or not self.client._access_token:
|
||||
auth_success = await self.authenticate()
|
||||
if not auth_success:
|
||||
return None
|
||||
|
||||
income_api = self.client.income()
|
||||
|
||||
# Создаем клиента, если передана информация
|
||||
income_client = None
|
||||
if client_info:
|
||||
income_client = IncomeClient(
|
||||
contact_phone=client_info.get("phone"),
|
||||
display_name=client_info.get("name"),
|
||||
income_type=client_info.get("income_type", IncomeType.FROM_INDIVIDUAL),
|
||||
inn=client_info.get("inn")
|
||||
)
|
||||
|
||||
result = await income_api.create(
|
||||
name=name,
|
||||
amount=Decimal(str(amount)),
|
||||
quantity=quantity,
|
||||
client=income_client
|
||||
)
|
||||
|
||||
receipt_uuid = result.get("approvedReceiptUuid")
|
||||
if receipt_uuid:
|
||||
logger.info(f"Чек создан успешно: {receipt_uuid} на сумму {amount}₽")
|
||||
return receipt_uuid
|
||||
else:
|
||||
logger.error(f"Ошибка создания чека: {result}")
|
||||
return None
|
||||
|
||||
except Exception as error:
|
||||
logger.error("Ошибка создания чека в NaloGO: %s", error, exc_info=True)
|
||||
return None
|
||||
@@ -952,6 +952,10 @@ class YooKassaPaymentMixin:
|
||||
payment.amount_kopeks / 100,
|
||||
)
|
||||
|
||||
# Создаем чек через NaloGO для пополнения баланса
|
||||
if not is_simple_subscription and hasattr(self, "nalogo_service") and self.nalogo_service:
|
||||
await self._create_nalogo_receipt(payment)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as error:
|
||||
@@ -1002,6 +1006,38 @@ class YooKassaPaymentMixin:
|
||||
|
||||
return updated_metadata
|
||||
|
||||
async def _create_nalogo_receipt(
|
||||
self,
|
||||
payment: "YooKassaPayment",
|
||||
) -> None:
|
||||
"""Создание чека через NaloGO для успешного платежа."""
|
||||
if not hasattr(self, "nalogo_service") or not self.nalogo_service:
|
||||
logger.debug("NaloGO сервис не инициализирован, чек не создан")
|
||||
return
|
||||
|
||||
try:
|
||||
amount_rubles = payment.amount_kopeks / 100
|
||||
receipt_name = "Интернет-сервис - Пополнение баланса"
|
||||
|
||||
receipt_uuid = await self.nalogo_service.create_receipt(
|
||||
name=receipt_name,
|
||||
amount=amount_rubles,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
if receipt_uuid:
|
||||
logger.info(f"Чек NaloGO создан для платежа {payment.yookassa_payment_id}: {receipt_uuid}")
|
||||
else:
|
||||
logger.warning(f"Не удалось создать чек NaloGO для платежа {payment.yookassa_payment_id}")
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
"Ошибка создания чека NaloGO для платежа %s: %s",
|
||||
payment.yookassa_payment_id,
|
||||
error,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
async def process_yookassa_webhook(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
|
||||
@@ -30,6 +30,7 @@ from app.services.payment import (
|
||||
)
|
||||
from app.services.yookassa_service import YooKassaService
|
||||
from app.services.wata_service import WataService
|
||||
from app.services.nalogo_service import NaloGoService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -300,6 +301,7 @@ class PaymentService(
|
||||
PlategaService() if settings.is_platega_enabled() else None
|
||||
)
|
||||
self.wata_service = WataService() if settings.is_wata_enabled() else None
|
||||
self.nalogo_service = NaloGoService() if settings.is_nalogo_enabled() else None
|
||||
|
||||
mulenpay_name = settings.get_mulenpay_display_name()
|
||||
logger.debug(
|
||||
|
||||
@@ -19,6 +19,9 @@ python-multipart==0.0.9
|
||||
# YooKassa SDK
|
||||
yookassa==3.7.0
|
||||
|
||||
# NaloGO для чеков в налоговую
|
||||
nalogo
|
||||
|
||||
# Логирование и мониторинг
|
||||
structlog==23.2.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user