Универсальный API для получения информации по чекам

в 13:52, , рубрики: api, open source, проверка чека, универсальное апи для проверки чека, фнс

Привет жителям Хабра.

В данной статье хотелось бы рассказать про API для получения чеков, которое нам не предоставила всеми любимая ФНС.

Когда только появились QR-коды на чеках я подумал «Вау, как круто! Ты сканируешь код и видишь если не всю инфу по чеку, то ссылку на него». И какого же было мое разочарование, когда просканировав такой код я увидел что-то вроде
t=20180518T220500&s=975.88&fn=8710000101125654&i=99456&fp=1250448795&n=1
Но расстраиваться я не стал и подумал, что ФНС позаботилась о нас и предоставила API для получения такой информации. Погуглив некоторое время я понял, что ФНС нам предоставила только мобильное приложение для проверки чека и просмотра той информации, что поступила к ним от магазина.

Но! Между магазином и налоговой имеется ещё одно звено — ОФД — те, кто обрабатывают информацию по чекам, полученную от магазинов, и отправляют в налоговую. Вот они то и предоставляют API для получения нужной нам информации. Не все. И не всегда бесплатно.

Судя по информации из википедии по состоянию на 1 марта 2018 зарегистрировано 17 ОФД. Допустим 10 из них предоставляют открытое и бесплатное API. Учитывая то, что мы не знаем с каким ОФД работает конкретный магазин, нужно будет пройтись по API 10 операторов фискальных данных. Далеко не лучший вариант.

Спустя какое-то время, я случайно наткнулся на приложение (не от ФНС), которое по QR-коду с чека получает информацию по чеку. Не будут же они «пробегать» по всем ОФД и собирать оттуда информацию — подумал я. Снова отправился в гугл и наткнулся на такой ответ.
Казалось, после этого ответа можно заканчивать импровизированное расследование, но у меня оставались ещё вопросы:

  • Что будет, если использовать другие заголовки?
  • Что делать, если пользователь не зарегистрирован? Скачивать мобильное приложение и регистрироваться? (Сайт ФНС не предоставляет возможности зарегистрироваться в этом контексте)
  • А если забыл пароль?

Запустив Android Device Monitor и SoapUI я начал разбираться. Выкладываю здесь всю обобщенную информацию, что удалось получить. ФНС предоставляет следующее публичное API:

Регистрация

POST
https://proverkacheka.nalog.ru:9999/v1/mobile/users/signup
Content-Type: application/json; charset=UTF-8
Содержимое:

{"email":"some@mail.com","name":"SomeName","phone":"+79991234567"}

Все параметры обязательные.

Если результат успешен, то пользователь создается, СМС с паролем отправляется на указанный номер, а в ответ возвращается 204 No content.
Если пользователь уже существует, то возвращается 409 Conflict и сообщение «user exists».
Если номер телефона некорректный, то возвращается 500 Internal Server Error и сообщение «failed with code 20101».
Если адрес электронной почты некорректный, то возвращается 400 Bad Request и сообщение "[«Object didn't pass validation for format email: <адрес электронной почты, который вы указали>»]".
Если адрес электронной почты уже используется, а телефон нет, то ошибок не возникает и регистрация проходит успешно.

Логин

GET
https://proverkacheka.nalog.ru:9999/v1/mobile/users/login
В заголовке передается Pre-emptive Basic Authorization, где в качестве username передается номер телефона, в виде "+79991234567", а в качестве пароля — код, полученный в смс при регистрации или восстановлении пароля.
Если все хорошо, то вернется 200 OK и сообщение в виде json

{
   "email": "<адрес электронной почты, указанный при регистрации>",
   "name": "<имя, указанное при регистрации>"
}

Если указать некорректный номер телефона или пароль, то вернется 403 Forbidden и сообщение «the user was not found or the specified password was not correct».
Если не указать номер телефона и/или пароль, то не вернется ничего.

Восстановление пароля

POST
https://proverkacheka.nalog.ru:9999/v1/mobile/users/restore
Content-Type: application/json; charset=UTF-8
Содержимое:
{"phone":"+79991234567"}

Если номер телефона найден, то возвращается 204 No Content и на телефон приходит СМС с новым паролем.
Если номер телефона не найден или номер некорректный, то возвращается 404 Not Found и сообщение «the user was not found».

Проверка существования чека

GET
https://proverkacheka.nalog.ru:9999/v1/ofds/*/inns/*/fss/<номер ФН>/operations/1/tickets/<номер ФД>?fiscalSign=<номер ФПД>&date=2018-05-17T17:57:00&sum=3900
Где

  • Номер ФН (Фискальный Номер) — 16-значный номер. Например 8710000100518392
  • Номер ФД (Фискальный документ) — до 10 знаков. Например 54812
  • Номер ФПД (Фискальный Признак Документа, также известный как ФП) — до 10 знаков. Например 3522207165
  • (Мои догадки) В качестве единицы используется параметр с QR-кода в чеке, помеченный в начале статьи, как n=1
  • Дата — дата с чека. Формат может отличаться. Я пробовал переворачивать дату (т.е. 17-05-2018), ставить вместо Т пробел, удалять секунды
  • Сумма — сумма с чека в копейках

Если чек найден, то вернется 204 No Content.
Если чек не найден, то вернется 406 Not Acceptable.
Если дата/сумма некорректная или не совпадает с датой/суммой, указанной в чеке, то возвращается 406 Not Acceptable. При этом секунды не учитываются.
Если не указать параметр дата/сумма, то возвращается 400 Bad Request и сообщение "[«Missing required property: <property_name>»]".

Получение детальной информации по чеку

GET
https://proverkacheka.nalog.ru:9999/v1/inns/*/kkts/*/fss/<Номер ФН>/tickets/<Номер ФД>?fiscalSign=<Номер ФПД>&sendToEmail=no
Где

  • Номер ФН (Фискальный Номер) — 16-значный номер. Например 8710000100518392
  • Номер ФД (Фискальный документ) — до 10 знаков. Например 54812
  • Номер ФПД (Фискальный Признак Документа, также известный как ФП) — до 10 знаков. Например 3522207165
  • (Мои догадки) В качестве единицы используется параметр с QR-кода в чеке, помеченный в начале статьи, как n=1

Также обязательно указать хотя бы пустые заголовки device-id и device-os
Если указаны некорректные данные пользователя, то возвращается 403 Forbidden и сообщение «the user was not found or the specified password was not correct».
Если не указать номер телефона и/или пароль, то ничего не вернется.
Если перед вызовом данного метода не происходила проверка существования чека, то вернется 202 Accepted (без сообщений и любого содержимого). При повторном вызове информация по чеку вернется.
Если в параметре «sendToEmail» попытаться подставить значение «yes», то вернется 500 Internal Server Error и сообщение «connect ECONNREFUSED 127.0.0.1:465». При попытке подставить другие значения («true», 1 и т.д.) вернется 400 Bad Request и сообщение "[«No enum match for: <значение, которое пытались передать>»]".
Если всё хорошо, то вернется 200 ОК и содержимое в формате json примерно такого вида:

{"document": {"receipt": {
   "operationType": 1,
   "fiscalSign": 3522207165,
   "dateTime": "2018-05-17T17:57:00",
   "rawData": "AwAzAREEEAA4NzEwMDAwMTAwNTE4MzEzDQQUADAwMDExOTM1MTQwNDE0MDUgICAg+gMMADc4MjU3MDYwODYgIBAEBAAJ2gAA9AMEAGzC/Vo1BAYAMQTSDyLSDgQEABYBAAASBAQAogAAAB4EAQAB/AMCADwPPAQPAD0EAwCKrqQ+BAQARzYzNyMERQAGBCcAKjM0OTIyNzcgTkVTVC6MruAuTUFYSUIukZKQgJeAkoWLLjE0MKyrNwQCAJ8P/wMEAAZAQg8TBAIAnw9PBAIAbAH9Aw4AhK6ro+PopaKgIICtraAHBAIAPA85BAEAAE8EAgBsARgEDACAo+Cu4q7goyCOjo7xAyoANjIwMDE3LCCjLiCFqqDipeCoraHj4KMsIOOrLiCAp6itoCwgpC4gMTimHwQBAAE=",
   "totalSum": 3900,
   "nds10": 364,
   "userInn": "7825706086",
   "taxationType": 1,
   "operator": "<Данные кассира>",
   "fiscalDocumentNumber": 54812,
   "properties": [   {
      "value": "G637",
      "key": "Код"
   }],
   "receiptCode": 3,
   "requestNumber": 162,
   "user": "Агроторг ООО",
   "kktRegId": "0001193514041405",
   "fiscalDriveNumber": "8710000100518392",
   "items": [   {
      "sum": 3999,
      "price": 3999,
      "name": "*3492277 NEST.Мор.MAXIB.СТРАЧАТЕЛ.140мл",
      "quantity": 1,
      "nds10": 364
   }],
   "ecashTotalSum": 0,
   "retailPlaceAddress": "620017, г. Екатеринбург, ул. Азина, д. 18ж",
   "cashTotalSum": 3900,
   "shiftNumber": 278
}}}

Где

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

Ещё один пример возвращаемого чека

{"document": {"receipt": {
   "cashTotalSum": 0,
   "fiscalSign": 1301551154,
   "nds18": 4859,
   "operationType": 1,
   "userInn": "7728029110",
   "dateTime": "2018-05-18T22:05:00",
   "fiscalDocumentNumber": 12654,
   "receiptCode": 3,
   "ecashTotalSum": 97588,
   "nds10": 5976,
   "requestNumber": 395,
   "retailPlaceAddress": "г.Екатеринбург, ул.Сулимова, д.50",
   "fiscalDriveNumber": "871000010459859",
   "taxationType": 1,
   "user": "АО ТД Перекресток",
   "operator": "<Данные кассира>",
   "items":    [
            {
         "sum": 3799,
         "quantity": 1,
         "price": 3799,
         "name": "18074 Укроп пакет 100г",
         "nds10": 345
      },
            {
         "sum": 7490,
         "quantity": 0.872,
         "nds18": 1143,
         "name": "2000339 Яблоки СЕЗОН.ПРЕДЛОЖЕНИЕ 1кг",
         "price": 8590
      }
   ],
   "totalSum": 97588,
   "rawData": "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",
   "shiftNumber": 262,
   "kktRegId": "0001248888049341"
}}}

Особого смысла в логине я не вижу, но он используется в их мобильном приложении. Возможно, в дальнейшем он для чего-нибудь пригодится.
Кому интересен пример реализации подключения к этому API, вот ссылка на гитхаб проект библиотеки, написанной на C#.
По всем вопросам или замечаниям прошу в комментарии.

Автор: Дмитрий Бобровский

Источник