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

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

UPD 29-09-2021: Мы обновили клиент. Теперь проходить аутентификацию можно по номеру телефона и подтверждению по SMS. Репозиторий на GitHub

Ранее в блоге мы рассказывали, как благодаря открытому 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

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

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

Николай Валиотти 2020

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

Юрий Майоров 2020

Добрый день!
Подскажите, пожалуйста, что я делаю не так.
Может я не совсем понял, ваш код, так как не владею 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'

Николай Валиотти 2020

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

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'
Просто Вася 2020

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

Николай Валиотти 2020

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

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

1 2020

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

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

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

1 2020

либо кто знаком с рекапчей я коротко буквально дам
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 2020

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

Юрий Майоров 2020

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

Владислав 2020

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

Владислав 2020

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

Антон 2020

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

Николай Валиотти 2020

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

Антон 2020

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

Антон 2020

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

Николай Валиотти 2020

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

Виктор Малышев 2020

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

Tim Suvorkov 2020

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

Антон 2020

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

Антон 2020

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

Артем Живодеров 2020

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

Alexey Starchikov 2020

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

Иван Русь 2020

Запрос такой:

GET https://irkkt-mobile.nalog.ru:8888/v2/check/ticket?fsId=XXX&operationType=1&documentId=XXX&fiscalSign=XXX&date=2020-09-14T12:00:00&sum=XXX

fsId — ФН
operationType — тип операции (1 — приход, 2 — возврат прихода, 3 — расход, 4 — возврат расхода)
documentId — ФД
fiscalSign — ФП
date — дата покупки
sum — сумма чека (без разделителей)

При корректном чеке статус ответа будет 204 (пустая страница), при некорректном покажет ошибку

источник https://github.com/DmitriyBobrovskiy/CheckReceiptSDK/issues/12#issuecomment-692067673

Николай Валиотти 2020

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

Alexander Chernykh 2020

Хм, отличное руководство, но у меня видимо руки растут не оттуда. Когда получил ticket_id и время расшифровывать чек, то в ответе прилетает Not Found.

Alexander Chernykh 2020

Да, действительно, не из того места :) Вместо GET запроса делал POST на последнем этапе. Автору огромное спасибо!

Павел Полушин 2021

Добрый день. Правильно ли я понял, что CLIENT_SECRET вы получили, как организация и просто пошарили его для всех нас?)
Свой получать только в ФНС?

Николай Валиотти 2021

Он там у всех, кажется, один и тот же был

Букреев Сергей 2021

Привет. Возвращает 403 ошибку в функции: set_session_id(self) в строке: resp = requests.post(url, json=payload, headers=headers). content = b’Bad LKFL credentials’
Подскажите, у вас до сих пор всё работает? Клиент сикрет использую такой: b’{«inn»: «мой инн», «client_secret»: «IyvrAbKt9h/8p6a7QPh8gpkXYQ4=», «password»: «мой пароль»}’

П.С. Использую ваш пример с гита

Павел Полушин 2021

Переписал пример на С#. Пока что работает. Спасибо.

Павел Полушин 2021

Если вдруг кому интересно, можно таким же путем дернуть все свои чеки, как они видны в приложении через запрос к https://irkkt-mobile.nalog.ru:8888/v2/tickets/.
Разумеется, для этого точно так же нужно сначала получить sessionid.

Павел Полушин 2021

Николай, возможно вы в курсе, принимает ли API GET запросы с фильтрацией результата. Например по дате?

Николай Валиотти 2021

К сожалению, не в курсе

Dmitry Valentsev 2021

Добрый день!

Подскажите — не поменялись ли механизм/параметры получения session_id в API ФНС ?

С недавнего времени стал получать ошибку сервера 500, при выполнении http-запроса в методе set_session_id(self) с проекта https://github.com/valiotti/get-receipts

Раньше работало

Антон 2021

Добрый день. Возвращается статус Unexpected service error

Evgeny Yakovlev 2021

То же самое, возвращается Unexpected service error, 500Internal Server Error

Ян Краснов 2021

Подписываюсь под этой темой
Сервер выдает ошибку Unexpected service error

Скорее всего изменилась авторизация (метод/заголовки/url)

Николай Валиотти 2021

Ян, здравствуйте! Подскажите, какая у вас ОС на телефоне?

Damnexile 2021

Всё работает, спасибо большое!

rafik 2022

Добрый день, а есть где-то описание формата возвращаемой json’ки? Потому что по опыту для разных типов чеков разный набор полей возвращается

Damir 2022

Здравствуйте! У кого-нибудь получилось сохранить сессию регистрации? И для чего нужна функция refresh token?

Дмитрий Иванов 11 мес

Добрый день

Пытаюсь распарсить уже выгруженный файл с чеками и получаю ошибку:

KeyError: ’document’

в чем может быть дело?

Популярное