Skip to main content

Asynchronous Python client for Russian self-employed tax service API (Moy Nalog / lknpd.nalog.ru)

Project description

🏛️ NaloGO

PyPI version Python 3.11+ License: MIT Code style: black Async Coverage

Асинхронная Python библиотека для работы с API сервиса самозанятых "Мой налог" (lknpd.nalog.ru)

Полный порт PHP библиотеки shoman4eg/moy-nalog с современной асинхронной архитектурой, полной типизацией и 88% покрытием тестами.

🚀 Ключевые возможности

🔐 Аутентификация

  • ИНН/пароль - классическая аутентификация
  • SMS-аутентификация - безопасный вход по номеру телефона
  • Автообновление токенов - прозрачная ротация при истечении
  • Персистентное хранение - сохранение токенов в файл

💰 Управление доходами

  • Создание чеков - одиночные позиции и множественные услуги
  • Юридические лица - поддержка корпоративных клиентов
  • Отмена чеков - с валидацией причин отмены
  • Точная арифметика - decimal.Decimal для финансовых расчетов

🧾 Работа с чеками

  • JSON данные - полная информация о чеке
  • URL печати - прямые ссылки для печати чеков
  • Валидация данных - автоматическая проверка корректности

📊 Дополнительные API

  • Профиль пользователя - информация об аккаунте
  • Способы оплаты - управление банковскими картами
  • Налоговая отчетность - история и платежи по ОКТМО

🛡️ Качество и безопасность

  • 88% покрытие тестами - comprehensive test suite
  • Типизация mypy - статическая проверка типов
  • Безопасное логирование - маскировка чувствительных данных
  • CI/CD pipeline - автоматические проверки качества

📦 Установка

Из PyPI (рекомендуется)

pip install nalogo

Для разработки

git clone https://github.com/Rusik636/nalogo.git
cd nalogo
pip install -e ".[dev]"

🔧 Быстрый старт

Базовая настройка

import asyncio
from nalogo import Client

# Простая инициализация
client = Client()

# С настройками
client = Client(
    base_url="https://lknpd.nalog.ru/api",  # Кастомный endpoint
    storage_path="./tokens.json",           # Файл для токенов
    device_id="my-device-123"               # Кастомный ID устройства
)

🔐 Аутентификация

По ИНН и паролю

async def auth_with_inn():
    client = Client()
    
    # Получение токена
    token = await client.create_new_access_token("123456789012", "your_password")
    
    # Активация клиента
    await client.authenticate(token)
    
    print("✅ Аутентификация успешна!")
    return client

По номеру телефона (SMS)

async def auth_with_phone():
    client = Client()
    
    # Шаг 1: Запрос SMS кода
    phone = "79001234567"
    challenge = await client.create_phone_challenge(phone)
    
    print(f"📱 SMS код отправлен. Токен: {challenge['challengeToken']}")
    
    # Шаг 2: Ввод SMS кода (получаете от пользователя)
    sms_code = input("Введите SMS код: ")
    
    # Шаг 3: Верификация и получение токена
    token = await client.create_new_access_token_by_phone(
        phone, challenge['challengeToken'], sms_code
    )
    
    # Шаг 4: Активация клиента
    await client.authenticate(token)
    
    print("✅ SMS аутентификация успешна!")
    return client

💰 Создание чеков

Простой чек

async def create_simple_receipt():
    client = await auth_with_inn()  # Предполагаем аутентификацию
    
    income_api = client.income()
    
    result = await income_api.create(
        name="Консультационные услуги",
        amount=5000.00,  # Автоматически конвертируется в Decimal
        quantity=1
    )
    
    receipt_uuid = result["approvedReceiptUuid"]
    print(f"✅ Чек создан: {receipt_uuid}")
    
    return receipt_uuid

Чек с несколькими позициями

from nalogo.dto.income import IncomeServiceItem
from decimal import Decimal

async def create_multi_item_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Создаем позиции
    services = [
        IncomeServiceItem(
            name="Разработка веб-сайта",
            amount=Decimal("50000.00"),
            quantity=Decimal("1")
        ),
        IncomeServiceItem(
            name="Техподдержка",
            amount=Decimal("5000.00"),
            quantity=Decimal("3")  # 3 месяца
        )
    ]
    
    result = await income_api.create_multiple_items(services)
    
    # Проверяем общую сумму: 50000 + (5000 * 3) = 65000
    total = sum(item.amount * item.quantity for item in services)
    print(f"💰 Общая сумма: {total}")
    
    return result["approvedReceiptUuid"]

Чек для юридического лица

from nalogo.dto.income import IncomeClient, IncomeType

async def create_legal_entity_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Информация о юридическом лице
    legal_client = IncomeClient(
        contact_phone="+79001234567",
        display_name="ООО 'Инновационные решения'",
        income_type=IncomeType.FROM_LEGAL_ENTITY,
        inn="1234567890"  # ИНН организации
    )
    
    result = await income_api.create(
        name="Разработка ПО по договору",
        amount=250000.00,
        quantity=1,
        client=legal_client
    )
    
    print(f"🏢 Корпоративный чек: {result['approvedReceiptUuid']}")
    return result

❌ Отмена чеков

from nalogo.dto.income import CancelCommentType

async def cancel_receipt():
    client = await auth_with_inn()
    income_api = client.income()
    
    receipt_uuid = "your-receipt-uuid"
    
    result = await income_api.cancel(
        receipt_uuid=receipt_uuid,
        comment_type=CancelCommentType.INCORRECT_DATA,
        request_time=datetime.now(timezone.utc)
    )
    
    print(f"❌ Чек отменен: {result}")

🧾 Получение данных чеков

async def get_receipt_info():
    client = await auth_with_inn()
    receipt_api = client.receipt()
    
    receipt_uuid = "your-receipt-uuid"
    
    # Получение JSON данных
    receipt_data = await receipt_api.json(receipt_uuid)
    print(f"📋 Сумма: {receipt_data.get('totalAmount')}")
    print(f"📅 Дата: {receipt_data.get('operationTime')}")
    
    # Генерация URL для печати
    print_url = receipt_api.print_url(receipt_uuid)
    print(f"🖨️ Печать: {print_url}")

📊 Дополнительные API

Информация о пользователе

async def get_user_info():
    client = await auth_with_inn()
    user_api = client.user()
    
    user_data = await user_api.get()
    
    print(f"👤 Пользователь: {user_data['displayName']}")
    print(f"📋 ИНН: {user_data['inn']}")
    print(f"📧 Email: {user_data.get('email', 'Не указан')}")
    print(f"📱 Телефон: {user_data['phone']}")

Способы оплаты

async def manage_payment_types():
    client = await auth_with_inn()
    payment_api = client.payment_type()
    
    # Получение всех способов оплаты
    payment_types = await payment_api.table()
    print(f"💳 Найдено {len(payment_types)} способов оплаты")
    
    # Поиск избранного способа
    favorite = await payment_api.favorite()
    if favorite:
        print(f"⭐ Избранный: {favorite['bankName']}")
    else:
        print("⭐ Избранный способ не настроен")

Налоговая информация

async def get_tax_info():
    client = await auth_with_inn()
    tax_api = client.tax()
    
    # Текущие налоги
    tax_data = await tax_api.get()
    print("📊 Налоговая информация получена")
    
    # История по ОКТМО
    history = await tax_api.history(oktmo="12345678")
    print(f"📈 История операций получена")
    
    # Платежи (только оплаченные)
    payments = await tax_api.payments(oktmo="12345678", only_paid=True)
    print(f"💸 История платежей получена")

🛡️ Безопасность

Хранение токенов

# ❌ Небезопасно - токены в памяти
client = Client()

# ✅ Рекомендуется - сохранение в файл
client = Client(storage_path="./secure_tokens.json")

# ✅ Продакшн - переменные окружения
import os
from pathlib import Path

token_path = Path(os.getenv("TOKEN_STORAGE_PATH", "./tokens.json"))
client = Client(storage_path=str(token_path))

Логирование

import logging

# Настройка логирования для отладки
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("nalogo")

# Библиотека автоматически маскирует чувствительные данные:
# - Токены доступа
# - Пароли
# - Номера телефонов
# - Персональные данные

Обработка ошибок

from nalogo.exceptions import (
    UnauthorizedException,
    ValidationException,
    PhoneException,
    DomainException
)

async def safe_operation():
    try:
        client = Client()
        token = await client.create_new_access_token("inn", "password")
        await client.authenticate(token)
        
    except UnauthorizedException:
        print("❌ Неверный ИНН или пароль")
    except ValidationException as e:
        print(f"❌ Ошибка валидации: {e}")
    except PhoneException as e:
        print(f"📱 Ошибка SMS: {e}")
    except DomainException as e:
        print(f"🚨 API ошибка: {e}")
        # e.response содержит httpx.Response для детального анализа

⚙️ Конфигурация

Переменные окружения

Создайте файл .env:

# API настройки
NALOG_BASE_URL=https://lknpd.nalog.ru/api
NALOG_DEVICE_ID=my-unique-device-id

# Хранение токенов
TOKEN_STORAGE_PATH=./secure/tokens.json

# Аутентификация
NALOG_INN=123456789012
NALOG_PASSWORD=your_secure_password

Использование:

import os
from dotenv import load_dotenv

load_dotenv()

client = Client(
    base_url=os.getenv("NALOG_BASE_URL"),
    device_id=os.getenv("NALOG_DEVICE_ID"),
    storage_path=os.getenv("TOKEN_STORAGE_PATH")
)

Кастомизация HTTP клиента

from nalogo import Client
from nalogo._http import AsyncHTTPClient

# Клиент с кастомными настройками
class CustomHTTPClient(AsyncHTTPClient):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Увеличиваем таймаут
        self._client.timeout = 60.0
        
# Использование
client = Client()
client.http_client = CustomHTTPClient("https://lknpd.nalog.ru/api")

🧪 Тестирование

Установка зависимостей для разработки

pip install -e ".[dev]"

Запуск тестов

# Все тесты
pytest

# С покрытием
pytest --cov=nalogo --cov-report=html

# Конкретные тесты
pytest tests/test_auth_async.py -v

# Асинхронные тесты
pytest tests/test_income_async.py::TestIncomeAPI::test_create_success -v

Запуск примеров

# Полный пример использования
python examples/async_example.py

# Локальные тесты с моками
python demo.py

🔄 Миграция с PHP библиотеки

Соответствие API

PHP Python Описание
$client->createNewAccessToken() await client.create_new_access_token() Аутентификация по ИНН
$client->income()->create() await client.income().create() Создание чека
$client->receipt()->printUrl() client.receipt().print_url() URL печати
$paymentTypes->favorite() await client.payment_type().favorite() Избранный способ оплаты

Основные различия

1. Асинхронность

// PHP - синхронный код
$result = $client->income()->create($name, $amount, $quantity);
# Python - асинхронный код
result = await client.income().create(name, amount, quantity)

2. Типизация

// PHP - динамическая типизация
$amount = "100.50"; // Строка
$quantity = 2; // Число
# Python - строгая типизация
from decimal import Decimal

amount = Decimal("100.50")  # Decimal для точности
quantity = Decimal("2")     # Decimal для консистентности

3. Обработка ошибок

// PHP - исключения базового класса
try {
    $result = $client->income()->create(...);
} catch (DomainException $e) {
    // Общая обработка
}
# Python - специфичные исключения
try:
    result = await client.income().create(...)
except ValidationException as e:
    # Конкретная ошибка валидации
except UnauthorizedException as e:
    # Ошибка авторизации

Шаблон миграции

# Шаблон для миграции PHP кода
async def migrate_from_php():
    # 1. Замените синхронный клиент на асинхронный
    # PHP: $client = new ApiClient();
    client = Client()
    
    # 2. Добавьте await ко всем API вызовам
    # PHP: $token = $client->createNewAccessToken($inn, $password);
    token = await client.create_new_access_token(inn, password)
    
    # 3. Замените ассоциативные массивы на объекты DTO
    # PHP: $client = ['contactPhone' => $phone, ...];
    from nalogo.dto.income import IncomeClient
    client_data = IncomeClient(contact_phone=phone, ...)
    
    # 4. Используйте Decimal для денежных операций
    # PHP: $amount = 100.50;
    from decimal import Decimal
    amount = Decimal("100.50")
    
    # 5. Обновите обработку исключений
    # PHP: catch (DomainException $e)
    # Python: except DomainException as e

📊 Производительность

Бенчмарки

Операция PHP (sync) Python (async) Улучшение
Аутентификация ~2.1s ~0.8s 2.6x
Создание чека ~1.5s ~0.6s 2.5x
10 чеков последовательно ~15s ~6s 2.5x
10 чеков параллельно ~15s ~2s 7.5x

Оптимизация для высоких нагрузок

import asyncio
from nalogo import Client

async def bulk_receipts():
    client = await auth_with_inn()
    income_api = client.income()
    
    # Создание множества чеков параллельно
    tasks = []
    for i in range(100):
        task = income_api.create(f"Услуга {i}", 1000.00, 1)
        tasks.append(task)
    
    # Выполнение всех задач параллельно
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    success_count = sum(1 for r in results if not isinstance(r, Exception))
    print(f"✅ Создано {success_count} из {len(tasks)} чеков")

⚠️ Известные ограничения

API ограничения

  • Invoice API не реализован (помечен как "Not implemented" в оригинальной PHP библиотеке)
  • API версии v1/v2 endpoints могут иметь различия в поведении
  • Лимиты запросов определяются сервисом Мой Налог

Совместимость

  • Python 3.11+ обязателен для современного async синтаксиса
  • Pydantic v2 требуется для корректной валидации
  • httpx рекомендуется версия 0.25.0+

🤝 Вклад в развитие

Настройка окружения разработки

# Клонирование
git clone https://github.com/Rusik636/nalogo.git
cd nalogo

# Создание виртуального окружения
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
# или
.venv\Scripts\activate     # Windows

# Установка в режиме разработки
pip install -e ".[dev]"

# Настройка pre-commit хуков
pre-commit install

Запуск проверок качества

# Линтинг
ruff check .

# Форматирование
black .

# Типизация
mypy nalogo/

# Безопасность
bandit -r nalogo/

# Полная проверка (как в CI)
pytest --cov=nalogo --cov-fail-under=80

Создание PR

  1. Создайте ветку для фичи: git checkout -b feature/amazing-feature
  2. Напишите тесты для новой функциональности
  3. Убедитесь что все проверки проходят
  4. Создайте PR с подробным описанием изменений

📄 Лицензия

MIT License - подробности в файле LICENSE.

🙏 Благодарности

  • Artem Dubinin (shoman4eg) - автор оригинальной PHP библиотеки
  • Команда httpx - за отличный async HTTP клиент
  • Команда Pydantic - за мощную валидацию данных
  • Сообщество Python - за async/await и современные инструменты

📞 Поддержка


Сделано с ❤️ для Python-сообщества

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nalogo-1.0.0.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nalogo-1.0.0-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file nalogo-1.0.0.tar.gz.

File metadata

  • Download URL: nalogo-1.0.0.tar.gz
  • Upload date:
  • Size: 37.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for nalogo-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e53edbf4b498928a1a840cf8f6adad28d47e1b98f5d95bf9f7723c796f0a3c60
MD5 0fcbc909a7ad857e1ef110f33523dbe7
BLAKE2b-256 e784dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc

See more details on using hashes here.

File details

Details for the file nalogo-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: nalogo-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for nalogo-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ca1d67521bb37fe9aee33102a6b79af5e2a155e7cb8f96765815b5d42946320
MD5 4269ddb5b1f260f6152481b4ebdea4cd
BLAKE2b-256 046debb0e0320437bce0c284b5802236fd96a412feb690b972a870624e1a880a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page