Новшества по 54-ФЗ в области новой ККТ и все новые аббревиатуры из двух и трех букв (ОФД, ФН, ФД и т.д.) – одни из главных событий в торговой отрасли 2025-2025-2025 гг. Несмотря на всю сложность выполнения новых обязательных требований для магазинов, для рядовых пользователей появляется возможность автоматизировать получение данных о своих покупках от Операторов фискальных данных (ОФД).
Государство ввело QR-код в чеке как инструмент гражданского контроля. Можно скачать бесплатное мобильное приложение «Проверка кассового чека в ФНС России», отсканировать QR-код и проверить чек на корректность. Однако, практической пользы для накопления данных о своих покупках с последующим их анализом, мобильное приложение ФНС пока предоставляет в очень ограниченном объеме. Кроме того, остается вопрос сохранения относительной приватности покупателя, так как для получения возможностей сохранения чеков в своем личном кабинете необходимо зарегистрироваться с предоставлением своих персональных данных.
Вашему вниманию предлагается несколько вариантов использования данных со своих онлайн-чеков (на примере получения данных о покупках в магазинах «Лента»):
— внешняя обработка для получения данных о покупках по ФПД (фискальному признаку документа) и сумме чека. Теоретические обработка должна работать в любой конфигурации на управляемых формах, у которой режим совместимости позволяет работать с методами типа «СтрНайти». Открывается через меню «Файл»
— мобильное приложение на платформе 1С с возможностью сканирования QR-кодов и загрузки данных в мобильное приложение с последующим анализом на количество и сумму покупок по товарам. Мобильное приложение скомпилировано для ОС Android (хотя приложение проверялась на разных версиях системы Android 3-4-5-6, однако, возможна ситуация, когда приложение не будет работать). Разработанное мобильное приложение никуда не передает данные и не обменивается с другими базами данных, приложениями или программами. Для загрузки онлайн-чеков не используются какие-либо персональные данные пользователя. Все накопленные данные являются исключительно собственностью пользователя мобильного приложения. Поэтому вся ответственность за сохранностью и целостностью данных лежит исключительно на пользователе мобильного приложения. Если у вас не получается загрузить мобильное приложением, то попробуйте его скачать с облака ТПУ по ссылке: https://filecloud.tpu.ru/index.php/s/ejfo64JndsC78HV
— инструкция по работе с мобильным приложением. Инструкцию по работе с мобильным приложением можно также скачать по ссылке: https://filecloud.tpu.ru/index.php/s/sBQ9JmHxLYMKp3p
Ниже приведены исходные модули внешней обработки. Настоящая статья написана в поддержку доклада «Народный big data или 54-ФЗ на службе анализа и планирования для рядовых покупателей» на конференции INFOSTART EVENT 2025 COMMUNITY (прямая ссылка на доклад: http://event.infostart.ru/2017/agenda/#item644121) Если доклад пройдет в итоговый список выступлений, то будет также выложены исходные модули мобильного приложения.
Модули формы внешней обработки:
&НаСервере
Процедура ЗагрузитьЧекНаСервере()
ТекОбъект = РеквизитФормыВЗначение("Объект");
URL = СокрЛП(Объект.URLЧека);
ОтправляемаяКомандаНаФокус = URL;
URLРазделенный = ТекОбъект.РазделитьURL(URL);
Таймаут = 5;
ИмяСервера = URLРазделенный.ИмяСервера;
ПутьКФайлуНаСервере = URLРазделенный.ПутьКФайлуНаСервере;
Протокол = URLРазделенный.Протокол;
Порт = Неопределено;
ЗащищенноеСоединение = Неопределено;
Если Протокол = "https" Тогда
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL;
КонецЕсли;
ПолнаяСтруктураURL = ТекОбъект.СтруктураURI(URL);
Если Не ПустаяСтрока(ПолнаяСтруктураURL.Порт) Тогда
ИмяСервера = ПолнаяСтруктураURL.Хост;
Порт = ПолнаяСтруктураURL.Порт;
КонецЕсли;
Прокси = ТекОбъект.ПолучитьПрокси(Протокол);
Попытка
Соединение = Новый HTTPСоединение(ИмяСервера, Порт, , , Прокси, Таймаут, ЗащищенноеСоединение);
Исключение
ИнформацияОбОшибке = ИнформацияОбОшибке();
СообщениеОбОшибке = НСтр("ru = 'Ошибка при создании HTTP-соединения с сервером %1:'") + Символы.ПС + "%2";
ВызватьИсключение СообщениеОбОшибке;
КонецПопытки;
Заголовки = Новый Соответствие;
HTTPЗапрос = Новый HTTPЗапрос(ПутьКФайлуНаСервере, Заголовки);
HTTPОтвет = Соединение.Получить(HTTPЗапрос);
ФайлОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();
HTTPОтвет = ТекОбъект.СтроковыеФункцииКлиентСерверИзвлечьТекстИзHTML(ФайлОтвета);
КоличествоТоваровВЧеке = СтрЧислоВхождений(HTTPОтвет,"vmb_i67bc30956372029315_vblock")/13 - 1;
Для НомерТовара = 0 По КоличествоТоваровВЧеке Цикл
ПоправкаНаДлинуНомераСтроки = 0;
Если НомерТовара >= 10 Тогда
ПоправкаНаДлинуНомераСтроки = 1;
КонецЕсли;
ТекНачалоСтрокиТовара = СтрНайти(HTTPОтвет,"vmb_i67bc30956372029315_vblock"+СокрЛП(НомерТовара),,,2);
Если ТекНачалоСтрокиТовара = 0 Тогда
Продолжить;
КонецЕсли;
НачалоСтрокиТовара = ТекНачалоСтрокиТовара + 54 + ПоправкаНаДлинуНомераСтроки;
ОкончаниеСтрокиТовара = СтрНайти(HTTPОтвет,"</span>",,НачалоСтрокиТовара);
ДлинаНазванияТовара = ОкончаниеСтрокиТовара - НачалоСтрокиТовара;
НазваниеТовара = Сред(HTTPОтвет,НачалоСтрокиТовара,ДлинаНазванияТовара);
//количество
НачалоСтрокиКоличества = СтрНайти(HTTPОтвет,"vmb_i67bc30956372029315_vblock"+СокрЛП(НомерТовара),,,3) + 54 + ПоправкаНаДлинуНомераСтроки;
ОкончаниеСтрокиКоличества = СтрНайти(HTTPОтвет,"</span>",,НачалоСтрокиКоличества);
ДлинаКоличества = ОкончаниеСтрокиКоличества - НачалоСтрокиКоличества;
Количество = Сред(HTTPОтвет,НачалоСтрокиКоличества,ДлинаКоличества);
//цена
НачалоСтрокиЦены = СтрНайти(HTTPОтвет,"vmb_i67bc30956372029315_vblock"+СокрЛП(НомерТовара),,,4) + 54 + ПоправкаНаДлинуНомераСтроки;
ОкончаниеСтрокиЦены = СтрНайти(HTTPОтвет,"</span>",,НачалоСтрокиЦены);
ДлинаЦены = ОкончаниеСтрокиЦены - НачалоСтрокиЦены;
Цена = Сред(HTTPОтвет,НачалоСтрокиЦены,ДлинаЦены);
//сумма
НачалоСтрокиСуммы = СтрНайти(HTTPОтвет,"vmb_i67bc30956372029315_vblock"+СокрЛП(НомерТовара),,,9) + 55 + ПоправкаНаДлинуНомераСтроки;
ОкончаниеСтрокиСуммы = СтрНайти(HTTPОтвет,"</span>",,НачалоСтрокиСуммы);
ДлинаСуммы = ОкончаниеСтрокиСуммы - НачалоСтрокиСуммы;
Сумма = Сред(HTTPОтвет,НачалоСтрокиСуммы,ДлинаСуммы);
НоваяСтрока = Объект.Покупки.Добавить();
НоваяСтрока.НазваниеТовара = НазваниеТовара;
НоваяСтрока.Количество = Количество;
НоваяСтрока.Цена = Цена;
НоваяСтрока.Сумма = Сумма;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьЧек(Команда)
Объект.Покупки.Очистить();
СразуСсылкаНаЧек = "http://receipt.taxcom.ru/v01/show?fp=" + СокрЛП(Объект.ФПД) + "&s=" + СокрЛП(Формат(Объект.СуммаПокупки,"ЧГ=0")) + "&sf=False&sfn=False";
Объект.URLЧека = СразуСсылкаНаЧек;
ЗагрузитьЧекНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ПерейтиПоСсылке(Команда)
ЗапуститьПриложение(Объект.URLЧека);
КонецПроцедуры
Модули объекта внешней обработки:
Функция РазделитьURL(URL) Экспорт
Результат = ПолучениеФайловИзИнтернетаКлиентСервер.РазделитьURL(URL);
Если Результат.Свойство("ПустьКФайлуНаСервере") Тогда
Результат.Вставить("ПутьКФайлуНаСервере", Результат.ПустьКФайлуНаСервере);
КонецЕсли;
Возврат Результат;
КонецФункции
Функция СтруктураURI(СтрокаURI) Экспорт
Возврат ОбщегоНазначенияКлиентСервер.СтруктураURI(СтрокаURI);
КонецФункции
Функция ПолучитьПрокси(Протокол) Экспорт
Попытка
Возврат ПолучениеФайловИзИнтернетаКлиентСервер.ПолучитьПрокси(Протокол);
Исключение
Возврат Неопределено;
КонецПопытки;
КонецФункции
Процедура УстановитьHTTPЗаголовки(Заголовки) Экспорт
Куки = Новый Соответствие;
Куки.Вставить("new-int-rel", "1");
Куки.Вставить("expires", Формат(ДобавитьМесяц(ТекущаяДата(), 1), "Л=en_US; ДФ='ddd, dd-MMM-yyyy H:mm:ss G""MT'"));
Куки.Вставить("path", "/");
СтрКуки = "";
Для Каждого Кука Из Куки Цикл
СтрКуки = СтрКуки + "; " + Кука.Ключ + "=" + КодироватьСтроку(Кука.Значение, СпособКодированияСтроки.КодировкаURL);
КонецЦикла;
СтрКуки = Сред(СтрКуки, 3);
Заголовки.Вставить("Cookie", СтрКуки);
КонецПроцедуры
Функция СтроковыеФункцииКлиентСерверИзвлечьТекстИзHTML(Знач ИсходныйТекст) Экспорт
Результат = "";
Текст = НРег(ИсходныйТекст);
Позиция = Найти(Текст, "<body");
Если Позиция > 0 Тогда
Текст = Сред(Текст, Позиция + 5);
ИсходныйТекст = Сред(ИсходныйТекст, Позиция + 5);
Позиция = Найти(Текст, ">");
Если Позиция > 0 Тогда
Текст = Сред(Текст, Позиция + 1);
ИсходныйТекст = Сред(ИсходныйТекст, Позиция + 1);
КонецЕсли;
КонецЕсли;
Позиция = Найти(Текст, "</body>");
Если Позиция > 0 Тогда
Текст = Лев(Текст, Позиция - 1);
ИсходныйТекст = Лев(ИсходныйТекст, Позиция - 1);
КонецЕсли;
ПозицияНачалаУдаления = стрНайти(Текст, "ttc_multiple_block_panel",,1);
ДлинаТекста = СтрДлина(Текст);
ВтораяЧастьТекста = Прав(Текст,ДлинаТекста - ПозицияНачалаУдаления - 29);
ИсходныйТекст = ВтораяЧастьТекста;
Результат = Результат + ИсходныйТекст;
Возврат СокрЛП(Результат);
КонецФункции
Данная статья не претендует на универсальность в загрузке данных чеков с ОФД. Автор хотел лишь поделиться своим мнением и некоторыми наработками в вопросе работы с доступными данными с сайтов ОФД.









Интересно
Сейчас почти наладили взаимодействие Битрикс + Касса Атол на ферме касс с последующи сброса чека в базу 1с
А откуда информацию берете о сервисах по получению информации о чеках?
(1) В данном случае данные о сервисе берутся непосредственно с сервиса проверки чеков оператора фискальных данных Такском ( с передачей всех необходимых для проверки чеков для этого ОФД параметров: фискального признака документа и суммы чека.
В обработке это строка кода:
СразуСсылкаНаЧек = «http://receipt.taxcom.ru/v01/show?fp=» + СокрЛП(Объект.ФПД) + «&s=» + СокрЛП(Формат(Объект.СуммаПокупки,»ЧГ=0″))
(2)т.е. у всех ОФД буду открытые сервисы для программного подключения и вытаскивания информации о чеках (а не просто формочка на их сайте) ?
(3) Этот вопрос к каждому ОФД в отдельности. На сайтах ОФД про такого рода возможности ничего не написано. Лично мы в своих исследованиях из 10 действующих на текущий момент ОФД возможность получения данных по чекам нашли только у троих. Частично этот вопрос раскрывается в моем докладе на конференции Инфостарт-2017 (.
(4)а каких ОФД не подскажите? первый ОФД( там есть?
(5) Нет, с Первого ОФД автоматически данные получать пока не получается. Данные удается пока получать только с ЭВОТОРа, ПЕТЕР-СЕРВИС Спецтехнологии и Такскома. Но даже во всех этих случаях есть свои ньюансы и сложности.
А в каком формате чек возвращается?
(7) Для приведенного примера работы с ОФД Такском формат чека XML. Некоторые ОФД возвращают в формате JSON.
Нашел на gihube интересный код для получения данных онлайн-чека с Первого ОФД, нужно только помочь перевести его часть кода с Python на язык 1С (вставил сюда только самую значимую часть кода:
url_first_get = «https://consumer.1-ofd.ru/#/landing» url_receipt_get = «https://consumer.1-ofd.ru/api/tickets/ticket/{}» url_receipt_find = «https://consumer.1-ofd.ru/api/tickets/find-ticket» def is_suitable(self, data): //функция по возврату булевых данных return data[‘fiscal_drive_id’] and data[‘fiscal_id’] and data[‘fiscal_document_number’] and not data[‘kkt’] def search(self): //self — сама на себя print(«Search in ofd1…») ofd1_payload = { «fiscalDocumentNumber»: self.fiscal_document_number, «fiscalDriveId»: self.fiscal_drive_id, «fiscalId»: self.fiscal_id } # fix for single quotes server error ofd1_payload = json.dumps(ofd1_payload, sort_keys=True) session = requests.Session() session.get(self.url_first_get) session.headers.update({ ‘Content-Type’: ‘application/json’, # fix 415 error ‘X-XSRF-TOKEN’: session.cookies.get_dict()[‘XSRF-TOKEN’], ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36’ }) # print(session.headers) # print(session.cookies.get_dict()) # cookies = session.cookies.get_dict().copy() # cookies.update({ # ‘PLAY_LANG’: ‘ru’ # }) # print(cookies) ofd1 = session.post(self.url_receipt_find, data=ofd1_payload) if (ofd1.status_code == 200): answer = ofd1.json() status = answer[«status»] self.receipt_id = answer[«uid»] print(«Getting the receipt…») ofd1 = requests.get(self.url_receipt_get.format(self.receipt_id)) if (ofd1.status_code == 200): self.raw = json.dumps( ofd1.json(), ensure_ascii=False).encode(‘utf8’) self.receipt_data = json.loads(self.raw) filename = self.get_receipt_file_name() if not os.path.exists(filename): with open(filename, ‘w’) as outfile: outfile.write(self.raw) else: print(«Receipt already saved!») if not self.resend: print(«Skipping…») return False return True else: print(«Error {} while getting receipt from ofd1!».format( ofd1.status_code)) if config.debug: print(ofd1.text) elif (ofd1.status_code == 404): print(«Not found!») else: print(«Error {} while searching in ofd1!».format(ofd1.status_code)) if config.debug: print(ofd1.text) return FalseПоказать