Пишем клиент для нового API nalog.ru

Время чтения текста – 6 минут

Ранее в блоге мы рассказывали, как благодаря открытому API можно собирать данные от ФНС по нашим чекам из магазинов, обращаясь к приложению налоговой. С прошлой недели способ стал нерабочим: ФНС обновили методы приложения. Сегодня напишем свой клиент для nalog.ru, который проходит авторизацию и отправляет чеки на проверку.

Авторизация

Прежде чем начать пользоваться приложением, наш профиль необходимо авторизовать. В отличии от предыдущей версии, текущая требует прохождение капчи для авторизации по мобильному телефону — такой способ нам не подходит. Проще всего будет входить в профиль по ИНН и паролю от личного кабинета налогоплательщика. Для хранения этих данных создадим файл credentials.env. Переменную CLIENT_SECRET зададим согласно коду: она отвечает за авторизацию приложения. А ИНН и пароль подставляем свои.

INN = 
PASSWORD = 
CLIENT_SECRET=IyvrAbKt9h/8p6a7QPh8gpkXYQ4=

Теперь создадим файл nalog_python.py, в котором будет описан клиент. Библиотека dotenv используется для загрузки нашего логина и пароля из .env файла.

import os
import json
import requests
from dotenv import load_dotenv

Опишем класс NalogRuPython, и начнём с глобальных переменных класса. Здесь перечисляем headers, необходимые для отправки запроса.

class NalogRuPython:
    HOST = 'irkkt-mobile.nalog.ru:8888'
    DEVICE_OS = 'iOS'
    CLIENT_VERSION = '2.9.0'
    DEVICE_ID = '7C82010F-16CC-446B-8F66-FC4080C66521'
    ACCEPT = '*/*'
    USER_AGENT = 'billchecker/2.9.0 (iPhone; iOS 13.6; Scale/2.00)'
    ACCEPT_LANGUAGE = 'ru-RU;q=1, en-US;q=0.9'

В конструкторе класса прочитаем данные из нашего окружения методом load_dotenv и вызовем метод set_session_id для получения __session_id, который сейчас опишем. Идентификатор сессии необходим для отправки прочих запросов к серверу налоговой, поэтому его получаем первым делом.

def __init__(self):
        load_dotenv()
        self.__session_id = None
        self.set_session_id()

Метод set_session_id проводит авторизацию пользователя по ИНН и паролю от личного кабинета налогоплательщика и ничего не возвращает, только задаёт значение переменной __session_id. Отправляем по указанному в глобальных переменных HOST запрос с нашими данными от аккаунта и получаем идентификатор сессии в ответе.

def set_session_id(self) -> None:
        if os.getenv('CLIENT_SECRET') is None:
            raise ValueError('OS environments not content "CLIENT_SECRET"')
        if os.getenv('INN') is None:
            raise ValueError('OS environments not content "INN"')
        if os.getenv('PASSWORD') is None:
            raise ValueError('OS environments not content "PASSWORD"')

        url = f'https://{self.HOST}/v2/mobile/users/lkfl/auth'
        payload = {
            'inn': os.getenv('INN'),
            'client_secret': os.getenv('CLIENT_SECRET'),
            'password': os.getenv('PASSWORD')
        }
        headers = {
            'Host': self.HOST,
            'Accept': self.ACCEPT,
            'Device-OS': self.DEVICE_OS,
            'Device-Id': self.DEVICE_ID,
            'clientVersion': self.CLIENT_VERSION,
            'Accept-Language': self.ACCEPT_LANGUAGE,
            'User-Agent': self.USER_AGENT,
        }

        resp = requests.post(url, json=payload, headers=headers)
        self.__session_id = resp.json()['sessionId']

Получение идентификатора чека

Следующий шаг — получение ticket_id. Прежде чем отправить сам чек, необходимо получить его идентификатор для проверки. Опишем метод _get_ticket_id, который принимает строку с расшифрованным QR-кодом чека, отправляет соответствующий запрос на сервер и возвращает идентификатор для этой строки. В headers помимо указания глобальных переменных появился также __session_id, который требует метод /v2/ticket.

def _get_ticket_id(self, qr: str) -> str:
        url = f'https://{self.HOST}/v2/ticket'
        payload = {'qr': qr}
        headers = {
            'Host': self.HOST,
            'Accept': self.ACCEPT,
            'Device-OS': self.DEVICE_OS,
            'Device-Id': self.DEVICE_ID,
            'clientVersion': self.CLIENT_VERSION,
            'Accept-Language': self.ACCEPT_LANGUAGE,
            'sessionId': self.__session_id,
            'User-Agent': self.USER_AGENT,
        }
        resp = requests.post(url, json=payload, headers=headers)
        return resp.json()["id"]

Расшифровка чека

Последний шаг — проверка чека. Формируем по ticket_id запрос к серверу и получаем подробную расшифровку чека в ответе. На этом клиент полностью описан и готов к работе.

def get_ticket(self, qr: str) -> dict:
        ticket_id = self._get_ticket_id(qr)
        url = f'https://{self.HOST}/v2/tickets/{ticket_id}'
        headers = {
            'Host': self.HOST,
            'sessionId': self.__session_id,
            'Device-OS': self.DEVICE_OS,
            'clientVersion': self.CLIENT_VERSION,
            'Device-Id': self.DEVICE_ID,
            'Accept': self.ACCEPT,
            'User-Agent': self.USER_AGENT,
            'Accept-Language': self.ACCEPT_LANGUAGE,
        }
        resp = requests.get(url, headers=headers)
        return resp.json()

Наконец, для удобства опишем пример работы клиента. Создадим экземпляр класса NalogRuPython, зададим для примера строку QR-кода и получим расшифрованный ticket, который затем напечатаем на экране.

if __name__ == '__main__':
    client = NalogRuPython()
    qr_code = "t=20200727T1117&s=4850.00&fn=9287440300634471&i=13571&fp=3730902192&n=1"
    ticket = client.get_ticket(qr_code)
    print(json.dumps(ticket, indent=4))

Клиент можно использовать и в своих скриптах: для этого нужно импортировать класс, создать экземпляр и, как и в примере, вызвать метод get_ticket.

from nalog_python import NalogRuPython

client = NalogRuPython()
qr_code = "t=20200727T1117&s=4850.00&fn=9287440300634471&i=13571&fp=3730902192&n=1"
ticket = client.get_ticket(qr_code)

Полный код проекта на GitHub

Поделиться
Отправить
 1376   1 мес   Data Analytics   nalog.ru   python
18 комментариев
Просто Вася 1 мес

Привет!
В коде ещё требуется CLIENT_SECRET
Где его взять?

Николай Валиотти 1 мес

А пост читали? :) У нас версия с авторизацией по личным данным кабинета в nalog.ru.

Юрий Майоров 1 мес

Добрый день!
Подскажите, пожалуйста, что я делаю не так.
Может я не совсем понял, ваш код, так как не владею Python.
Но я отправляю такой запрос (см. cUrl ниже) и получаю ошибку 400 — Bad request. Если не передать Device-OS или Device-Id то в описании еще указывается, что этот заголовок не передан, но для моего запроса никакого описания не выдает — наверное я неправильно составил запрос

curl -i -X POST \
-H «Device-OS:iOS» \
-H «Device-Id:7C82010F-16CC-446B-8F66-FC4080C66521» \
-H «Accept:*/*» \
-H «clientVersion:2.9.0» \
-H «Accept-Language:ru-RU;q=1, en-US;q=0.9» \
-H «User-Agent:billchecker/2.9.0 (iPhone; iOS 13.6; Scale/2.00)» \
-d \
’{
«inn»:«Мой ИНН»,
«password»:«Мой пароль»
}’ \
https://irkkt-mobile.nalog.ru:8888/v2/mobile/users/lkfl/auth'

Николай Валиотти 1 мес

Попробуйте таким образом:

curl -H 'Host: irkkt-mobile.nalog.ru:8888' \
-H 'Accept: */*' \
-H 'Device-OS: iOS' \
-H 'Device-Id: 7C82010F-16CC-446B-8F66-FC4080C66521' \
-H 'clientVersion: 2.9.0' -H 'Accept-Language: ru-RU;q=1, en-US;q=0.9' \
-H 'User-Agent: billchecker/2.9.0 (iPhone; iOS 13.6; Scale/2.00)' \
-H 'Content-Type: application/json' \
--data-binary '{"inn":"Мой ИНН","client_secret":"IyvrAbKt9h\/8p6a7QPh8gpkXYQ4=","password":"Мой пароль"}' \
--compressed 'https://irkkt-mobile.nalog.ru:8888/v2/mobile/users/lkfl/auth'
Просто Вася 1 мес

Николай, а вы код на который даёте ссылку читали? :)
Давайте я вам помогу, вот с этой строки: https://github.com/valiotti/get-receipts/blob/master/nalog_python.py#L27

Николай Валиотти 1 мес

Конечно, читал.

Прошу прощения, в теле заметки забыл указать CLIENT_SECRET, исправлено :)

1 1 мес

ТС все правильно говорит, но раз мы хотим авторизоваться по телефону, там разрабы ввели рекапчу, что в принципе не проблема с использованием сервиса антигейт например.
Они там развернули подпольный бизнес на фоне госки
http://www.gkr.su/ вот разрабы, вот их домены рабочие
http://disconto.me/ http://studiotg.ru/

вообщем люди не хотят чтобы кто то бесплатно юзал по праву очевидно бесплатный сервис который должен принадлежать народу, не удивлен, подмазались разработали для гос сферы софт, далее сели сделали свой «платный» сервис

короче кому надо дамп, где понятно как авторизоваться с помощью телефона(смс), с участием рекапчи, пишите в тг nickholden2
(сам скрипт пока не делал, времени нет)

1 1 мес

либо кто знаком с рекапчей я коротко буквально дам
GET https://www.google.com/recaptcha/api2/anchor?ar=1&k=6LcusKUUAAAAALxaUSCxM-kyBHdEXAaIV0LTocvd&co=aHR0cHM6Ly9zdGFnaW5nLmlya2t0LnN0dWRpb3RnLnJ1OjQ0Mw..&hl=ru&v=aUMtGvKgJZfNs4PdY842Qp03&size=invisible&cb=y7huqriackym

вы уже видите нужные параметры
дальше как макаки распознали капчу вы получите uvresp

теперь вам останется сделать только вот такой запрос на смс

POST https://irkkt-mobile.nalog.ru:8888/v2/auth/phone/request HTTP/1.1
Host: irkkt-mobile.nalog.ru:8888
Content-Type: application/json
Device-OS: iOS
Connection: keep-alive
clientVersion: 2.9.0
Device-Id: 77966EE0-AA45-4841-9B03-9F89AF67995C
Accept: */*
User-Agent: billchecker/2.9.0 (iPhone; iOS 13.6; Scale/3.00)
Accept-Language: ru-RU;q=1
Content-Length: 516
Accept-Encoding: gzip, deflate, br

{«phone»:«+123456»,«os»:«iOS»,«captcha»:«распознанная рекапча»,«client_secret»:«IyvrAbKt9h\/8p6a7QPh8gpkXYQ4=»}
клиент секрет фиксированное значение судя по всему

так уходит смс на ваш номер, далее шлете следующий запрос и все у вас есть сессионид.

1 1 мес

вообщем за дампом в тг, тут не запостил не потому что жадный, а потом что больно быстро разрабы изменения вносят, они жадные

Юрий Майоров 1 мес

Спасибо огромное!
С вашим client_secret все заработало!!!

Владислав 1 мес

/v2/mobile/users/lkfl/auth возвращает: «Session was not found», устанавливаю заголовок «sessionId» с существующей сессией — ответ «Not foud». По инн авторизоваться нельзя уже?

Владислав 1 мес

Был косяк, все так же можно авторизоваться

Антон 27 дн

Добрый день.
Есть ли ограничение на количество запрашиваемых чеков в день при авторизации с помощью ИНН/Телефон?

Николай Валиотти 26 дн

Это вопрос к кому? К ФНС?

Антон 27 дн

Теперь при каждой авторизации по номеру телефона отправляется смс c кодом?

Антон 26 дн

Вопрос к разработчику и к тем, кто взял данное решение себе на вооружение.

Николай Валиотти 26 дн

Буду рад вместе с вами, если кто-то протестирует эмпирическим путем и поделится результатами.
Я свой комментарий написал к тому, что никакой документации, как вы понимаете, у эндпоинта нет, хотя могли бы сделать по-человечески.

Виктор Малышев 26 дн

Добрый день!
Подскажите пожалуйста каким образом посмотреть параметры
Device-OS:
clientVersion:
Device-Id:
для Андроид приложения Проверка чеков

Tim Suvorkov 26 дн

Добрый день!
Если брать вход по рекапче https://irkkt-mobile.nalog.ru:8888/v2/auth/phone/request
Какое значение задать параметру captcha? Знает кто? Пример бы хоть какой то

Антон 25 дн

Могу поделиться той незначительной информацией, что есть у меня:
В предыдущей версии с авторизацией через номер телефона и пароль ограничение было в районе 18 чеков в день.
Используя данное решение с авторизацией через ИНН проверил 25 чеков и сообщения о превышении лимита не было.

Антон 16 дн

Господа, а не сталкивались с проблемой избирательности https://irkkt-mobile.nalog.ru:8888 при общении с разными ip адресами?
Столкнулся с проблемой — при запуске кода с локалького пк все ок, а при выполнении запроса из облачного сервера получаю 400 Bad Request

Артем Живодеров 13 дн

Подскажите, откуда изначально взять CLIENT_SECRET, чтобы не использовать ваш?

Alexey Starchikov 12 дн

А кто-то разбирался в авторизации детально? Не просто так (надеюсь) в ответе есть refresh_token.

Популярное