Пишем клиент для нового 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)
Привет!
В коде ещё требуется CLIENT_SECRET
Где его взять?
А пост читали? :) У нас версия с авторизацией по личным данным кабинета в nalog.ru.
Добрый день!
Подскажите, пожалуйста, что я делаю не так.
Может я не совсем понял, ваш код, так как не владею 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'
Попробуйте таким образом:
Николай, а вы код на который даёте ссылку читали? :)
Давайте я вам помогу, вот с этой строки: https://github.com/valiotti/get-receipts/blob/master/nalog_python.py#L27
Конечно, читал.
Прошу прощения, в теле заметки забыл указать CLIENT_SECRET, исправлено :)
ТС все правильно говорит, но раз мы хотим авторизоваться по телефону, там разрабы ввели рекапчу, что в принципе не проблема с использованием сервиса антигейт например.
Они там развернули подпольный бизнес на фоне госки
http://www.gkr.su/ вот разрабы, вот их домены рабочие
http://disconto.me/ http://studiotg.ru/
вообщем люди не хотят чтобы кто то бесплатно юзал по праву очевидно бесплатный сервис который должен принадлежать народу, не удивлен, подмазались разработали для гос сферы софт, далее сели сделали свой «платный» сервис
короче кому надо дамп, где понятно как авторизоваться с помощью телефона(смс), с участием рекапчи, пишите в тг nickholden2
(сам скрипт пока не делал, времени нет)
либо кто знаком с рекапчей я коротко буквально дам
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=»}
клиент секрет фиксированное значение судя по всему
так уходит смс на ваш номер, далее шлете следующий запрос и все у вас есть сессионид.
вообщем за дампом в тг, тут не запостил не потому что жадный, а потом что больно быстро разрабы изменения вносят, они жадные
Спасибо огромное!
С вашим client_secret все заработало!!!
/v2/mobile/users/lkfl/auth возвращает: «Session was not found», устанавливаю заголовок «sessionId» с существующей сессией — ответ «Not foud». По инн авторизоваться нельзя уже?
Был косяк, все так же можно авторизоваться
Добрый день.
Есть ли ограничение на количество запрашиваемых чеков в день при авторизации с помощью ИНН/Телефон?
Это вопрос к кому? К ФНС?
Теперь при каждой авторизации по номеру телефона отправляется смс c кодом?
Вопрос к разработчику и к тем, кто взял данное решение себе на вооружение.
Буду рад вместе с вами, если кто-то протестирует эмпирическим путем и поделится результатами.
Я свой комментарий написал к тому, что никакой документации, как вы понимаете, у эндпоинта нет, хотя могли бы сделать по-человечески.
Добрый день!
Подскажите пожалуйста каким образом посмотреть параметры
Device-OS:
clientVersion:
Device-Id:
для Андроид приложения Проверка чеков
Добрый день!
Если брать вход по рекапче https://irkkt-mobile.nalog.ru:8888/v2/auth/phone/request
Какое значение задать параметру captcha? Знает кто? Пример бы хоть какой то
Могу поделиться той незначительной информацией, что есть у меня:
В предыдущей версии с авторизацией через номер телефона и пароль ограничение было в районе 18 чеков в день.
Используя данное решение с авторизацией через ИНН проверил 25 чеков и сообщения о превышении лимита не было.
Господа, а не сталкивались с проблемой избирательности https://irkkt-mobile.nalog.ru:8888 при общении с разными ip адресами?
Столкнулся с проблемой — при запуске кода с локалького пк все ок, а при выполнении запроса из облачного сервера получаю 400 Bad Request
Подскажите, откуда изначально взять CLIENT_SECRET, чтобы не использовать ваш?
А кто-то разбирался в авторизации детально? Не просто так (надеюсь) в ответе есть refresh_token.
Запрос такой:
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
Отлично, а данные чека есть возможность получить или речь только о проверке статуса?
Хм, отличное руководство, но у меня видимо руки растут не оттуда. Когда получил ticket_id и время расшифровывать чек, то в ответе прилетает Not Found.
Да, действительно, не из того места :) Вместо GET запроса делал POST на последнем этапе. Автору огромное спасибо!
Добрый день. Правильно ли я понял, что CLIENT_SECRET вы получили, как организация и просто пошарили его для всех нас?)
Свой получать только в ФНС?
Он там у всех, кажется, один и тот же был
Привет. Возвращает 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»: «мой пароль»}’
П.С. Использую ваш пример с гита
Переписал пример на С#. Пока что работает. Спасибо.
Если вдруг кому интересно, можно таким же путем дернуть все свои чеки, как они видны в приложении через запрос к https://irkkt-mobile.nalog.ru:8888/v2/tickets/.
Разумеется, для этого точно так же нужно сначала получить sessionid.
Николай, возможно вы в курсе, принимает ли API GET запросы с фильтрацией результата. Например по дате?
К сожалению, не в курсе
Добрый день!
Подскажите — не поменялись ли механизм/параметры получения session_id в API ФНС ?
С недавнего времени стал получать ошибку сервера 500, при выполнении http-запроса в методе set_session_id(self) с проекта https://github.com/valiotti/get-receipts
Раньше работало
Добрый день. Возвращается статус Unexpected service error
То же самое, возвращается Unexpected service error, 500Internal Server Error
Подписываюсь под этой темой
Сервер выдает ошибку Unexpected service error
Скорее всего изменилась авторизация (метод/заголовки/url)
Ян, здравствуйте! Подскажите, какая у вас ОС на телефоне?
Всё работает, спасибо большое!
Добрый день, а есть где-то описание формата возвращаемой json’ки? Потому что по опыту для разных типов чеков разный набор полей возвращается
Здравствуйте! У кого-нибудь получилось сохранить сессию регистрации? И для чего нужна функция refresh token?
Добрый день
Пытаюсь распарсить уже выгруженный файл с чеками и получаю ошибку:
KeyError: ’document’
в чем может быть дело?