Обработка загрузки кассовых чеков из ИФНС по QR-коду чека (с авторизацией по данным авторизации в ЛК Налогоплательщика).
Проверено на 1с 8.3.19.1351 (будет работать и на младших и на старших релизах платформы). Управляемые формы. Режим совместимости не требуется.
0. Возможно, первоначально нужно скачать приложение и зарегистрироваться. А возможно и так заработает - мы маскируемся под приложение, которое использует APIv2 (irkkt-mobile.nalog.ru:8888).
1. Авторизация - вводите ИНН и пароль из ЛК Налогоплательщика. На выходе получаем некий SessionID.
2. Указываете требуемый QR-код и получаете JSON чека (в обработке он дополнительно преобразован в структуру - со структурой уже что хотите, то и делаете дальше под свои нужды).
#если не вебклиент тогда
&НаКлиенте
Перем HTTPСоединение;
&НаКлиенте
Функция Базовые_headers() // унификация
#Область headers
//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,
//}
#КонецОбласти
// опытным путем оставил обязательные поля
Заголовки = Новый Соответствие;
Заголовки.Вставить("HOST", "irkkt-mobile.nalog.ru:8888");
Заголовки.Вставить("Device-OS", "iOS");
//Заголовки.Вставить("Device-OS", "Windows11"); // любое похожее значение
Заголовки.Вставить("Device-Id", "7C82010F-16CC-446B-8F66-FC4080C66521"); // любое похожее значение
Заголовки.Вставить("Content-Type", "application/json"); // чтобы вернулся json
//Заголовки.Вставить("Content-Charset", "utf-8");
//Заголовки.Вставить("ACCEPT", "*/*");
//Заголовки.Вставить("CLIENT_VERSION", "2.9.0");
//Заголовки.Вставить("Accept-Language", "ru-RU;q=1, en-US;q=0.9");
//Заголовки.Вставить("USER_AGENT", "billchecker/2.9.0 (iPhone; iOS 13.6; Scale/2.00)");
Возврат Заголовки;
#Область ЗаголовкиДляРежимаПоТелефону_ДляЭнтузиастов
// по телефону (дальнейший код не описан и немного отличается от аутентификации по ЛК Налогоплательщика)
// кому интересно - https://github.com/kosov/fns-check/issues/3 // там же борьба с капчей
//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=»}
//клиент секрет фиксированное значение судя по всему
#КонецОбласти
КонецФункции
&НаКлиенте
Процедура УстановитьHTTPСоединение() // унификация
Если HTTPСоединение=Неопределено Тогда
HTTPСоединение = Новый HTTPСоединение("irkkt-mobile.nalog.ru",8888,,,,,Новый ЗащищенноеСоединениеOpenSSL());
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ВыполнитьPOST(HTTPЗапрос) // унификация
УстановитьHTTPСоединение();
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
лТелоКаСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
Если не HTTPОтвет.КодСостояния=200 Тогда
Сообщить("Ответ: "+HTTPОтвет.КодСостояния+Символы.ПС
+HTTPОтвет.Заголовки.Получить("Content-Type")+Символы.ПС
+лТелоКаСтрока);
Возврат Неопределено;
КонецЕсли;
ЧтениеJSON = Новый ЧтениеJSON();
ЧтениеJSON.УстановитьСтроку(лТелоКаСтрока);
Возврат ПрочитатьJSON(ЧтениеJSON);
КонецФункции
&НаКлиенте
Функция ВыполнитьGET(HTTPЗапрос,лТелоКаСтрока) // унификация
УстановитьHTTPСоединение();
HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
лТелоКаСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
Если не HTTPОтвет.КодСостояния=200 Тогда
Сообщить("Ответ: "+HTTPОтвет.КодСостояния+Символы.ПС
+HTTPОтвет.Заголовки.Получить("Content-Type")+Символы.ПС
+лТелоКаСтрока);
Возврат Неопределено;
КонецЕсли;
ЧтениеJSON = Новый ЧтениеJSON();
ЧтениеJSON.УстановитьСтроку(лТелоКаСтрока);
Возврат ПрочитатьJSON(ЧтениеJSON);
КонецФункции
&НаКлиенте
Функция СтруктураВJSON(лСтруктура) // унификация
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON,лСтруктура);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
&НаКлиенте
Процедура Авторизация(Команда)
//url = f'https://{self.HOST}/v2/mobile/users/lkfl/auth'
url = "/v2/mobile/users/lkfl/auth"; // по ЛК Налогоплательщика
//url = "/v2/auth/phone/verify"; // по телефону с капчей
#Область payload
//payload = {
// 'inn': os.getenv('INN'),
// 'password': os.getenv('PASSWORD'),
// 'client_secret': os.getenv('CLIENT_SECRET')
//}
//ТелоКакСтрока =
// "{
// |""inn"": """+ИНН+""",
// |""password"": """+Пароль+""",
// |""client_secret"": ""IyvrAbKt9h/8p6a7QPh8gpkXYQ4=""
// |}"
// ;
////CLIENT_SECRET=mnALjKobrqT/sC9um4wXlamXnOo= // для Android ?
#КонецОбласти
payload = СтруктураВJSON(новый Структура("inn,password,client_secret",ИНН,Пароль,"IyvrAbKt9h/8p6a7QPh8gpkXYQ4="));
headers = Базовые_headers();
//resp = requests.post(url, json=payload, headers=headers)
Запрос = Новый HTTPЗапрос(url, headers);
Запрос.УстановитьТелоИзСтроки(payload);
СтроктураSession_Id = ВыполнитьPOST(Запрос);
Если не СтроктураSession_Id=Неопределено Тогда
sessionId = СтроктураSession_Id.sessionId;
refresh_token = СтроктураSession_Id.refresh_token;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОбновитьSessionId(Команда)
#Область Комментарий
//Насколько я понял, сессия на 24 часа.
//Затем нужно слать POST на https://irkkt-mobile.nalog.ru:8888/v2/mobile/users/refresh указав client_secret и refresh_token. В ответ получаешь новый sessionId и refresh_token.
//POST /v2/mobile/users/refresh HTTP/1.1
//Host: irkkt-mobile.nalog.ru:8888
//Device-OS: Android
//Device-ID: 1234
//Content-Type: application/json
//{
//"client_secret": "",
//"refresh_token": ""
//}
//Response:
//{
//"sessionId": "",
//"refresh_token": ""
//}
//Также после рефреша старые сессии продолжают работать.
#КонецОбласти
//url = f'https://{self.HOST}/v2/mobile/users/refresh'
url = "/v2/mobile/users/refresh";
#Область payload
//ТелоКакСтрока =
// "{
// |""refresh_token"": """+refresh_token+""",
// |""client_secret"": ""IyvrAbKt9h/8p6a7QPh8gpkXYQ4=""
// |}"
// ;
#КонецОбласти
payload = СтруктураВJSON(новый Структура("refresh_token,client_secret",refresh_token,"IyvrAbKt9h/8p6a7QPh8gpkXYQ4="));
headers = Базовые_headers();
//resp = requests.post(url, json=payload, headers=headers)
Запрос = Новый HTTPЗапрос(url, headers);
Запрос.УстановитьТелоИзСтроки(payload);
СтроктураSession_Id = ВыполнитьPOST(Запрос);
Если не СтроктураSession_Id=Неопределено Тогда
sessionId = СтроктураSession_Id.sessionId;
refresh_token = СтроктураSession_Id.refresh_token;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПолучитьЧекПоQR(Команда)
// POST https://irkkt-mobile.nalog.ru:8888/v2/ticket c {"qr":"<данные из QR-кода>"} и
// url = f'https://{self.HOST}/v2/ticket'
url = "/v2/ticket";
#Область payload
//payload = {'qr': qr}
#КонецОбласти
payload = СтруктураВJSON(новый Структура("qr",QRКод));
#Область headers
//curl -X POST 'https://irkkt-mobile.nalog.ru:8888/v2/ticket' \
// --data '{"qr":"<данные из QR-кода>"}'
// -H 'sessionId: <айди сессии>'
// -H 'Content-Type: application/json'
//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,
// }
#КонецОбласти
headers = Базовые_headers();
headers.Вставить("sessionId", sessionId);
//resp = requests.post(url, json=payload, headers=headers)
Запрос = Новый HTTPЗапрос(url, headers);
Запрос.УстановитьТелоИзСтроки(payload);
СтроктураIdЧека = ВыполнитьPOST(Запрос);
Если СтроктураIdЧека=Неопределено Тогда
Возврат;
Иначе
//в ответ придет {"kind":"kkt","id":"<айди чека>","status":0}
IdЧека = СтроктураIdЧека.Id;
КонецЕсли;
// GET https://irkkt-mobile.nalog.ru:8888/v2/tickets/<айди из ответа выше> для получения чека.
url = "/v2/tickets/"+IdЧека;
//Ну и потом
//curl 'https://irkkt-mobile.nalog.ru:8888/v2/tickets/<айди чека>'
// -H 'sessionId: <айди сессии>'
// -H 'Device-OS: Android 4.4'
// -H 'Device-Id: iPhone 12'
Запрос = Новый HTTPЗапрос(url, headers);
лТелоКаСтрока = "";
СтруктураЧека = ВыполнитьGET(Запрос,лТелоКаСтрока);
JSONЧека = лТелоКаСтрока;
// status
//5: Ожидает соединения
//8: Автономная касса / Чек корректен, но отсутствует в хранилище: касса автономна
//9: Ошибка получения
//15: Не кассовый чек / Чек не является кассовым чеком или БСО
//В ответ придёт
//{
// "id": "<id>",
// "status": 2,
// "kind": "kkt",
// "createdAt": "2020-08-14T10:07:19+03:00",
// "statusDescription": {},
// "qr": "<данные из куера>",
// "operation": { "date": "2020-07-26T04:41:00+03:00", "type": 1, "sum": 8080 },
// "seller": { "name": "ИП ...", "inn": "1234567890" },
// "process": [{ "time": "2020-08-14T10:07:19+03:00", "result": 2 }],
// "query": {
// "operationType": 1,
// "sum": 8080,
// "documentId": 55123,
// "fsId": "9280440300312584",
// "fiscalSign": "1857231278",
// "date": "2020-07-26T04:41"
// },
// "ticket": {
// "document": {
// "receipt": {
// "dateTime": 123546789,
// "authorityUri": "www.nalog.ru",
// "cashTotalSum": 0,
// "ecashTotalSum": 8080,
// "fiscalDocumentNumber": 55123,
// "fiscalDriveNumber": "9280440300312584",
// "fiscalSign": 1857231278,
// "items": [
// {
// "name": "083385:хлеб Гречневый 400г СП (БЕЗ НДС)",
// "price": 5790,
// "quantity": 1,
// "sum": 5790
// },
// ...
// ],
// "kktRegId": "0047419162022120",
// "ndsNo": 8080,
// "operationType": 1,
// "operator": "Глебова",
// "receiptCode": 3,
// "requestNumber": 109,
// "retailPlaceAddress": "...",
// "shiftNumber": 230,
// "taxationType": 2,
// "totalSum": 8080,
// "user": "ИП ...",
// "userInn": "1234567890"
// }
// }
// },
// "organization": { "name": "ИП ...", "inn": "1234567890" }
//}
КонецПроцедуры
#КонецЕсли