<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: nalog.ru</title>
<link>http://test.leftjoin.ru/tags/nalog-ru/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Пишем клиент для нового API nalog.ru</title>
<guid isPermaLink="false">56</guid>
<link>http://test.leftjoin.ru/all/nalog-ru-client/</link>
<comments>http://test.leftjoin.ru/all/nalog-ru-client/</comments>
<description>
&lt;p&gt;&lt;b&gt;UPD 29-09-2021&lt;/b&gt;: Мы обновили клиент. Теперь проходить аутентификацию можно по номеру телефона и подтверждению по SMS. &lt;a href="https://github.com/valiotti/get-receipts"&gt;Репозиторий на GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ранее в блоге мы рассказывали, как благодаря &lt;a href="http://test.leftjoin.ru/all/sobiraem-dannye-s-chekov-gipermarketov-na-python/"&gt;открытому API можно собирать данные от ФНС по нашим чекам из магазинов&lt;/a&gt;, обращаясь к приложению налоговой. С прошлой недели способ стал нерабочим: ФНС обновили методы приложения. Сегодня напишем свой клиент для nalog.ru, который проходит авторизацию и отправляет чеки на проверку.&lt;/p&gt;
&lt;h2&gt;Авторизация&lt;/h2&gt;
&lt;p&gt;Прежде чем начать пользоваться приложением, наш профиль необходимо авторизовать. В отличии от предыдущей версии, текущая требует прохождение капчи для авторизации по мобильному телефону — такой способ нам не подходит. Проще всего будет входить в профиль по ИНН и паролю от &lt;a href="https://lkfl2.nalog.ru/lkfl/login"&gt;личного кабинета&lt;/a&gt; налогоплательщика. Для хранения этих данных создадим файл &lt;span class="inline-code"&gt;credentials.env&lt;/span&gt;. Переменную CLIENT_SECRET зададим согласно коду: она отвечает за авторизацию приложения. А ИНН и пароль подставляем свои.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;INN = 
PASSWORD = 
CLIENT_SECRET=IyvrAbKt9h/8p6a7QPh8gpkXYQ4=&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь создадим файл &lt;span class="inline-code"&gt;nalog_python.py&lt;/span&gt;, в котором будет описан клиент. Библиотека &lt;span class="inline-code"&gt;dotenv&lt;/span&gt; используется для загрузки нашего логина и пароля из .env файла.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import os
import json
import requests
from dotenv import load_dotenv&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Опишем класс &lt;span class="inline-code"&gt;NalogRuPython&lt;/span&gt;, и начнём с глобальных переменных класса. Здесь перечисляем headers, необходимые для отправки запроса.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;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'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В конструкторе класса прочитаем данные из нашего окружения методом &lt;span class="inline-code"&gt;load_dotenv&lt;/span&gt; и вызовем метод &lt;span class="inline-code"&gt;set_session_id&lt;/span&gt; для получения &lt;span class="inline-code"&gt;__session_id&lt;/span&gt;, который сейчас опишем. Идентификатор сессии необходим для отправки прочих запросов к серверу налоговой, поэтому его получаем первым делом.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def __init__(self):
        load_dotenv()
        self.__session_id = None
        self.set_session_id()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Метод set_session_id проводит авторизацию пользователя по ИНН и паролю от личного кабинета налогоплательщика и ничего не возвращает, только задаёт значение переменной &lt;span class="inline-code"&gt;__session_id&lt;/span&gt;. Отправляем по указанному в глобальных переменных &lt;span class="inline-code"&gt;HOST&lt;/span&gt; запрос с нашими данными от аккаунта и получаем идентификатор сессии в ответе.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def set_session_id(self) -&amp;gt; None:
        if os.getenv('CLIENT_SECRET') is None:
            raise ValueError('OS environments not content &amp;quot;CLIENT_SECRET&amp;quot;')
        if os.getenv('INN') is None:
            raise ValueError('OS environments not content &amp;quot;INN&amp;quot;')
        if os.getenv('PASSWORD') is None:
            raise ValueError('OS environments not content &amp;quot;PASSWORD&amp;quot;')

        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']&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Получение идентификатора чека&lt;/h2&gt;
&lt;p&gt;Следующий шаг — получение &lt;span class="inline-code"&gt;ticket_id&lt;/span&gt;. Прежде чем отправить сам чек, необходимо получить его идентификатор для проверки. Опишем метод &lt;span class="inline-code"&gt;_get_ticket_id&lt;/span&gt;, который принимает строку с расшифрованным QR-кодом чека, отправляет соответствующий запрос на сервер и возвращает идентификатор для этой строки. В headers помимо указания глобальных переменных появился также &lt;span class="inline-code"&gt;__session_id&lt;/span&gt;, который требует метод &lt;span class="inline-code"&gt;/v2/ticket&lt;/span&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def _get_ticket_id(self, qr: str) -&amp;gt; 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()[&amp;quot;id&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Расшифровка чека&lt;/h2&gt;
&lt;p&gt;Последний шаг — проверка чека. Формируем по &lt;span class="inline-code"&gt;ticket_id&lt;/span&gt; запрос к серверу и получаем подробную расшифровку чека в ответе. На этом клиент полностью описан и готов к работе.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_ticket(self, qr: str) -&amp;gt; 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()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наконец, для удобства опишем пример работы клиента. Создадим экземпляр класса &lt;span class="inline-code"&gt;NalogRuPython&lt;/span&gt;, зададим для примера строку QR-кода и получим расшифрованный &lt;span class="inline-code"&gt;ticket&lt;/span&gt;, который затем напечатаем на экране.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;if __name__ == '__main__':
    client = NalogRuPython()
    qr_code = &amp;quot;t=20200727T1117&amp;amp;s=4850.00&amp;amp;fn=9287440300634471&amp;amp;i=13571&amp;amp;fp=3730902192&amp;amp;n=1&amp;quot;
    ticket = client.get_ticket(qr_code)
    print(json.dumps(ticket, indent=4))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Клиент можно использовать и в своих скриптах: для этого нужно импортировать класс, создать экземпляр и, как и в примере, вызвать метод &lt;span class="inline-code"&gt;get_ticket&lt;/span&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from nalog_python import NalogRuPython

client = NalogRuPython()
qr_code = &amp;quot;t=20200727T1117&amp;amp;s=4850.00&amp;amp;fn=9287440300634471&amp;amp;i=13571&amp;amp;fp=3730902192&amp;amp;n=1&amp;quot;
ticket = client.get_ticket(qr_code)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://github.com/valiotti/get-receipts"&gt;Полный код проекта на GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<pubDate>Wed, 19 Aug 2020 12:59:28 +0300</pubDate>
</item>


</channel>
</rss>