Постановка задачи
Организация занимается розничной торговлей. В розничном магазине используется программа 1С, к которой подключена онлайн-касса. Организация имеет интернет-сайт, на котором есть возможность заказать товар. Необходимо сделать возможность оплатить товар на сайте, при этом чек должен пробиться на кассе, которая установлена в розничном магазине.
Что требуется для решения
1. Какой-то особый кассовый аппарат не требуется. Важно, чтобы кассовый аппарат не имел признака «только для расчетов в сети Интернет». Т.е. этого признака не должно быть в реестре ФНС для данной модели, и этот признак не должен устанавливаться при регистрации ККТ. Другими словами, подойдет любой аппарат для розничной торговли, имеющий драйвера для 1С.
2. Для приёма оплаты с помощью банковских карт на сайте потребуется Интернет-эквайринг.
3. Потребуется вспомогательная программа «Модуль ККТ: онлайн-касса по сети»: infostart.ru/public/1109732/. Эта программа позволяет работать с онлайн-кассой по протоколам TCP и HTTP.
Для интернет-магазинов на платформе "1С-Битрикс" уже есть готовые механизмы для приёма оплаты и пробития чека на онлайн-кассе. Битрикс предлагает установить приложение "1С-Битрикс.Кассы" (BxDTO), которое с помощью HTTP-запроса будет получать список чеков с сайта и пробивать их на кассе. При этом имеются два ограничения:
- Приложение BxDTO при запуске займет порт ККТ, и подключиться к кассе из 1С будет невозможно.
- Приложение BxDTO работает только с кассами Атол.
Чтобы обойти эти ограничения, мы предлагаем использовать программу «Модуль ККТ: онлайн-касса по сети»: infostart.ru/public/1109732/. Принцип работы этой программы:
- Программа выполнена в виде службы "Сервер ККТ".
- "Сервер ККТ" подключается к кассовому аппарату и открывает к нему доступ по протоколу TCP.
- Программы 1С подключаются к "Серверу ККТ" через драйвер "Клиент ККТ".
- "Сервер ККТ" каждую минуту выполняет HTTP-запрос к сайту для получения списка чеков. Если в списке есть чеки, то "Сервер ККТ" пробивает чек на кассе.
Приложение BxDTO при этом не нужно, его роль выполняет "Сервер ККТ".
Схема работы программы:
Настройка сайта:
В разделе «Администрирование»–«Магазин»–«Кассы»–«Список касс» нажать на кнопку «Добавить кассу»:
Установить параметры кассы:
Марку ККМ указать ATOL, даже если будет использоваться ККТ другой фирмы
В настройках платежных систем нужно поставить галку «Разрешить печать чеков» (раздел «Администрирование»–«Магазин»–«Настройки»–«Платежные системы»:
Нажать кнопку «Подключить ККМ к интернет-магазину» в разделе Администрирование»–«Магазин»–«Кассы»:
Скачивать и устанавливать приложение «1С-Битрикс.Кассы» не нужно, его роль будет исполнять "Модуль ККТ".
После нажатия кнопки «Подключить ККМ к интернет-магазину» сгенерируется URL, которое потребуется в настройках «Модуля ККТ»:
Настройка «Модуля ККТ»
В программе KKT_Server_Setting.exe установить настройки:
- Обработчик: два варианта (ФФД 1.05 или ФФД1.2)
- URL – ссылка, полученая по кнопке «Подключить ККМ к интернет-магазину»
- ИД кассы – это «Внешний идентификатор кассы» из настроек сайта
- Тип чека: бумажный или электронный
- Открывать смену:
- Нет – не открывать смену.
- При пробитии чека – программа проверит статус смены, если смена еще не открыта, то программа откроет смену.
- В указанное время – программа откроет смену в указанное время
- Закрывать смену:
- Нет – не закрывать смену
- По времени в Битрикс – программа закроет смену по команде из Битрикс
- В указанное время – программа закроет смену в указанное время
- Кассир – ФИО кассира
- На закладке "История чеков" можно указать папку, в которую будут сохраняться напечатанные чеки в формате xml. Может использоваться для загрузки чеков в 1С.
На закладке «Драйвер» нужно установить галки:
• Не отключать устройство
• Разрешить повторное открытие/закрытие смены
Описание работы
После того, как покупатель оплатил заказ, на сайте создается объект "Чек". Посмотреть чеки можно в разделе «Администрирование»–«Магазин»–«Кассы»–«Чеки за день». Изначально чек создаётся со статусом "Не напечатан".
Один раз в минуту служба "Сервер ККТ" отправляет запрос на сайт для получения списка чеков. Чеки, попавшие в список, на сайте получают статус "В процессе печати".
Если в настройках "Сервера ККТ" указано "Открывать смену при пробитии чека", то "Сервер ККТ" предварительно проверит статус кассовой смены:
- если кассовая смена ещё не открыта, то "Сервер ККТ" откроет смену.
- если кассовая смена истекла, то "Сервер ККТ" закроет и откроет смену.
После печати чека "Сервер ККТ" отправляет запрос на сайт для подтверждения. После подтверждения чек получает статус "Напечатан".
На сайте в настройках кассы указывается время, в которое нужно закрывать кассовую смену (формировать Z-отчет). В указанное время на сайте будет создан объект "Z-отчет" (список Z-отчетов можно посмотреть в разделе «Администрирование»–«Магазин»–«Кассы»–«Z-отчеты»). Далее этот Z-отчет добавляется в список чеков и печатается "Сервером ККТ".
Открытие и закрытие кассовых смен
Чтобы избежать коллизий с открытием/закрытием смен в настройках нужно поставить галку "Разрешить повторное открытие/закрытие смены" (см.выше). Тогда "Сервер ККТ", получив от 1С команду "ОткрытьСмену", сначала проверит статус кассовой смены:
- если смена ещё не открыта, то "Сервер ККТ" выполнит команду "ОткрытьСмену"
- если смена уже открыта (например, при печати чека с сайта), то "Сервер ККТ" не будет выполнять команду, но вернёт в 1С результат "Успешно".
Аналогично с закрытием смены. Такой алгоритм позволит корректно работать 1С и сайту.
Загрузка чеков в 1С
При печати чеков в указанной папке будут создаваться xml-файлы, которые можно использовать для импорта в 1С:
210513104147075_OpenShift_26.xml:
<?xml version="1.0" encoding="UTF-8"?>
<OutputParameters>
<Parameters ККТ_id="6756756" ShiftNumber="26" ShiftState="2" DateTime="2021-05-13T10:41:47"/>
</OutputParameters>
210513104147192_Check_51.xml:
<?xml version="1.0" encoding="UTF-8"?>
<CheckPackage>
<Parameters ККТ_id="6756756" Check_id="check|192.168.0.61|21" CashierName="Администратор" OperationType="1" TaxationSystem="0" CustomerEmail="1@mail.com"/>
<Positions>
<FiscalString Name="Футболка Женская Ночь" Quantity="20.00" PriceWithDiscount="759.00" AmountWithDiscount="15180.00" VATRate="none" PaymentMethod="4" CalculationSubject="1"></FiscalString>
<FiscalString Name="Доставка" Quantity="1.00" PriceWithDiscount="199.12" AmountWithDiscount="199.12" VATRate="none" PaymentMethod="4" CalculationSubject="4"></FiscalString>
</Positions>
<Payments Cash="0.00" ElectronicPayment="15379.12" PrePayment="0.00" PostPayment="0.00" Barter="0.00"/>
<OutputParameters ShiftNumber="26" CheckNumber="51" ShiftClosingCheckNumber="1" AddressSiteInspections="" FiscalSign="5187529" DateTime="2021-05-13T10:41:47"/>
</CheckPackage>
210513179044791_CloseShift_26.xml:
<?xml version="1.0" encoding="UTF-8"?>
<OutputParameters>
<Parameters ККТ_id="6756756" ShiftNumber="26" ShiftState="1" DateTime="2021-05-13T09:00:44"/>
</OutputParameters>
Пример обработки для загрузки чеков в конфигурацию "1С:Управление торговлей 11.4":
&НаКлиенте
Процедура КомандаПрочитатьДокументы(Команда)
Отказ = Ложь;
ОписаниеОшибки = "";
ПрочитатьДокументы("\\server\KKT\",Отказ,ОписаниеОшибки);
Если Отказ Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ОписаниеОшибки;
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьСтавкуНДС(VATRate)
Если VATRate="20" Тогда
Результат = Перечисления.СтавкиНДС.НДС20;
ИначеЕсли VATRate="18" Тогда
Результат = Перечисления.СтавкиНДС.НДС18;
ИначеЕсли VATRate="10" Тогда
Результат = Перечисления.СтавкиНДС.НДС10;
ИначеЕсли VATRate="0" Тогда
Результат = Перечисления.СтавкиНДС.НДС0;
ИначеЕсли VATRate="20/120" Тогда
Результат = Перечисления.СтавкиНДС.НДС20_120;
ИначеЕсли VATRate="18/118" Тогда
Результат = Перечисления.СтавкиНДС.НДС18_118;
ИначеЕсли VATRate="10/110" Тогда
Результат = Перечисления.СтавкиНДС.НДС10_110;
Иначе
Результат = Перечисления.СтавкиНДС.БезНДС;
КонецЕсли;
КонецФункции
&НаСервереБезКонтекста
Функция НайтиКассовуюСмену(КассаККМ,НомерСмены,ДатаСмены)
Запрос = Новый Запрос(
"ВЫБРАТЬ
| КассоваяСмена.Ссылка КАК Ссылка,
| КассоваяСмена.Статус КАК Статус
|ИЗ
| Документ.КассоваяСмена КАК КассоваяСмена
|ГДЕ
| КассоваяСмена.Проведен = ИСТИНА
| И КассоваяСмена.НомерСменыККТ = &НомерСмены
| И КассоваяСмена.ДатаСменыККТ >= &ДатаСмены
| И КассоваяСмена.КассаККМ = &КассаККМ");
Запрос.УстановитьПараметр("НомерСмены",НомерСмены);
Запрос.УстановитьПараметр("ДатаСмены",ДатаСмены-86400);
Запрос.УстановитьПараметр("КассаККМ",КассаККМ);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=0 Тогда
Результат = Неопределено;
Иначе
Результат = Новый Структура;
Результат.Вставить("Ссылка",Табл[0].Ссылка);
Результат.Вставить("Статус",Табл[0].Статус);
КонецЕсли;
Возврат Результат;
КонецФункции
&НаСервереБезКонтекста
Процедура ПрочитатьДокументы(Папка,Отказ,ОписаниеОшибки)
Табл = Новый ТаблицаЗначений;
Табл.Колонки.Добавить("Имя");
Табл.Колонки.Добавить("ПолноеИмя");
Табл.Колонки.Добавить("XML");
Файлы = НайтиФайлы(Папка,"*.xml",Ложь);
Для Каждого Стр Из Файлы Цикл
Если Стр.ЭтоФайл() Тогда
НовСтрока = Табл.Добавить();
НовСтрока.Имя = Стр.Имя;
НовСтрока.ПолноеИмя = Стр.ПолноеИмя;
Т = Новый ТекстовыйДокумент;
Т.Прочитать(Стр.ПолноеИмя);
НовСтрока.XML = Т.ПолучитьТекст();
КонецЕсли;
КонецЦикла;
Табл.Сортировать("Имя");
Для Каждого Стр Из Табл Цикл
Если Найти(Стр.Имя,"_Check_")>0 Тогда
СоздатьЧек(Стр.XML,Отказ,ОписаниеОшибки);
ИначеЕсли Найти(Стр.Имя,"_OpenShift_")>0 Тогда
ОткрытьСмену(Стр.XML,Отказ,ОписаниеОшибки);
ИначеЕсли Найти(Стр.Имя,"_CloseShift_")>0 Тогда
ЗакрытьСмену(Стр.XML,Отказ,ОписаниеОшибки);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОткрытьСмену(XML,Отказ,ОписаниеОшибки)
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(XML);
Попытка
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
КассаККМ = НайтиКассуККМ(Д.Parameters.ККТ_id);
Если КассаККМ=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найдена касса ККМ "+Д.Parameters.ККТ_id;
Возврат;
КонецЕсли;
ДатаСмены = ПолучитьДату(Д.Parameters.DateTime);
НомерСмены = Число(Д.Parameters.ShiftNumber);
КассоваяСмена = НайтиКассовуюСмену(КассаККМ,НомерСмены,ДатаСмены);
Если КассоваяСмена<>Неопределено Тогда
Возврат;
КонецЕсли;
Док = Документы.КассоваяСмена.СоздатьДокумент();
Док.НомерСменыККТ = НомерСмены;
Док.Дата = ДатаСмены;
Док.ДатаСменыККТ = ДатаСмены;
Док.НачалоКассовойСмены = ДатаСмены;
Док.Статус = Перечисления.СтатусыКассовойСмены.Открыта;
Док.КассаККМ = КассаККМ;
Док.Организация = Док.КассаККМ.Владелец;
Док.Комментарий = "Интернет-магазин";
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Неоперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗакрытьСмену(XML,Отказ,ОписаниеОшибки)
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(XML);
Попытка
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
КассаККМ = НайтиКассуККМ(Д.Parameters.ККТ_id);
Если КассаККМ=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найдена касса ККМ "+Д.Parameters.ККТ_id;
Возврат;
КонецЕсли;
ДатаСмены = ПолучитьДату(Д.Parameters.DateTime);
НомерСмены = Число(Д.Parameters.ShiftNumber);
КассоваяСмена = НайтиКассовуюСмену(КассаККМ,НомерСмены,ДатаСмены);
Если КассоваяСмена=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Кассовая смена не найдена: "+НомерСмены;
Возврат;
КонецЕсли;
Док = КассоваяСмена.Ссылка.ПолучитьОбъект();
Док.Статус = Перечисления.СтатусыКассовойСмены.Закрыта;
Док.ОкончаниеКассовойСмены = ДатаСмены;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Неоперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
ОтчетОРозничныхПродажах = РозничныеПродажи.ЗаполнитьОтчетОРозничныхПродажахПоКассовойСмене(КассоваяСмена.Ссылка, ОписаниеОшибки);
Если ОтчетОРозничныхПродажах=Неопределено Тогда
Отказ = Истина;
Возврат;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДату(Стр)
А = СтрЗаменить(Стр,"T","");
А = СтрЗаменить(А,"-","");
А = СтрЗаменить(А,":","");
Возврат Дата(А);
КонецФункции
&НаСервереБезКонтекста
Функция НайтиКассуККМ(id)
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка
|ИЗ
| Справочник.КассыККМ КАК Спр
|ГДЕ
| Спр.ПометкаУдаления = ЛОЖЬ
| И Спр.СерийныйНомер = &id
|
|УПОРЯДОЧИТЬ ПО
| Ссылка");
Запрос.УстановитьПараметр("id",id);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=0 Тогда
Результат = Неопределено;
Иначе
Результат = Табл[0].Ссылка;
КонецЕсли;
Возврат Результат;
КонецФункции
&НаСервереБезКонтекста
Процедура СоздатьЧек(XML,Отказ,ОписаниеОшибки)
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(XML);
Попытка
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
КассаККМ = НайтиКассуККМ(Д.Parameters.ККТ_id);
Если КассаККМ=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найдена касса ККМ "+Д.Parameters.ККТ_id;
Возврат;
КонецЕсли;
ДатаЧека = ПолучитьДату(Д.OutputParameters.DateTime);
CashierName = Д.Parameters.CashierName;
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка
|ИЗ
| Справочник.Пользователи КАК Спр
|ГДЕ
| Спр.ПометкаУдаления = ЛОЖЬ
| И Спр.Наименование = &CashierName
|
|УПОРЯДОЧИТЬ ПО
| Ссылка");
Запрос.УстановитьПараметр("CashierName",CashierName);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=0 Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найден пользователь "+CashierName;
Возврат;
КонецЕсли;
Кассир = Табл[0].Ссылка;
КассоваяСмена = НайтиКассовуюСмену(КассаККМ,Число(Д.OutputParameters.ShiftNumber),ДатаЧека);
Если КассоваяСмена=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найдена кассовая смена "+Д.OutputParameters.ShiftNumber;
Возврат;
КонецЕсли;
Если КассоваяСмена.Статус<>Перечисления.СтатусыКассовойСмены.Открыта Тогда
Отказ = Истина;
ОписаниеОшибки = "Статус кассовой смены: "+КассоваяСмена.Статус;
Возврат;
КонецЕсли;
TaxationSystem = Д.Parameters.TaxationSystem;
Если TaxationSystem="0" Тогда
НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаОблагаетсяНДС;
ИначеЕсли TaxationSystem="5" Тогда
НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаПоПатенту;
Иначе
НалогообложениеНДС = Перечисления.ТипыНалогообложенияНДС.ПродажаНеОблагаетсяНДС;
КонецЕсли;
НомерСмены = Число(Д.OutputParameters.ShiftNumber);
НомерЧека = Число(Д.OutputParameters.CheckNumber); //Номер фискального документа (ФД)
ФискальныйПризнак = Д.OutputParameters.FiscalSign; //Фискальный признак (ФПД)
Док = Документы.ЧекККМ.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Док.Валюта = Константы.ВалютаРегламентированногоУчета.Получить();
Док.Кассир = Кассир;
Док.КассаККМ = КассаККМ;
Док.Организация = Док.КассаККМ.Владелец;
Док.НалогообложениеНДС = НалогообложениеНДС;
Док.КассоваяСмена = КассоваяСмена.Ссылка;
//Док.Склад = Настройки.Склад;
Док.ЦенаВключаетНДС = Истина;
Док.ФормаОплаты = Перечисления.ФормыОплаты.Безналичная;
Док.Партнер = Справочники.Партнеры.РозничныйПокупатель;
Док.Комментарий = "Интернет-магазин "+Д.Parameters.Check_id;
Док.Статус = Перечисления.СтатусыЧековККМ.Пробит;
Если Д.Positions.Свойства().Получить("FiscalString")<>Неопределено Тогда
М = Новый Массив;
Если ТипЗнч(Д.Positions.FiscalString)=Тип("СписокXDTO") Тогда
Для Каждого Стр Из Д.Positions.FiscalString Цикл
М.Добавить(Стр);
КонецЦикла;
ИначеЕсли ТипЗнч(Д.Positions.FiscalString)=Тип("ОбъектXDTO") Тогда
М.Добавить(Д.Positions.FiscalString);
КонецЕсли;
Для Каждого Стр Из М Цикл
НовСтрока = Док.Товары.Добавить();
НовСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(Стр.Name,Истина);
НовСтрока.Упаковка = НовСтрока.Номенклатура.ЕдиницаИзмерения;
НовСтрока.Количество = Стр.Quantity;
НовСтрока.КоличествоУпаковок = НовСтрока.Количество;
НовСтрока.Цена = Стр.PriceWithDiscount;
НовСтрока.Сумма = НовСтрока.Цена*НовСтрока.Количество;
НовСтрока.СтавкаНДС = ПолучитьСтавкуНДС(Стр.VATRate);
Если Стр.Свойства().Получить("VATAmount")<>Неопределено Тогда
НовСтрока.СуммаНДС = Стр.VATAmount;
КонецЕсли;
НовСтрока.Продавец = Кассир;
НовСтрока.КлючСвязи = НовСтрока.НомерСтроки;
НовСтрока.ИдентификаторСтроки = Строка(Новый УникальныйИдентификатор);
КонецЦикла;
КонецЕсли;
НовСтрока = Док.ОплатаПлатежнымиКартами.Добавить();
НовСтрока.ЭквайринговыйТерминал = Справочники.ЭквайринговыеТерминалы.НайтиПоНаименованию("Интернет-магазин");
НовСтрока.НомерПлатежнойКарты = "***";
НовСтрока.Сумма = Д.Payments.ElectronicPayment;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Оперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
КонецПроцедуры
В этом примере сайт будет выполнять запрос к 1С для создания документа "Чек ККМ" и пробития чека на кассе. Предполагается, что организация имеет "белый" IP-адрес, на который сайт сможет отправлять HTTP-запрос. При этом сайт может работать на любой платформе, в том числе и на "1С-Битрикс". Так как это не типовая схема работы, то потребуются доработки и сайта и 1С.
Приём оплаты
Для сайтов на платформе "1С-Битрикс" можно использовать существующие обработчики платежных систем. Для других платформ может потребоваться подключение интернет-эквайринга. В упрощенном виде процесс оплаты состоит из следующих шагов:
- По кнопке "Оплатить" клиент переадресуется на URL интернет-эквайринга, где он должен ввести данные своей банковской карты (номер, срок действия и т.д.). Обычно в параметрах запроса указываются URL для обратной связи (отдельно URL для успешной оплаты и URL для ошибок).
- При успешной оплате интернет-эквайер оповещает сайт, используя переданый ему URL для успешной оплаты.
- Сайт оповещает клиента об успешной оплате заказа.
Пробитие чека
После оплаты сайт должен выполнить запрос к 1С для создания документа "Чек ККМ" и пробития чека на кассе. Для этот потребуется доработать конфигурацию 1С, добавив http-сервис. Можно использовать любою конфигурацию, которая поддерживает розничную торговлю и торговое оборудование (Розница, УТ, КА, УНФ и т.д., примеры доработок описаны ниже).
Так как http-сервис в 1С работает в контексте "сервера", а работа с торговым оборудованием в 1С осуществляется в контексте "клиента", была использована вспомогательная программа «Модуль ККТ: онлайн-касса по сети» (//infostart.ru/public/1109732/). Эта программа позволяет подключать онлайн-кассу к службе "Сервер ККТ", а 1С может подключаться к этой службе через драйвер "Клиент ККТ" как в контексте "клиента", так и в контексте "сервера". Получилась следующая схема:
Кассовые смены
Так как Интернет-сайт не зависит от графика работы магазина, то потребовалось внести изменения в процесс открытия и закрытия кассовых смен.
1. Открытие смены
Смену открывает кассир в магазине или Интернет-сайт, в зависимости от того, кто раньше регистрирует оплату.
В начале рабочего дня кассир проверяет статус кассовой смены. Если смена не открыта, то кассир открывает смену. С другой стороны, сайт перед каждым приёмом оплаты тоже проверяет статус кассовой смены и, при необходимости, открывает смену.
2. Закрытие смены
По окончании рабочего дня кассир закрывает смену. Сайт перед каждым приёмом оплаты проверяет статус кассовой смены. Если смена закрыта, открывает смену. По расписанию в конце дня (например, в 23:55) сайт проверяет статус смены. Если смена открыта, то сайт закрывает смену.
Расширение СерверККТ_ИнтернетМагазин_УТ_11_4_13_123.cfe можно скачать в публикации Модуль ККТ: онлайн-касса по сети в разделе Файлы (архив "Примеры адаптаций для 1С:8.3 (cfe).zip")
1. Для взаимодействия сайта и 1С нужно добавить http-сервис:
Модуль http-сервиса:
Функция ВызовМетодаPOST(Запрос)
ИмяМетода = Запрос.ПараметрыURL["ИмяМетода"];
Отказ = Ложь;
ОписаниеОшибки = "";
Попытка
Выполнить("Результат = "+ИмяМетода+"(Запрос.ПолучитьТелоКакСтроку(),Отказ,ОписаниеОшибки);");
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Если Отказ Тогда
Результат = ОписаниеОшибки;
КодСостояния = 201;
Иначе
КодСостояния = 200;
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.УстановитьТелоИзСтроки(Результат, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Ответ.Заголовки["Content-Type"] = "text/html;charset=utf-8";
Возврат Ответ;
КонецФункции
Функция Значение_В_JSON(Значение)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Значение);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Функция JSON_В_Значение(JSON)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(JSON);
Результат = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат Результат;
КонецФункции
Функция ПолучитьНастройки()
Настройки = Новый Структура("КассаККМ,Кассир,НалогообложениеНДС,Склад,ЭквайринговыйТерминал,ВидЦены,СерверККТ,СистемаНалогообложения");
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Рег.КассаККМ КАК КассаККМ,
| Рег.Кассир КАК Кассир,
| Рег.НалогообложениеНДС КАК НалогообложениеНДС,
| Рег.Склад КАК Склад,
| Рег.ЭквайринговыйТерминал КАК ЭквайринговыйТерминал,
| Рег.ВидЦены КАК ВидЦены,
| Рег.СерверККТ КАК СерверККТ,
| Рег.СистемаНалогообложения КАК СистемаНалогообложения
|ИЗ
| РегистрСведений.СерверККТ_HTTP_Настройки.СрезПоследних(&Дата, ) КАК Рег");
Запрос.УстановитьПараметр("Дата",ТекущаяДата());
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ЗаполнитьЗначенияСвойств(Настройки,Выборка);
КонецЕсли;
Возврат Настройки;
КонецФункции
Функция CreateCheck(JSON,Отказ,ОписаниеОшибки)
Попытка
А = JSON_В_Значение(JSON);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON";
Возврат Неопределено;
КонецПопытки;
Настройки = ПолучитьНастройки();
Док = Документы.ЧекККМ.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Док.Валюта = Константы.ВалютаРегламентированногоУчета.Получить();
Док.Кассир = Настройки.Кассир;
Док.КассаККМ = Настройки.КассаККМ;
Док.Организация = Док.КассаККМ.Владелец;
Док.НалогообложениеНДС = Настройки.НалогообложениеНДС;
Док.ВидЦены = Настройки.ВидЦены;
Док.Склад = Настройки.Склад;
Док.ЦенаВключаетНДС = Истина;
Док.ФормаОплаты = Перечисления.ФормыОплаты.Безналичная;
Док.Партнер = Справочники.Партнеры.РозничныйПокупатель;
Док.Комментарий = "Интернет-магазин";
Для Каждого Стр Из А.Positions Цикл
НовСтрока = Док.Товары.Добавить();
НовСтрока.Номенклатура = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(Стр.Ref_Key));
НовСтрока.Упаковка = НовСтрока.Номенклатура.ЕдиницаИзмерения;
НовСтрока.Количество = Стр.Quantity;
НовСтрока.КоличествоУпаковок = НовСтрока.Количество;
НовСтрока.Цена = Стр.Price;
НовСтрока.Сумма = НовСтрока.Цена*НовСтрока.Количество;
НовСтрока.СтавкаНДС = Перечисления.СтавкиНДС[Стр.VATRate];
НовСтрока.СуммаНДС = Стр.VATAmount;
НовСтрока.Продавец = Настройки.Кассир;
НовСтрока.КлючСвязи = НовСтрока.НомерСтроки;
НовСтрока.ИдентификаторСтроки = Строка(Новый УникальныйИдентификатор);
КонецЦикла;
НовСтрока = Док.ОплатаПлатежнымиКартами.Добавить();
НовСтрока.ЭквайринговыйТерминал = Настройки.ЭквайринговыйТерминал;
НовСтрока.НомерПлатежнойКарты = А.CardNumber;
НовСтрока.Сумма = Док.Товары.Итог("Сумма");
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Оперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
Б = Новый Структура;
Б.Вставить("doc",""+Док.Ссылка.УникальныйИдентификатор());
Возврат Значение_В_JSON(Б);
КонецФункции
Функция ProcessCheck(JSON,Отказ,ОписаниеОшибки)
Попытка
А = JSON_В_Значение(JSON);
ИД = СокрЛП(А.doc);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON";
Возврат Неопределено;
КонецПопытки;
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
Ссылка = Документы.ЧекККМ.ПолучитьСсылку(Новый УникальныйИдентификатор(ИД));
Док = Ссылка.ПолучитьОбъект();
Если Док=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найден документ по ИД: "+Ссылка.УникальныйИдентификатор();
Возврат Неопределено;
КонецЕсли;
Попытка
Док.КассоваяСмена = НайтиКассовуюСмену(Док.КассаККМ);
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Оперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
ОбщиеПараметры = Новый Структура;
ОбщиеПараметры.Вставить("ТипРасчета",Перечисления.ТипыРасчетаДенежнымиСредствами.ПриходДенежныхСредств);
ОбщиеПараметры.Вставить("СистемаНалогообложения",ПараметрыПодключения.СистемаНалогообложения);
ОбщиеПараметры.Вставить("Кассир",СокрЛП(ПараметрыПодключения.Кассир));
ОбщиеПараметры.Вставить("КассирИНН","");
ОбщиеПараметры.Вставить("ОтправительEmail","");
ОбщиеПараметры.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
ОбщиеПараметры.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
ОбщиеПараметры.Вставить("Электронно",Истина);
ОбщиеПараметры.Вставить("Получатель","");
ОбщиеПараметры.Вставить("ПолучательИНН","");
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Товары.НомерСтроки КАК НомерСтроки,
| ИСТИНА КАК ФискальнаяСтрока,
| Товары.Номенклатура.НаименованиеПолное КАК Наименование,
| Товары.Количество,
| Товары.Цена КАК ЦенаСоСкидками,
| Товары.Сумма,
| 0 КАК СуммаСкидок,
| 1 КАК НомерСекции,
| """" КАК СтавкаНДС,
| ЗНАЧЕНИЕ(Перечисление.ПризнакиСпособаРасчета.ПередачаСПолнойОплатой) КАК ПризнакСпособаРасчета,
| ЗНАЧЕНИЕ(Перечисление.ПризнакиПредметаРасчета.Товар) КАК ПризнакПредметаРасчета
|ИЗ
| Документ.ЧекККМ.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
|
|УПОРЯДОЧИТЬ ПО
| НомерСтроки");
Запрос.УстановитьПараметр("Ссылка",Ссылка);
Табл = Запрос.Выполнить().Выгрузить();
ПозицииЧека = Новый Массив;
Для Каждого Стр Из Табл Цикл
П = Новый Структура("ФискальнаяСтрока,Наименование,Количество,ЦенаСоСкидками,Сумма,СуммаСкидок,НомерСекции,СтавкаНДС,ПризнакСпособаРасчета,ПризнакПредметаРасчета,ДанныеАгента,ДанныеПоставщика,ДанныеКодаТоварнойНоменклатуры,КодВидаНоменклатурнойКлассификации,Штрихкод");
ЗаполнитьЗначенияСвойств(П,Стр);
П.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
П.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
П.Вставить("ДанныеКодаТоварнойНоменклатуры",МенеджерОборудованияКлиентСервер.ПараметрыДанныеКодаТоварнойНоменклатуры());
ПозицииЧека.Добавить(П);
КонецЦикла;
ОбщиеПараметры.Вставить("ПозицииЧека",ПозицииЧека);
ТаблицаОплат = Новый Массив;
П = Новый Структура("ТипОплаты,Сумма",Перечисления.ТипыОплатыККТ.Электронно,Док.СуммаДокумента);
ТаблицаОплат.Добавить(П);
ОбщиеПараметры.Вставить("ТаблицаОплат",ТаблицаОплат);
ПараметрыФискализации = Новый Структура("ДанныеЧекаXML,ТипРасчета,СуммаЧека,ОплатаНаличные,ОплатаЭлектронно,ОплатаПостоплата,ОплатаПредоплата,ОплатаВстречноеПредоставление,РевизияИнтерфейса");
ПараметрыФискализации.РевизияИнтерфейса = 3003;
МенеджерОборудованияВызовСервера.СформироватьXMLПакетДляФискализацияЧека(ОбщиеПараметры,ПараметрыФискализации);
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.СформироватьЧек(ПараметрыПодключения.ИДУстройства, ОбщиеПараметры.Электронно, ПараметрыФискализации.ДанныеЧекаXML, ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода ProcessCheck : "+П1;
Возврат Неопределено;
КонецЕсли;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ВыходныеПараметры);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерСмены = Число(Д.Parameters.ShiftNumber);
НомерЧека = Число(Д.Parameters.CheckNumber); //Номер фискального документа (ФД)
ФискальныйПризнак = Д.Parameters.FiscalSign; //Фискальный признак (ФПД)
ДатаЧека = СтрЗаменить(Д.Parameters.DateTime,"T","");
ДатаЧека = СтрЗаменить(ДатаЧека,"-","");
ДатаЧека = СтрЗаменить(ДатаЧека,":","");
ДатаЧека = Дата(ДатаЧека);
//Получим номер ФН
НомерФН = "";
ТаблицаПараметровККТ = "";
АдресСайтаПроверки = "";
Если ПараметрыПодключения.Драйвер.ПолучитьПараметрыККТ(ПараметрыПодключения.ИДУстройства,ТаблицаПараметровККТ)=Истина Тогда
Попытка
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТаблицаПараметровККТ);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерФН = Д.FNSerialNumber;
АдресСайтаПроверки = Д.FNSURL;
Исключение
КонецПопытки;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Попытка
Док.Статус = Перечисления.СтатусыЧековККМ.Пробит;
Док.ОбменДанными.Загрузка = Истина;
Док.Записать(РежимЗаписиДокумента.Запись);
ИдентификаторЗаписи = Строка(Новый УникальныйИдентификатор);
Набор = РегистрыСведений.ФискальныеОперации.СоздатьНаборЗаписей();
Набор.Отбор.ДокументОснование.Установить(Док.Ссылка);
Набор.Отбор.ИдентификаторЗаписи.Установить(ИдентификаторЗаписи);
НовСтрока = Набор.Добавить();
НовСтрока.ДокументОснование = Док.Ссылка;
НовСтрока.ИдентификаторЗаписи = ИдентификаторЗаписи;
НовСтрока.ТипРасчета = Перечисления.ТипыРасчетаДенежнымиСредствами.ПриходДенежныхСредств;
НовСтрока.Организация = Док.Организация;
НовСтрока.ТипДокумента = Перечисления.ТипыФискальныхДокументовККТ.КассовыйЧек;
НовСтрока.ТорговыйОбъект = Док.КассаККМ;
НовСтрока.ДанныеXML = Новый ХранилищеЗначения(ПараметрыФискализации.ДанныеЧекаXML);
НовСтрока.НомерЧекаККМ = НомерЧека;
НовСтрока.ФискальныйПризнак = ФискальныйПризнак;
НовСтрока.ЗаводскойНомерФН = НомерФН;
НовСтрока.АдресСайтаПроверки = АдресСайтаПроверки;
НовСтрока.Сумма = Док.СуммаДокумента;
НовСтрока.ОплатаЭлектронно = Док.СуммаДокумента;
НовСтрока.Дата = ДатаЧека;
НовСтрока.НомерСменыККМ = НомерСмены;
Набор.Записать();
Исключение
КонецПопытки;
Ответ = Новый Структура;
Ответ.Вставить("FNSerialNumber",НомерФН);
Ответ.Вставить("CheckNumber",НомерЧека);
Ответ.Вставить("FiscalSign",ФискальныйПризнак);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция ПодготовитьККТ(Отказ,ОписаниеОшибки)
Настройки = ПолучитьНастройки();
Результат = Новый Структура;
Если ЗначениеЗаполнено(Настройки.КассаККМ)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указана касса ККМ для Интернет-магазина!";
Возврат Результат;
КонецЕсли;
//храним настройки подключения к серверу ККТ в виде Сервер:Порт
//здесь Сервер - имя компьютера, к которому подключен кассовый аппарат. Порт по умолчанию 52111
П = Настройки.СерверККТ;
Если ЗначениеЗаполнено(П)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указаны параметры сервера ККТ";
Возврат Результат;
КонецЕсли;
х = Найти(П,":");
Если х=0 Тогда
Сервер = П;
Порт = 52111;
Иначе
Сервер = Лев(П,х-1);
Попытка
Порт = Число(Сред(П,х+1));
Исключение
Порт = 52111;
КонецПопытки;
КонецЕсли;
Если ПодключитьВнешнююКомпоненту("ОбщийМакет.СерверККТ_HTTP_KKT_Client_3003","ZY5684",ТипВнешнейКомпоненты.Native)<>Истина Тогда
Отказ = Истина;
ОписаниеОшибки = "Ошибка подключения внешней компоненты KKT_Client_3003";
Возврат Результат;
КонецЕсли;
Компонента = Новый("AddIn.ZY5684.MKClient_3003");
Компонента.УстановитьПараметр("Server",Сервер);
Компонента.УстановитьПараметр("Port",Порт);
ИДУстройства = "";
Если Компонента.Подключить(ИДУстройства)<>Истина Тогда
Отказ = Истина;
П1 = "";
Компонента.ПолучитьОшибку(П1);
ОписаниеОшибки = "Ошибка открытия ККТ : "+П1;
Возврат Результат;
КонецЕсли;
Результат.Вставить("КассаККМ",Настройки.КассаККМ);
Результат.Вставить("Кассир",Настройки.Кассир);
Результат.Вставить("СистемаНалогообложения",Настройки.СистемаНалогообложения);
Результат.Вставить("Драйвер",Компонента);
Результат.Вставить("ИДУстройства",ИДУстройства);
Возврат Результат;
КонецФункции
Функция GetCurrentStatus(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+СокрЛП(ПараметрыПодключения.Кассир)+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ПолучитьТекущееСостояние(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода GetCurrentStatus : "+П1;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.СостояниеСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить состояние кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
Б = Новый Структура("ShiftState",П.СостояниеСмены);
Возврат Значение_В_JSON(Б);
КонецФункции
Функция ПолучитьВыходныеПараметрыИзXML(XML)
Результат = Новый Структура("СостояниеСмены,НомерСмены");
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(XML);
ЧтениеXML.ПерейтиКСодержимому();
Если ЧтениеXML.Имя="OutputParameters" И ЧтениеXML.ТипУзла=ТипУзлаXML.НачалоЭлемента Тогда
Если ЧтениеXML.Прочитать() И ЧтениеXML.Имя="Parameters" И ЧтениеXML.ТипУзла=ТипУзлаXML.НачалоЭлемента Тогда
ShiftState = СокрЛП(ЧтениеXML.ЗначениеАтрибута("ShiftState"));
Если ShiftState="1" Тогда
Результат.СостояниеСмены = "Закрыта";
ИначеЕсли ShiftState="2" Тогда
Результат.СостояниеСмены = "Открыта";
ИначеЕсли ShiftState="3" Тогда
Результат.СостояниеСмены = "Истекла";
КонецЕсли;
ShiftNumber = СокрЛП(ЧтениеXML.ЗначениеАтрибута("ShiftNumber"));
Попытка
Результат.НомерСмены = Число(ShiftNumber);
Исключение
КонецПопытки;
КонецЕсли;
КонецЕсли;
Возврат Результат;
КонецФункции
Функция OpenShift(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+СокрЛП(ПараметрыПодключения.Кассир)+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ОткрытьСмену(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода OpenShift : "+П1;
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.НомерСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить номер кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
//Создаем документ КассоваяСмена
Док = Документы.КассоваяСмена.СоздатьДокумент();
Док.Дата = ТекущаяДатаСеанса();
Док.НачалоКассовойСмены = Док.Дата;
Док.КассаККМ = ПараметрыПодключения.КассаККМ;
Док.Организация = Док.КассаККМ.Владелец;
Док.Статус = Перечисления.СтатусыКассовойСмены.Открыта;
Док.НомерСменыККТ = П.НомерСмены;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка записи документа КассоваяСмена : "+ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
Ответ = Новый Структура;
Ответ.Вставить("ShiftNumber",П.НомерСмены);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция CloseShift(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+СокрЛП(ПараметрыПодключения.Кассир)+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ЗакрытьСмену(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода CloseShift : "+П1;
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.НомерСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить номер кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| Док.Ссылка,
| Док.НачалоКассовойСмены КАК НачалоКассовойСмены
|ИЗ
| Документ.КассоваяСмена КАК Док
|ГДЕ
| Док.КассаККМ = &КассаККМ
| И Док.Проведен = ИСТИНА
| И Док.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыКассовойСмены.Открыта)
|
|УПОРЯДОЧИТЬ ПО
| НачалоКассовойСмены УБЫВ");
Запрос.УстановитьПараметр("КассаККМ",ПараметрыПодключения.КассаККМ);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=1 Тогда
Док = Табл[0].Ссылка.ПолучитьОбъект();
Док.ОкончаниеКассовойСмены = ТекущаяДата();
Док.Статус = Перечисления.СтатусыКассовойСмены.Закрыта;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка записи документа КассоваяСмена : "+ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
КонецЕсли;
Ответ = Новый Структура;
Ответ.Вставить("ShiftNumber",П.НомерСмены);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция Googs(JSON,Отказ,ОписаниеОшибки)
Настройки = ПолучитьНастройки();
Если Не ЗначениеЗаполнено(Настройки.ВидЦены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не установлена настройка ""Вид цены""";
Возврат Неопределено;
КонецЕсли;
Если Не ЗначениеЗаполнено(Настройки.Склад) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не установлена настройка ""Склад""";
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос(
"ВЫБРАТЬ
| МАКСИМУМ(Цены.Цена) КАК Цена,
| Цены.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ Цены
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ВидЦены = &ВидЦены) КАК Цены
|
|СГРУППИРОВАТЬ ПО
| Цены.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Рег.Номенклатура КАК Номенклатура,
| Рег.ВНаличииОстаток КАК Количество
|ПОМЕСТИТЬ Остатки
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Рег
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена,
| Остатки.Количество КАК Количество,
| Цены.Номенклатура.Наименование КАК Наименование,
| Цены.Номенклатура.Артикул КАК Артикул
|ИЗ
| Цены КАК Цены
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Остатки КАК Остатки
| ПО Цены.Номенклатура = Остатки.Номенклатура");
Запрос.УстановитьПараметр("Дата",ТекущаяДата());
Запрос.УстановитьПараметр("ВидЦены",Настройки.ВидЦены);
Запрос.УстановитьПараметр("Склад",Настройки.Склад);
Табл = Запрос.Выполнить().Выгрузить();
М = Новый Массив;
Для Каждого Стр Из Табл Цикл
А = Новый Структура;
А.Вставить("Ref_Key", ""+Стр.Номенклатура.УникальныйИдентификатор());
А.Вставить("Article", Стр.Артикул);
А.Вставить("Name", Стр.Наименование);
А.Вставить("Quantity", Стр.Количество);
А.Вставить("Price", Стр.Цена);
М.Добавить(А);
КонецЦикла;
Возврат Значение_В_JSON(М);
КонецФункции
Функция НайтиКассовуюСмену(КассаККМ)
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| КассоваяСмена.Ссылка КАК Ссылка,
| КассоваяСмена.Дата КАК Дата
|ИЗ
| Документ.КассоваяСмена КАК КассоваяСмена
|ГДЕ
| КассоваяСмена.Проведен = Истина
| И КассоваяСмена.Статус = Значение(Перечисление.СтатусыКассовойСмены.Открыта)
| И КассоваяСмена.КассаККМ = &КассаККМ
|
|УПОРЯДОЧИТЬ ПО
| Дата Убыв");
Запрос.УстановитьПараметр("КассаККМ",КассаККМ);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=0 Тогда
Возврат Неопределено;
Иначе
Возврат Табл[0].Ссылка;
КонецЕсли;
КонецФункции
2. Для хранения настроек нужно добавить регистр сведений "СерверККТ_HTTP_Настройки":
- Периодичность: день
- Измерения: нет
- Ресурсы:
- КассаККМ (СправочникСсылка.КассыККМ)
- Кассир (СправочникСсылка.Пользователи)
- СистемаНалогообложения (ПеречислениеСсылка.ТипыСистемНалогообложенияККТ)
- НалогообложениеНДС (ПеречислениеСсылка.ТипыНалогообложенияНДС)
- Склад (СправочникСсылка.Склады)
- ЭквайринговыйТерминал (СправочникСсылка.ЭквайринговыеТерминалы)
- ВидЦены (СправочникСсылка.ВидыЦен)
- СерверККТ (Строка)
Конфигурацию для объединения СерверККТ_ИнтернетМагазин_УТ_10_3.cf можно скачать в публикации Модуль ККТ: онлайн-касса по сети в разделе Файлы (архив "Примеры адаптаций для 1С:8.3 (cfe).zip")
1. Для взаимодействия сайта и 1С нужно добавить http-сервис ИнтернетМагазин:
Модуль http-сервиса:
Функция ВызовМетодаPOST(Запрос)
ИмяМетода = Запрос.ПараметрыURL["ИмяМетода"];
Отказ = Ложь;
ОписаниеОшибки = "";
Попытка
Выполнить("Результат = "+ИмяМетода+"(Запрос.ПолучитьТелоКакСтроку(),Отказ,ОписаниеОшибки);");
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Если Отказ Тогда
Результат = ОписаниеОшибки;
КодСостояния = 201;
Иначе
КодСостояния = 200;
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.УстановитьТелоИзСтроки(Результат, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Ответ.Заголовки["Content-Type"] = "text/html;charset=utf-8";
Возврат Ответ;
КонецФункции
Функция Значение_В_JSON(Значение)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Значение);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Функция JSON_В_Значение(JSON)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(JSON);
Результат = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат Результат;
КонецФункции
Функция CreateCheck(JSON,Отказ,ОписаниеОшибки)
Попытка
А = JSON_В_Значение(JSON);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON";
Возврат Неопределено;
КонецПопытки;
Док = Документы.ЧекККМ.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Док.ВидОперации = Перечисления.ВидыОперацийЧекККМ.Продажа;
Док.КассаККМ = Константы.ИнтернетМагазин_КассаККМ.Получить();
Док.Организация = Док.КассаККМ.Владелец;
Док.Склад = Константы.ИнтернетМагазин_Склад.Получить();
Док.Комментарий = "Интернет-магазин";
Для Каждого Стр Из А Цикл
НовСтрока = Док.Товары.Добавить();
НовСтрока.Номенклатура = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(Стр.Ref_Key));
НовСтрока.Количество = Стр.Quantity;
НовСтрока.Цена = Стр.Price;
НовСтрока.Сумма = НовСтрока.Цена*НовСтрока.Количество;
НовСтрока.ЕдиницаИзмерения = НовСтрока.Номенклатура.ЕдиницаХраненияОстатков;
НовСтрока.Коэффициент = 1;
КонецЦикла;
НовСтрока = Док.Оплата.Добавить();
НовСтрока.ВидОплаты = Константы.ИнтернетМагазин_ВидОплаты.Получить();
НовСтрока.Сумма = Док.Товары.Итог("Сумма");
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Оперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
Б = Новый Структура;
Б.Вставить("doc",""+Док.Ссылка.УникальныйИдентификатор());
Возврат Значение_В_JSON(Б);
КонецФункции
Функция ProcessCheck(JSON,Отказ,ОписаниеОшибки)
Попытка
А = JSON_В_Значение(JSON);
ИД = СокрЛП(А.doc);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON";
Возврат Неопределено;
КонецПопытки;
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
Ссылка = Документы.ЧекККМ.ПолучитьСсылку(Новый УникальныйИдентификатор(ИД));
Док = Ссылка.ПолучитьОбъект();
Если Док=Неопределено Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найден документ по ИД: "+Ссылка.УникальныйИдентификатор();
Возврат Неопределено;
КонецЕсли;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Оперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
ОбщиеПараметры = Новый Структура;
ОбщиеПараметры.Вставить("ТипРасчета",Перечисления.ТипыРасчетаДенежнымиСредствами.ПриходДенежныхСредств);
ОбщиеПараметры.Вставить("СистемаНалогообложения",Константы.ИнтернетМагазин_СистемаНалогообложения.Получить());
ОбщиеПараметры.Вставить("Кассир",Константы.ИнтернетМагазин_Кассир.Получить());
ОбщиеПараметры.Вставить("КассирИНН","");
ОбщиеПараметры.Вставить("ОтправительEmail","");
ОбщиеПараметры.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
ОбщиеПараметры.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
ОбщиеПараметры.Вставить("Электронно",Истина);
ОбщиеПараметры.Вставить("Получатель","");
ОбщиеПараметры.Вставить("ПолучательИНН","");
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Товары.НомерСтроки КАК НомерСтроки,
| ИСТИНА КАК ФискальнаяСтрока,
| Товары.Номенклатура.НаименованиеПолное КАК Наименование,
| Товары.Количество,
| Товары.Цена КАК ЦенаСоСкидками,
| Товары.Сумма,
| 0 КАК СуммаСкидок,
| 1 КАК НомерСекции,
| """" КАК СтавкаНДС,
| ЗНАЧЕНИЕ(Перечисление.ПризнакиСпособаРасчета.ПередачаСПолнойОплатой) КАК ПризнакСпособаРасчета,
| ЗНАЧЕНИЕ(Перечисление.ПризнакиПредметаРасчета.Товар) КАК ПризнакПредметаРасчета
|ИЗ
| Документ.ЧекККМ.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
|
|УПОРЯДОЧИТЬ ПО
| НомерСтроки");
Запрос.УстановитьПараметр("Ссылка",Ссылка);
Табл = Запрос.Выполнить().Выгрузить();
ПозицииЧека = Новый Массив;
Для Каждого Стр Из Табл Цикл
П = Новый Структура("ФискальнаяСтрока,Наименование,Количество,ЦенаСоСкидками,Сумма,СуммаСкидок,НомерСекции,СтавкаНДС,ПризнакСпособаРасчета,ПризнакПредметаРасчета,ДанныеАгента,ДанныеПоставщика,ДанныеКодаТоварнойНоменклатуры,КодВидаНоменклатурнойКлассификации,Штрихкод");
ЗаполнитьЗначенияСвойств(П,Стр);
П.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
П.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
П.Вставить("ДанныеКодаТоварнойНоменклатуры",МенеджерОборудованияКлиентСервер.ПараметрыДанныеКодаТоварнойНоменклатуры());
ПозицииЧека.Добавить(П);
КонецЦикла;
ОбщиеПараметры.Вставить("ПозицииЧека",ПозицииЧека);
ТаблицаОплат = Новый Массив;
П = Новый Структура("ТипОплаты,Сумма",Перечисления.ТипыОплатыККТ.Электронно,Док.СуммаДокумента);
ТаблицаОплат.Добавить(П);
ОбщиеПараметры.Вставить("ТаблицаОплат",ТаблицаОплат);
ПараметрыФискализации = Новый Структура("ДанныеЧекаXML,ТипРасчета,СуммаЧека,ОплатаНаличные,ОплатаЭлектронно,ОплатаПостоплата,ОплатаПредоплата,ОплатаВстречноеПредоставление,РевизияИнтерфейса");
ПараметрыФискализации.РевизияИнтерфейса = 3003;
МенеджерОборудованияВызовСервера.СформироватьXMLПакетДляФискализацияЧека(ОбщиеПараметры,ПараметрыФискализации);
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.СформироватьЧек(ПараметрыПодключения.ИДУстройства, ОбщиеПараметры.Электронно, ПараметрыФискализации.ДанныеЧекаXML, ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода ProcessCheck : "+П1;
Возврат Неопределено;
КонецЕсли;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ВыходныеПараметры);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерСмены = Число(Д.Parameters.ShiftNumber);
НомерЧека = Число(Д.Parameters.CheckNumber); //Номер фискального документа (ФД)
ФискальныйПризнак = Д.Parameters.FiscalSign; //Фискальный признак (ФПД)
//Получим номер ФН
НомерФН = "";
ТаблицаПараметровККТ = "";
Если ПараметрыПодключения.Драйвер.ПолучитьПараметрыККТ(ПараметрыПодключения.ИДУстройства,ТаблицаПараметровККТ)=Истина Тогда
Попытка
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТаблицаПараметровККТ);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерФН = Д.FNSerialNumber;
Исключение
КонецПопытки;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Попытка
Док.ЧекПробитНаККМ = Истина;
Док.НомерЧекаККМ = НомерЧека;
Док.НомерСменыККМ = НомерСмены;
Док.ОбменДанными.Загрузка = Истина;
Док.Записать(РежимЗаписиДокумента.Запись);
Исключение
КонецПопытки;
Ответ = Новый Структура;
Ответ.Вставить("FNSerialNumber",НомерФН);
Ответ.Вставить("CheckNumber",НомерЧека);
Ответ.Вставить("FiscalSign",ФискальныйПризнак);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция ПодготовитьККТ(Отказ,ОписаниеОшибки)
Результат = Новый Структура;
//добавили константу, где указываем ККМ для сайта
КассаККМ = Константы.ИнтернетМагазин_КассаККМ.Получить();
Если ЗначениеЗаполнено(КассаККМ)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указана касса ККМ для Интернет-платежей!";
Возврат Результат;
КонецЕсли;
//добавили константу, где храним настройки подключания к серверу ККТ в виде Сервер:Порт
//здесь Сервер - имя компьютера, к которому подключен кассовый аппарат. Порт по умолчанию 52111
П = Константы.ИнтернетМагазин_СерверККТ.Получить();
Если ЗначениеЗаполнено(П)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указаны параметры сервера ККТ";
Возврат Результат;
КонецЕсли;
х = Найти(П,":");
Если х=0 Тогда
Сервер = П;
Порт = 52111;
Иначе
Сервер = Лев(П,х-1);
Попытка
Порт = Число(Сред(П,х+1));
Исключение
Порт = 52111;
КонецПопытки;
КонецЕсли;
Если ПодключитьВнешнююКомпоненту("ОбщийМакет.KKT_Client_3003","ZY5684",ТипВнешнейКомпоненты.Native)<>Истина Тогда
Отказ = Истина;
ОписаниеОшибки = "Ошибка подключения внешней компоненты KKT_Client_3003";
Возврат Результат;
КонецЕсли;
Компонента = Новый("AddIn.ZY5684.MKClient_3003");
Компонента.УстановитьПараметр("Server",Сервер);
Компонента.УстановитьПараметр("Port",Порт);
ИДУстройства = "";
Если Компонента.Подключить(ИДУстройства)<>Истина Тогда
Отказ = Истина;
П1 = "";
Компонента.ПолучитьОшибку(П1);
ОписаниеОшибки = "Ошибка открытия ККТ : "+П1;
Возврат Результат;
КонецЕсли;
Результат.Вставить("КассаККМ",КассаККМ);
Результат.Вставить("Драйвер",Компонента);
Результат.Вставить("ИДУстройства",ИДУстройства);
Возврат Результат;
КонецФункции
Функция GetCurrentStatus(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+Константы.ИнтернетМагазин_Кассир.Получить()+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ПолучитьТекущееСостояние(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода GetCurrentStatus : "+П1;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.СостояниеСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить состояние кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
Б = Новый Структура("ShiftState",П.СостояниеСмены);
Возврат Значение_В_JSON(Б);
КонецФункции
Функция ПолучитьВыходныеПараметрыИзXML(XML)
Результат = Новый Структура("СостояниеСмены,НомерСмены");
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(XML);
ЧтениеXML.ПерейтиКСодержимому();
Если ЧтениеXML.Имя="OutputParameters" И ЧтениеXML.ТипУзла=ТипУзлаXML.НачалоЭлемента Тогда
Если ЧтениеXML.Прочитать() И ЧтениеXML.Имя="Parameters" И ЧтениеXML.ТипУзла=ТипУзлаXML.НачалоЭлемента Тогда
ShiftState = СокрЛП(ЧтениеXML.ЗначениеАтрибута("ShiftState"));
Если ShiftState="1" Тогда
Результат.СостояниеСмены = "Закрыта";
ИначеЕсли ShiftState="2" Тогда
Результат.СостояниеСмены = "Открыта";
ИначеЕсли ShiftState="3" Тогда
Результат.СостояниеСмены = "Истекла";
КонецЕсли;
ShiftNumber = СокрЛП(ЧтениеXML.ЗначениеАтрибута("ShiftNumber"));
Попытка
Результат.НомерСмены = Число(ShiftNumber);
Исключение
КонецПопытки;
КонецЕсли;
КонецЕсли;
Возврат Результат;
КонецФункции
Функция OpenShift(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+Константы.ИнтернетМагазин_Кассир.Получить()+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ОткрытьСмену(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода OpenShift : "+П1;
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.НомерСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить номер кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
//Создаем документ КассоваяСмена
Док = Документы.КассоваяСмена.СоздатьДокумент();
Док.Дата = ТекущаяДатаСеанса();
Док.НачалоКассовойСмены = Док.Дата;
Док.КассаККМ = ПараметрыПодключения.КассаККМ;
Док.Организация = Док.КассаККМ.Владелец;
Док.Статус = Перечисления.СтатусыКассовойСмены.Открыта;
Док.НомерСменыККТ = П.НомерСмены;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка записи документа КассоваяСмена : "+ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
Ответ = Новый Структура;
Ответ.Вставить("ShiftNumber",П.НомерСмены);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция CloseShift(JSON,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат Неопределено;
КонецЕсли;
ВходныеПараметры = "<?xml version=""1.0"" encoding=""UTF-8""?><InputParameters><Parameters CashierName="""+Константы.ИнтернетМагазин_Кассир.Получить()+"""/></InputParameters>";
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.ЗакрытьСмену(ПараметрыПодключения.ИДУстройства,ВходныеПараметры,ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода CloseShift : "+П1;
Возврат Неопределено;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
П = ПолучитьВыходныеПараметрыИзXML(ВыходныеПараметры);
Если Не ЗначениеЗаполнено(П.НомерСмены) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не удалось определить номер кассовой смены: "+ВыходныеПараметры;
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| Док.Ссылка,
| Док.НачалоКассовойСмены КАК НачалоКассовойСмены
|ИЗ
| Документ.КассоваяСмена КАК Док
|ГДЕ
| Док.КассаККМ = &КассаККМ
| И Док.Проведен = ИСТИНА
| И Док.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыКассовойСмены.Открыта)
|
|УПОРЯДОЧИТЬ ПО
| НачалоКассовойСмены УБЫВ");
Запрос.УстановитьПараметр("КассаККМ",ПараметрыПодключения.КассаККМ);
Табл = Запрос.Выполнить().Выгрузить();
Если Табл.Количество()=1 Тогда
Док = Табл[0].Ссылка.ПолучитьОбъект();
Док.ОкончаниеКассовойСмены = ТекущаяДата();
Док.Статус = Перечисления.СтатусыКассовойСмены.Закрыта;
Попытка
Док.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Отказ = Истина;
ОписаниеОшибки = "Ошибка записи документа КассоваяСмена : "+ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
КонецЕсли;
Ответ = Новый Структура;
Ответ.Вставить("ShiftNumber",П.НомерСмены);
Возврат Значение_В_JSON(Ответ);
КонецФункции
Функция Googs(JSON,Отказ,ОписаниеОшибки)
ТипЦен = Константы.ИнтернетМагазин_ТипЦен.Получить(); //Добавили константу, где указываем тип цен для сайта
Склад = Константы.ИнтернетМагазин_Склад.Получить(); //Добавили константу, где указываем склад для сайта
Если Не ЗначениеЗаполнено(ТипЦен) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не установлена константа ИнтернетМагазин_ТипЦен";
Возврат Неопределено;
КонецЕсли;
Если Не ЗначениеЗаполнено(Склад) Тогда
Отказ = Истина;
ОписаниеОшибки = "Не установлена константа ИнтернетМагазин_Склад";
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Табл.Номенклатура КАК Номенклатура,
| СУММА(Табл.Количество) КАК Количество,
| МАКСИМУМ(Табл.Цена) КАК Цена
|ПОМЕСТИТЬ Данные
|ИЗ
| (ВЫБРАТЬ
| Рег.Номенклатура КАК Номенклатура,
| Рег.КоличествоОстаток КАК Количество,
| 0 КАК Цена
| ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Рег
|
| ОБЪЕДИНИТЬ ВСЕ
|
| ВЫБРАТЬ
| Рег.Номенклатура,
| -Рег.КоличествоОстаток,
| 0
| ИЗ
| РегистрНакопления.ТоварыВРезервеНаСкладах.Остатки(, Склад = &Склад) КАК Рег
| ГДЕ
| Рег.КоличествоОстаток > 0
|
| ОБЪЕДИНИТЬ ВСЕ
|
| ВЫБРАТЬ
| Рег.Номенклатура,
| 0,
| Рег.Цена
| ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ТипЦен = &ТипЦен) КАК Рег) КАК Табл
|
|СГРУППИРОВАТЬ ПО
| Табл.Номенклатура
|
|ИМЕЮЩИЕ
| СУММА(Табл.Количество) > 0 И
| МАКСИМУМ(Табл.Цена) > 0
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Спр.Ссылка,
| Спр.Артикул,
| Спр.Наименование,
| Данные.Количество,
| Данные.Цена
|ИЗ
| Справочник.Номенклатура КАК Спр
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Данные КАК Данные
| ПО Спр.Ссылка = Данные.Номенклатура
|ГДЕ
| Спр.ПометкаУдаления = ЛОЖЬ");
Запрос.УстановитьПараметр("Дата",ТекущаяДата());
Запрос.УстановитьПараметр("ТипЦен",ТипЦен);
Запрос.УстановитьПараметр("Склад",Склад);
Табл = Запрос.Выполнить().Выгрузить();
М = Новый Массив;
Для Каждого Стр Из Табл Цикл
А = Новый Структура;
А.Вставить("Ref_Key", ""+Стр.Ссылка.УникальныйИдентификатор());
А.Вставить("Article", Стр.Артикул);
А.Вставить("Name", Стр.Наименование);
А.Вставить("Quantity", Стр.Количество);
А.Вставить("Price", Стр.Цена);
М.Добавить(А);
КонецЦикла;
Возврат Значение_В_JSON(М);
КонецФункции
2. Для хранения настроек нужно добавить константы:
- ИнтернетМагазин_ТипЦен (СправочникСсылка.ТипыЦенНоменклатуры)
- ИнтернетМагазин_КассаККМ (СправочникСсылка.КассыККМ)
- ИнтернетМагазин_Склад (СправочникСсылка.Склады)
- ИнтернетМагазин_ВидОплаты (СправочникСсылка.ВидыОплатЧекаККМ)
- ИнтернетМагазин_СерверККТ (Строка)
- ИнтернетМагазин_Кассир (Строка)
- ИнтернетМагазин_СистемаНалогообложения (ПеречислениеСсылка.ТипыСистемНалогообложенияККТ)
3. Работа с ККТ внутри http-сервиса осуществляется с помощью драйвера "Клиент ККТ". Так как http-сервис работает в контексте сервера 1С:Предприятие, то этот драйвер (файл KKT_Client_3003.zip) нужно поместить в общий макет типа "Двоичные данные" (см. Подготовка внешних компонент для загрузки в конфигурацию на ИТС).
Краткое описание методов
Для подключения драйвера используется вспомогательная функция ПодготовитьККТ(). Её задача: подключить драйвер ККТ (из общего макета), установить параметры драйвера и открыть устройство.
Для получение статуса кассовой смены используется функция GetCurrentStatus().
Для открытия и закрытия кассовых смен используются функции OpenShift() и CloseShift().
Перед пробитием чека создаётся документ ЧекККМ с помощью функции CreateCheck(). В функцию передается массив товаров, возвращает функция идентификатор документа.
Для пробития чека используется функция ProcessCheck(). Обратите внимание на параметр "Электронно = Истина", т.е. бумажный чек не распечатается, но в ОФД чек уйдет.
Если чек успешно пробит, то функция ProcessCheck() возвращает номер фискального документа, фискальный признак и номер фискального накопителя. Эти параметры можно использовать для составления ссылки на электронный чек. В нашем случае, для "Платформа ОФД", ссылка выглядит следующим образом:
https://lk.platformaofd.ru/web/noauth/cheque?fn=9251433377086452&fp=4493881571&i=7803
где fn - номер фискального накопителя
fp - фискальный признак
i - номер фискального документа
Как это выглядит для посетителя сайта
1. Посетитель сайта набрал товар в корзину. На сайте сделана кнопка "Оплатить":
2. Посетитель переадресуется на платёжную форму, где вводит данные своей банковской карты:
3. Выполняется запрос авторизации в банк-эквайер и проходит процедура проверки:
4. Посетителю отправляется ссылка на электронный чек:
В этом примере сайт будет выполнять запрос к "Серверу ККТ" для пробития чека на кассе. Предполагается, что организация имеет "белый" IP-адрес, на который сайт сможет отправлять HTTP-запрос. При этом сайт может работать на любой платформе, в том числе и на "1С-Битрикс". Так как это не типовая схема работы, то потребуется доработка сайта.
В этом примере сайт и 1С будут работать кассой с независимо друг от друга:
Для передачи команды с сайта на "Сервер ККТ" нужно использовать POST-запрос по адресу http://вашIP/addin
В качестве тела запроса нужно передать XML-пакет следующего образца:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="DeviceTest" Function="true" InterfaceRevision="3003">
<Parameter1 xsi:type="string"></Parameter1>
<Parameter2 xsi:type="string"></Parameter2>
</Request>
Здесь:
Атрибут Method указывет имя метода драйвера в соответствии со стандартом «Требования к разработке драйверов подключаемого оборудования».
Атрибут Function указывает, является ли метод функцией, и, соответственно, нужно ли возвращать значение.
Элементы Parameter1, Parameter2 и т.д. служат для передачи параметров в вызываемый метод. Допустимые типы: string, decimal, boolean, datetime, date, time. Если тип не указан, то параметр считается равным Неопределено. Количество и типы передаваемых параметров для каждого метода указаны в требованиях к разработке драйверов.
Атрибут InterfaceRevision указывает ревизию интерфейса (см. стандарт «Требования к разработке драйверов подключаемого оборудования»).
Если в качестве параметра нужно передать XML-пакет, то нужно использовать следующий алгоритм:
- XML-пакет преобразуется в строку с кодировкой UTF-8.
- В этой строке символы & " ' < > должны быть соответственно заменены на & " ' < > (стандартное экранирование для XML).
- Полученную строку передать в виде параметра с типом string.
В качестве ответа «Сервер ККТ» вернет XML-пакет следующего образца:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter1 xsi:type="string"></Parameter1>
<Parameter2 xsi:type="string"></Parameter2>
<Result xsi:type="string"></Result>
</Response>
Элемент ParameterN может отсутствовать, если его значение не поменялось.
Элемент Result будет присутствовать только для функций.
Для проверки связи с «Сервером ККТ» можно использовать GET- или POST-запрос на адрес http://вашIP/ping. В ответ сервер должен вернуть строку «pong».
1. Нужно проверить доступность сервера с помощью post-запроса /ping. Если сервер вернет код состояния 200, значит можно продолжать дальше.
2. Нужно открыть устройство с помощью метода драйвера Open(). Для этого выполняем post-запрос /addin, в качестве тела запроса передаем строку:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="Open" Function="true">
<Parameter1 xsi:type="string"></Parameter1>
</Request>
Параметр 1 заполнять не нужно, это выходной параметр. Сервер вернет в этом параметре так называемый «идентификатор подключенного экземпляра устройства», сокращенно ИДУстройства.
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter1 xsi:type="string">KKT_3002#1</Parameter1>
<Result xsi:type="boolean">true</Result>
</Response>
Здесь нужно удостовериться, что Result=true, т.е. метод успешно выполнен. Если Result=false, значит устройство не было открыто, причину можно узнать с помощью вызова метода GetLastError() (см. ниже).
В нашем случае устройство успешно открыто, ИДУстройства (параметр 1) равняется «KKT_3002#1». Это значение нужно запомнить для дальнейшего использования.
3. Далее проверяем состояние кассовой смены с помощью метода GetCurrentStatus(). При вызове нужно передать три параметра:
Параметр 1: ИДУстройства – значение, полученное ранее методом Open()
Параметр 2: XML-пакет, описывающий входные данные. Образец:
<?xml version="1.0" encoding="UTF-8"?>
<InputParameters>
<Parameters CashierName="Иванов И.П." CashierINN="324562345234"/>
</InputParameters>
Параметр 3: пустая строка, это выходной параметр.
Тело запроса для GetCurrentStatus:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="GetCurrentStatus" Function="true">
<Parameter1 xsi:type="string">KKT_3002#1</Parameter1>
<Parameter2 xsi:type="string"><?xml version="1.0" encoding="UTF-8"?><InputParameters><Parameters CashierName="Иванов И.П." CashierINN="324562345234"/></InputParameters></Parameter2>
<Parameter3 xsi:type="string"></Parameter3>
</Request>
Здесь Parameter2 – это экранированный XML-пакет с входными данными.
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter3 xsi:type="string"><?xml version="1.0" encoding="UTF-8"?> <OutputParameters><Parameters ShiftNumber="5" CheckNumber="2" ShiftState="1" CashBalance="3242.00" BacklogDocumentsCounter="" BacklogDocumentFirstNumber="" BacklogDocumentFirstDateTime=""/></OutputParameters></Parameter3>
<Result xsi:type="boolean">true</Result>
</Response>
Из ответа следует, что метод выполнился успешно (Result=true). Параметр 1 и параметр 2 не изменились (отсутствуют в ответе), параметр 3 – это xml-пакет с выходными данными. После разэкранирования выходные данные выглядят так:
<?xml version="1.0" encoding="UTF-8"?>
<OutputParameters>
<Parameters ShiftNumber="5" CheckNumber="2" ShiftState="1" CashBalance="3242.00" BacklogDocumentsCounter="" BacklogDocumentFirstNumber="" BacklogDocumentFirstDateTime=""/>
</OutputParameters>
Здесь нас интересует ShiftState – состояние смены (1 – закрыта; 2 – открыта; 3 – истекла).
4. Если смена закрыта, то нужно открыть смену с помощью метода OpenShift(). При вызове нужно передать три параметра, те же самые, что и для GetCurrentStatus().
Тело запроса для OpenShift:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="OpenShift" Function="true">
<Parameter1 xsi:type="string">KKT_3002#1</Parameter1>
<Parameter2 xsi:type="string"><?xml version="1.0" encoding="UTF-8"?><InputParameters><Parameters CashierName="Иванов И.П." CashierINN="324562345234"/></InputParameters></Parameter2>
<Parameter3 xsi:type="string"></Parameter3>
</Request>
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter3 xsi:type="string"><?xml version="1.0" encoding="UTF-8"?> <OutputParameters><Parameters ShiftNumber="7" DateTime="2020-05-20T15:51:04" ShiftState="2" FNError="false" FNOverflow="false" FNFail="false"/></OutputParameters></Parameter3>
<Result xsi:type="boolean">true</Result>
</Response>
Из ответа следует, что метод выполнился успешно (Result=true). Выходные данные (параметр 3) после разэкранирования выглядят так:
<?xml version="1.0" encoding="UTF-8"?>
<OutputParameters>
<Parameters ShiftNumber="7" DateTime="2020-05-20T15:51:04" ShiftState="2" FNError="false" FNOverflow="false" FNFail="false"/>
</OutputParameters>
5. После открытия смены можно пробивать чеки. Делается это с помощью метода ProcessCheck(). Параметры метода:
Параметр 1: ИДУстройства – значение, полученное ранее методом Open().
Параметр 2: Электронно (булево) – если true, то чек будет формироваться только в электронном виде, бумажного чека не будет.
Параметр 3: XML-пакет, содержащий данные чека.
Параметр 4: пустая строка, это выходной параметр.
XML-пакет с данными чека выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<CheckPackage>
<Parameters OperationType="1" TaxationSystem="2" CashierName="Иванов И.П. " />
<Positions>
<FiscalString Name="Товар 1" Quantity="2" PriceWithDiscount="5084.64" AmountWithDiscount="10169.28" DiscountAmount="0" ExciseAmount="0" VATRate="20" VATAmount="1694.88" PaymentMethod="4" CalculationSubject="1"/>
</Positions>
<Payments Cash="0" ElectronicPayment="10169.28"/>
</CheckPackage>
Подробное описание этого XML-пакета можно найти на сайте ИТС.
Тело запроса для ProcessCheck:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="ProcessCheck" Function="true">
<Parameter1 xsi:type="string">KKT_3002#1</Parameter1>
<Parameter2 xsi:type="boolean">true</Parameter2>
<Parameter3 xsi:type="string"><?xml version="1.0" encoding="UTF-8"?><CheckPackage> <Parameters OperationType="1" TaxationSystem="2" CashierName="Иванов И.П. " /> <Positions> <FiscalString Name="Товар 1" Quantity="2" PriceWithDiscount="5084.64" AmountWithDiscount="10169.28" DiscountAmount="0" ExciseAmount="0" VATRate="20" VATAmount="1694.88" PaymentMethod="4" CalculationSubject="1"/> </Positions> <Payments Cash="0" ElectronicPayment="10169.28"/> </CheckPackage></Parameter3>
<Parameter4 xsi:type="string"></Parameter4>
</Request>
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter4 xsi:type="string"><?xml version="1.0" encoding="utf-8"?> <DocumentOutputParameters><Parameters ShiftNumber="9" CheckNumber="4" ShiftClosingCheckNumber="2" AddressSiteInspections="" FiscalSign="1950921" DateTime="2020-05-20T16:34:18"/></DocumentOutputParameters></Parameter4>
<Result xsi:type="boolean">true</Result>
</Response>
Из ответа следует, что метод выполнился успешно (Result=true). Выходные данные (параметр 4) после разэкранирования выглядят так:
<?xml version="1.0" encoding="utf-8"?>
<DocumentOutputParameters>
<Parameters ShiftNumber="7" CheckNumber="4" ShiftClosingCheckNumber="2" AddressSiteInspections="" FiscalSign="1950921" DateTime="2020-05-20T16:34:18"/>
</DocumentOutputParameters>
6. После печати чека нужно закрыть устройство с помощью метода Close(). У этого метода один параметр – ИДУстройства.
Тело запроса для Close:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="Close" Function="true">
<Parameter1 xsi:type="string">KKT_3002#1</Parameter1>
</Request>
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Result xsi:type="boolean">true</Result>
</Response>
7. Не забудьте в конце дня закрыть смену, последовательно выполнив методы Open(), CloseShift(), Close(). Параметры у метода CloseShift() точно такие же, как и у OpenShift().
8. Если какой-либо метод не выполнился (вернул Result=false), то описание ошибки можно получить с помощью метода GetLastError. Этот метод имеет один выходной параметр.
Тело запроса для GetLastError:
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Method="GetLastError" Function="true">
<Parameter1 xsi:type="string"></Parameter1>
</Request>
Ответ сервера:
<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Parameter1 xsi:type="string">Смена уже открыта!</Parameter1>
<Result xsi:type="decimal">1</Result>
</Response>
В данном примере драйвер сообщил, что смена уже открыта и вернул код ошибки=1
Для того, чтобы включить http-сервер, нужно установить галку «Использовать http-сервер» в настройках:
Для проверки http-сервера можно использовать GET-запрос /, например http://192.168.0.61:8080/
где 192.168.0.61 – адрес компьютера, на котором запущен «Сервер ККТ»
Если организация не имеет своего IP-адреса, то можно использовать ту же схему работы, что и для "1С-Битрикс" (см. Пример 1). Если у вас не "1С-Битрикс", то потребуется доработка сайта, чтобы обрабатывать запросы от "Сервера ККТ".
Каждую минуту "Сервер ККТ" отправляет HTTP POST-запрос на сайт (URL указывается в настройках) для получения списка чеков. Тело запроса:
{
"status":"ok",
"api_version":2,
"mode":"dispatcher",
"kkm":[
{
"zn":"6756756",
"status":"ok"
}
]
}
здесь zn - идентификатор ККТ. Сайт может использовать zn для того, чтобы отличать одну ККТ от другой (если их несколько).
В ответ сайт должен вернуть ответ:
{
"print":[
{
"type":"sell",
"timestamp":"16.03.2021 12:42:52",
"external_id":"check|mysite.ru|19",
"taxationType":"osn",
"zn":"6756756",
"clientInfo":{
"emailOrPhone":"my@mail.com"
},
"payments":[
{
"type":"electronically",
"sum":0.84
}
],
"items":[
{
"name":"\u0428\u0430\u0439\u0431\u0430 \u0433\u0440\u043e\u0432\u0435\u0440 \u041c6 IEK",
"price":0.84,
"quantity":1,
"amount":0.84,
"paymentMethod":"fullPayment",
"paymentObject":"commodity",
"tax":{
"type":"vat20"
},
"nomenclatureCode":"RE0EMHcZcDZpVDNXTyU7QUI+OCAg"
}
],
"total":0.84
}
]
}
Поля, требующие пояснения:
- print - массив чеков для печати
- type - тип операции ("sell" - приход денежных средств, "sellReturn" - возврат прихода денежных средств)
- external_id - идентификатор чека
- taxationType - система налогообложения ("osn" - общая, "usnIncome" - УСН (доход), "usnIncomeOutcome" УСН (доход-расход), "envd" - ЕНВД, "esn" - ЕСХН, "patent" - патент)
- payments - массив оплат ("cash" - наличные, "electronically" - безналичные, "prepaid" - зачет предоплаты, "credit" - оплата в кредит)
- items - строки чека
- paymentMethod - способ расчета ("fullPrepayment" - предоплата полная, "prepayment" - предоплата частичная, "advance" - аванс, "fullPayment'" - полный расчет, "credit" - передача в кредит, "creditPayment" - оплата кредита)
- paymentObject - предмет расчета ("commodity" - товар, "excise" - подакцизный товар, "job" - работа, "service'" - услуга, "payment" - платеж).
- tax - ставка НДС ("none" - без НДС, "vat20", "vat120" - расчётная, "vat18", "vat118" - расчётная, "vat10", "vat110" - расчётная, "vat0" - 0%)
- nomenclatureCode (необязательное) - данные маркировки (значение тэга 1162) в формате base64
Если на сайте нет чеков для печати, сайт возвращает ответ "{}"
После успешной печати чека "Сервер ККТ" отправляет HTTP POST-запрос на сайт для установки статуса чека "Напечатан":
{
"status":"ok",
"api_version":2,
"mode":"dispatcher",
"kkm":[
{
"zn":"6756756",
"status":"ok",
"printed":[
{
"uuid":"check|mysite.ru|19",
"code":0,
"qr":"t=20210319T163155&s=0.84&fn=9865167810&i=12&fp=3739943&n=1"
}
]
}
]
}
здесь:
printed - список обработанных чеков
uuid - идентификатор чека
code - код ошибки (0 - нет ошибок)
qr - данные qr-кода (t - дата/время, s - сумма, fn - номер фискального накопителя, fp - фискальный признак, i - номер чека, n - тип операции). Используя данные qr-кода можно сгенерировать ссылку на чек на сайте ОФД.
В ответ на этот запрос сайт должен вернуть подтверждение:
{
"ack": ["check|mysite.ru|19"]
}
В этом примере 1С периодически выполняет запрос к сайту платформе на "1С-Битрикс" для получения списка чеков. Благодяря тому, что компонента "Клиент ККТ" может работать в контексте сервера "1С:Предприятие", то такой запрос можно выполнять в регламентном задании.
Настройку сайта нужно выполнить как в примере №1. В настройках KKT_Server_Setting.exe галку "Использовать CashboxBitrixV2" нужно снять.
Пример для конфигурации "1С: Управление торговлей 10.3":
Процедура ОбработчикРегламентногоЗадания() Экспорт
Отказ = Ложь;
ОписаниеОшибки = "";
НапечататьЧеки(Константы.ИнтернетМагазин_ПечатьЧеков_URL.Получить(),Константы.ИнтернетМагазин_ПечатьЧеков_ИдКассы.Получить(),Отказ,ОписаниеОшибки);
Если Отказ Тогда
//обработка ошибок
КонецЕсли;
КонецПроцедуры
Процедура НапечататьЧеки(URL,ИдКассы,Отказ,ОписаниеОшибки)
HTTP = СоздатьСоединение(URL,Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат;
КонецЕсли;
HTTP.Вставить("zn",СокрЛП(ИдКассы));
НапечатанныеЧеки = ПолучитьНапечатанныеЧеки();
лОтказ = Ложь;
лОписаниеОшибки = "";
УстановитьСтатусыЧеков(HTTP,НапечатанныеЧеки,лОтказ,лОписаниеОшибки);
НапечатанныеЧеки = ПолучитьНапечатанныеЧеки();
kkm_1 = Новый Структура;
kkm_1.Вставить("zn",HTTP.zn);
kkm_1.Вставить("status","ok");
kkm = Новый Массив;
kkm.Добавить(kkm_1);
data = Новый Структура;
data.Вставить("kkm",kkm);
data.Вставить("status","ok");
data.Вставить("api_version",2);
data.Вставить("mode","dispatcher");
data.Вставить("api_version",2);
HTTP.Запрос.УстановитьТелоИзСтроки(Значение_В_JSON(data),КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать);
Попытка
Ответ = HTTP.Соединение.ОтправитьДляОбработки(HTTP.Запрос);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
Тело = Ответ.ПолучитьТелоКакСтроку();
Если Ответ.КодСостояния<>200 Тогда
Отказ = Истина;
ОписаниеОшибки = Тело;
Возврат;
КонецЕсли;
Если Тело="{}" Тогда
Возврат;
КонецЕсли;
Данные = JSON_В_Значение(Тело);
Если ТипЗнч(Данные)<>Тип("Структура") Тогда
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON : "+Тело;
Возврат;
КонецЕсли;
Если Данные.Свойство("print")=Ложь Тогда
Возврат;
КонецЕсли;
ОписаниеОшибки2 = "";
Для Каждого Стр Из Данные.print Цикл
лОтказ = Ложь;
лОписаниеОшибки = "";
Если Стр.type="sell" ИЛИ Стр.type="sellReturn" Тогда
Если НапечатанныеЧеки.Найти(Стр.external_id,"uuid")<>Неопределено Тогда
Продолжить;
КонецЕсли;
ПробитьЧек(Стр,лОтказ,лОписаниеОшибки);
Если лОтказ Тогда
ОписаниеОшибки2 = ОписаниеОшибки2 + лОписаниеОшибки + Символы.ПС;
Продолжить;
КонецЕсли;
СоздатьЧек(Стр,лОтказ,лОписаниеОшибки);
//Запомним этот чек, чтобы не напечатать повторно
НовСтрока = НапечатанныеЧеки.Добавить();
НовСтрока.uuid = Стр.external_id;
НовСтрока.qr = Стр.qr;
Набор = РегистрыСведений.ИнтернетМагазин_НапечатанныеЧеки.СоздатьНаборЗаписей();
Набор.Отбор.uuid.Установить(Стр.external_id);
НовСтрока = Набор.Добавить();
НовСтрока.uuid = Стр.external_id;
НовСтрока.qr = Стр.qr;
Попытка
Набор.Записать();
Исключение
ОписаниеОшибки2 = ОписаниеОшибки2 + ОписаниеОшибки() + Символы.ПС;
КонецПопытки;
УстановитьСтатусыЧеков(HTTP,НапечатанныеЧеки,лОтказ,лОписаниеОшибки);
Если лОтказ Тогда
ОписаниеОшибки2 = ОписаниеОшибки2 + лОписаниеОшибки + Символы.ПС;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если ОписаниеОшибки2<>"" Тогда
ОписаниеОшибки = ОписаниеОшибки2;
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Функция Значение_В_JSON(Структура)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(ЗаписьJSON, Структура);
СтрJSON = ЗаписьJSON.Закрыть();
Возврат СтрJSON;
КонецФункции
Функция JSON_В_Значение(Значение)
Попытка
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Значение);
Результат = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Исключение
Результат = Неопределено;
КонецПопытки;
Возврат Результат;
КонецФункции
Функция ПодготовитьККТ(Отказ,ОписаниеОшибки)
Результат = Новый Структура;
//добавили константу, где указываем ККМ для сайта
КассаККМ = Константы.ИнтернетМагазин_КассаККМ.Получить();
Если ЗначениеЗаполнено(КассаККМ)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указана касса ККМ для Интернет-платежей!";
Возврат Результат;
КонецЕсли;
//добавили константу, где храним настройки подключания к серверу ККТ в виде Сервер:Порт
//здесь Сервер - имя компьютера, к которому подключен кассовый аппарат. Порт по умолчанию 52111
П = Константы.ИнтернетМагазин_СерверККТ.Получить();
Если ЗначениеЗаполнено(П)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не указаны параметры сервера ККТ";
Возврат Результат;
КонецЕсли;
х = Найти(П,":");
Если х=0 Тогда
Сервер = П;
Порт = 52111;
Иначе
Сервер = Лев(П,х-1);
Попытка
Порт = Число(Сред(П,х+1));
Исключение
Порт = 52111;
КонецПопытки;
КонецЕсли;
//файл компоненты KKT_Client_3003.zip должен быть загружен в общий макет
Если ПодключитьВнешнююКомпоненту("ОбщийМакет.KKT_Client_3003","ZY5684",ТипВнешнейКомпоненты.Native)<>Истина Тогда
Отказ = Истина;
ОписаниеОшибки = "Ошибка подключения внешней компоненты KKT_Client_3003";
Возврат Результат;
КонецЕсли;
Компонента = Новый("AddIn.ZY5684.MKClient_3003");
Компонента.УстановитьПараметр("Server",Сервер);
Компонента.УстановитьПараметр("Port",Порт);
ИДУстройства = "";
Если Компонента.Подключить(ИДУстройства)<>Истина Тогда
Отказ = Истина;
П1 = "";
Компонента.ПолучитьОшибку(П1);
ОписаниеОшибки = "Ошибка открытия ККТ : "+П1;
Возврат Результат;
КонецЕсли;
Результат.Вставить("КассаККМ",КассаККМ);
Результат.Вставить("Драйвер",Компонента);
Результат.Вставить("ИДУстройства",ИДУстройства);
Возврат Результат;
КонецФункции
Функция НайтиТовары(items,СоздаватьНенайденные,Отказ,ОписаниеОшибки)
Табл = Новый ТаблицаЗначений;
Табл.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка",, Новый КвалификаторыСтроки(1000)));
Для Каждого Стр Из items Цикл
Табл.Добавить().Наименование = Стр.name;
КонецЦикла;
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Табл.Наименование КАК Наименование
|ПОМЕСТИТЬ Табл
|ИЗ
| &Табл КАК Табл
|
|ИНДЕКСИРОВАТЬ ПО
| Наименование
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.ЕдиницаХраненияОстатков,
| Табл.Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Табл КАК Табл
| ПО Номенклатура.Наименование = Табл.Наименование
|ГДЕ
| Номенклатура.ЭтоГруппа = ЛОЖЬ
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.ЕдиницаХраненияОстатков,
| Табл.Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Табл КАК Табл
| ПО ((ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(1000))) = Табл.Наименование)
|ГДЕ
| Номенклатура.ЭтоГруппа = ЛОЖЬ");
Запрос.УстановитьПараметр("Табл",Табл);
Табл = Запрос.Выполнить().Выгрузить();
Для Каждого Стр Из items Цикл
х = Табл.Найти(Стр.name,"Наименование");
Если х=Неопределено И СоздаватьНенайденные=Истина Тогда
Шт = Справочники.КлассификаторЕдиницИзмерения.НайтиПоНаименованию("шт",Истина);
Если ЗначениеЗаполнено(Шт)=Ложь Тогда
Отказ = Истина;
ОписаниеОшибки = "Не найдена единица Шт";
Возврат Неопределено;
КонецЕсли;
Ссылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор);
Единица = Справочники.ЕдиницыИзмерения.СоздатьЭлемент();
Единица.Владелец = Ссылка;
Единица.ЕдиницаПоКлассификатору = Шт;
Единица.Коэффициент = 1;
Единица.Наименование = "шт";
Единица.ОбменДанными.Загрузка = Истина;
Попытка
Единица.Записать();
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
Спр = Справочники.Номенклатура.СоздатьЭлемент();
Спр.УстановитьСсылкуНового(Ссылка);
Спр.Наименование = Стр.name;
Спр.НаименованиеПолное = Спр.Наименование;
Спр.БазоваяЕдиницаИзмерения = Шт;
Спр.ЕдиницаХраненияОстатков = Единица.Ссылка;
Спр.ЕдиницаДляОтчетов = Единица.Ссылка;
Спр.ВидНоменклатуры = Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Товар",Истина);
Спр.ОбменДанными.Загрузка = Истина;
Попытка
Спр.Записать();
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат Неопределено;
КонецПопытки;
х = Табл.Добавить();
х.Ссылка = Спр.Ссылка;
х.Наименование = Спр.Наименование;
х.ЕдиницаХраненияОстатков = Единица.Ссылка;
КонецЕсли;
КонецЦикла;
Возврат Табл;
КонецФункции
Процедура СоздатьЧек(ДанныеЧека,Отказ,ОписаниеОшибки)
лОтказ = Ложь;
лОписаниеОшибки = "";
Товары = НайтиТовары(ДанныеЧека.items,Истина,лОтказ,лОписаниеОшибки);
Док = Документы.ЧекККМ.СоздатьДокумент();
Док.Дата = ТекущаяДата();
Если ДанныеЧека.type="sellReturn" Тогда
Док.ВидОперации = Перечисления.ВидыОперацийЧекККМ.Возврат;
Иначе
Док.ВидОперации = Перечисления.ВидыОперацийЧекККМ.Продажа;
КонецЕсли;
Док.КассаККМ = Константы.ИнтернетМагазин_КассаККМ.Получить();
Док.Организация = Док.КассаККМ.Владелец;
Док.Склад = Константы.ИнтернетМагазин_Склад.Получить();
Док.Комментарий = ДанныеЧека.external_id;
Док.ЧекПробитНаККМ = Истина;
Док.НомерЧекаККМ = ДанныеЧека.НомерЧека;
Док.НомерСменыККМ = ДанныеЧека.НомерСмены;
Для Каждого Стр Из ДанныеЧека.items Цикл
НовСтрока = Док.Товары.Добавить();
х = Товары.Найти(Стр.name,"Наименование");
Если х<>Неопределено Тогда
НовСтрока.Номенклатура = х.Ссылка;
НовСтрока.ЕдиницаИзмерения = х.ЕдиницаХраненияОстатков;
КонецЕсли;
НовСтрока.Количество = Стр.quantity;
НовСтрока.Цена = Стр.price;
НовСтрока.Сумма = Стр.amount;
НовСтрока.Коэффициент = 1;
КонецЦикла;
НовСтрока = Док.Оплата.Добавить();
НовСтрока.ВидОплаты = Константы.ИнтернетМагазин_ВидОплаты.Получить();
НовСтрока.Сумма = Док.Товары.Итог("Сумма");
Попытка
Док.Записать(РежимЗаписиДокумента.Запись);
Док.Записать(РежимЗаписиДокумента.Проведение,РежимПроведенияДокумента.Неоперативный);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Функция ПолучитьСтавкуНДС(Знач х)
х = СокрЛП(НРег(х));
х = СтрЗаменить(х,"vat","");
х = СтрЗаменить(х,"_","");
Если х="20" Тогда
Результат = 20;
ИначеЕсли х="120" Тогда
Результат = 120;
ИначеЕсли х="18" Тогда
Результат = 18;
ИначеЕсли х="118" Тогда
Результат = 118;
ИначеЕсли х="10" Тогда
Результат = 0;
ИначеЕсли х="110" Тогда
Результат = 110;
ИначеЕсли х="0" Тогда
Результат = 0;
Иначе
Результат = "";
КонецЕсли;
Возврат Результат;
КонецФункции
//type - тип операции ("sell" - приход денежных средств, "sellReturn" - возврат прихода денежных средств)
Функция ПолучитьТипРасчета(type)
Если type="sell" Тогда
Результат = Перечисления.ТипыРасчетаДенежнымиСредствами.ПриходДенежныхСредств;
ИначеЕсли type="sellReturn" Тогда
Результат = Перечисления.ТипыРасчетаДенежнымиСредствами.ВозвратДенежныхСредств;
Иначе
Результат = Неопределено;
КонецЕсли;
Возврат Результат;
КонецФункции
//taxationType - система налогообложения ("osn" - общая, "usnIncome" - УСН (доход), "usnIncomeOutcome" УСН (доход-расход), "envd" - ЕНВД, "esn" - ЕСХН, "patent" - патент)
Функция ПолучитьСистемуНалогообложения(taxationType)
Если taxationType="osn" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.ОСН;
ИначеЕсли taxationType="usnIncome" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.УСНДоход;
ИначеЕсли taxationType="usnIncomeOutcome" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.УСНДоходРасход;
ИначеЕсли taxationType="envd" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.ЕНВД;
ИначеЕсли taxationType="esn" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.ЕСН;
ИначеЕсли taxationType="patent" Тогда
Результат = Перечисления.ТипыСистемНалогообложенияККТ.Патент;
Иначе
Результат = Неопределено;
КонецЕсли;
Возврат Результат;
КонецФункции
//paymentMethod - способ расчета ("fullPrepayment" - предоплата полная, "prepayment" - предоплата частичная, "advance" - аванс, "fullPayment'" - полный расчет, "credit" - передача в кредит, "creditPayment" - оплата кредита)
Функция ПолучитьСпособРасчета(paymentMethod)
Если paymentMethod="fullPrepayment" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.ПредоплатаПолная;
ИначеЕсли paymentMethod="prepayment" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.ПредоплатаЧастичная;
ИначеЕсли paymentMethod="advance" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.Аванс;
ИначеЕсли paymentMethod="fullPayment" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.ПередачаСПолнойОплатой;
ИначеЕсли paymentMethod="credit" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.ПередачаБезОплаты;
ИначеЕсли paymentMethod="creditPayment" Тогда
Результат = Перечисления.ПризнакиСпособаРасчета.ОплатаКредита;
Иначе
Результат = Неопределено;
КонецЕсли;
Возврат Результат;
КонецФункции
//paymentObject - предмет расчета ("commodity" - товар, "excise" - подакцизный товар, "job" - работа, "service'" - услуга, "payment" - платеж)
Функция ПолучитьПредметРасчета(paymentObject)
Если paymentObject="commodity" Тогда
Результат = Перечисления.ПризнакиПредметаРасчета.Товар;
ИначеЕсли paymentObject="excise" Тогда
Результат = Перечисления.ПризнакиПредметаРасчета.ПодакцизныйТовар;
ИначеЕсли paymentObject="job" Тогда
Результат = Перечисления.ПризнакиПредметаРасчета.Работа;
ИначеЕсли paymentObject="service" Тогда
Результат = Перечисления.ПризнакиПредметаРасчета.Услуга;
ИначеЕсли paymentObject="payment" Тогда
Результат = Перечисления.ПризнакиПредметаРасчета.Платеж;
Иначе
Результат = Неопределено;
КонецЕсли;
Возврат Результат;
КонецФункции
//"cash" - наличные, "electronically" - безналичные, "prepaid" - зачет предоплаты, "credit" - оплата в кредит)
Функция ПолучитьТипОплаты(type)
Если type="cash" Тогда
Результат = Перечисления.ТипыОплатыККТ.Наличные;
ИначеЕсли type="electronically" Тогда
Результат = Перечисления.ТипыОплатыККТ.Электронно;
ИначеЕсли type="prepaid" Тогда
Результат = Перечисления.ТипыОплатыККТ.Предоплата;
ИначеЕсли type="credit" Тогда
Результат = Перечисления.ТипыОплатыККТ.Постоплата;
Иначе
Результат = Неопределено;
КонецЕсли;
Возврат Результат;
КонецФункции
Процедура ПробитьЧек(ДанныеЧека,Отказ,ОписаниеОшибки)
ПараметрыПодключения = ПодготовитьККТ(Отказ,ОписаниеОшибки);
Если Отказ Тогда
Возврат;
КонецЕсли;
ОбщиеПараметры = Новый Структура;
ОбщиеПараметры.Вставить("ТипРасчета", ПолучитьТипРасчета(ДанныеЧека.type));
ОбщиеПараметры.Вставить("СистемаНалогообложения",ПолучитьСистемуНалогообложения(ДанныеЧека.taxationType));
ОбщиеПараметры.Вставить("Кассир",Константы.ИнтернетМагазин_Кассир.Получить());
ОбщиеПараметры.Вставить("КассирИНН","");
ОбщиеПараметры.Вставить("ОтправительEmail","");
ОбщиеПараметры.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
ОбщиеПараметры.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
ОбщиеПараметры.Вставить("Электронно",Истина);
ОбщиеПараметры.Вставить("Получатель","");
ОбщиеПараметры.Вставить("ПолучательИНН","");
Если ДанныеЧека.Свойство("clientInfo") И ЗначениеЗаполнено(ДанныеЧека.clientInfo.emailOrPhone) Тогда
ОбщиеПараметры.Вставить("Отправляет1СEmail",Ложь);
ОбщиеПараметры.Вставить("ПокупательEmail",ДанныеЧека.clientInfo.emailOrPhone);
КонецЕсли;
ПозицииЧека = Новый Массив;
Для Каждого Стр Из ДанныеЧека.items Цикл
П = Новый Структура;
П.Вставить("ФискальнаяСтрока",Истина);
П.Вставить("ПризнакСпособаРасчета",ПолучитьСпособРасчета(Стр.paymentMethod));
П.Вставить("ПризнакПредметаРасчета",ПолучитьПредметРасчета(Стр.paymentObject));
П.Вставить("Наименование",Стр.name);
П.Вставить("Количество",Стр.quantity);
П.Вставить("ЦенаСоСкидками",Стр.price);
П.Вставить("Сумма",Стр.amount);
П.Вставить("СуммаСкидок",0);
П.Вставить("НомерСекции",1);
П.Вставить("СтавкаНДС",ПолучитьСтавкуНДС(Стр.tax.type));
П.Вставить("КодВидаНоменклатурнойКлассификации","");
П.Вставить("Штрихкод","");
П.Вставить("ДанныеАгента",МенеджерОборудованияКлиентСервер.ПараметрыДанныеАгента());
П.Вставить("ДанныеПоставщика",МенеджерОборудованияКлиентСервер.ПараметрыДанныеПоставщика());
П.Вставить("ДанныеКодаТоварнойНоменклатуры",МенеджерОборудованияКлиентСервер.ПараметрыДанныеКодаТоварнойНоменклатуры());
Если П.СтавкаНДС<>"" Тогда
Ставка = П.СтавкаНДС;
Если Ставка>100 Тогда
Ставка = Ставка - 100;
КонецЕсли;
П.Вставить("СуммаНДС",Окр(Стр.amount/(100+Ставка)*Ставка,2)); //НДС включен в сумму
КонецЕсли;
Если Стр.Свойство("nomenclatureCode") И ЗначениеЗаполнено(Стр.nomenclatureCode) Тогда //данные маркировки
П.ДанныеКодаТоварнойНоменклатуры.РеквизитКодаТовара = Стр.nomenclatureCode;
КонецЕсли;
ПозицииЧека.Добавить(П);
КонецЦикла;
ОбщиеПараметры.Вставить("ПозицииЧека",ПозицииЧека);
ТаблицаОплат = Новый Массив;
Для Каждого Стр Из ДанныеЧека.payments Цикл
П = Новый Структура;
П.Вставить("ТипОплаты",ПолучитьТипОплаты(Стр.type));
П.Вставить("Сумма",Стр.sum);
ТаблицаОплат.Добавить(П);
КонецЦикла;
ОбщиеПараметры.Вставить("ТаблицаОплат",ТаблицаОплат);
ПараметрыФискализации = Новый Структура("ДанныеЧекаXML,ТипРасчета,СуммаЧека,ОплатаНаличные,ОплатаЭлектронно,ОплатаПостоплата,ОплатаПредоплата,ОплатаВстречноеПредоставление,РевизияИнтерфейса");
ПараметрыФискализации.РевизияИнтерфейса = 3003;
МенеджерОборудованияВызовСервера.СформироватьXMLПакетДляФискализацияЧека(ОбщиеПараметры,ПараметрыФискализации);
ВыходныеПараметры = "";
Если ПараметрыПодключения.Драйвер.СформироватьЧек(ПараметрыПодключения.ИДУстройства, ОбщиеПараметры.Электронно, ПараметрыФискализации.ДанныеЧекаXML, ВыходныеПараметры)<>Истина Тогда
П1 = "";
ПараметрыПодключения.Драйвер.ПолучитьОшибку(П1);
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Отказ = Истина;
ОписаниеОшибки = "Ошибка вызова метода ProcessCheck : "+П1;
Возврат;
КонецЕсли;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ВыходныеПараметры);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерСмены = Число(Д.Parameters.ShiftNumber);
НомерЧека = Число(Д.Parameters.CheckNumber); //Номер фискального документа (ФД)
ФискальныйПризнак = Д.Parameters.FiscalSign; //Фискальный признак (ФПД)
Дата = Д.Parameters.DateTime;
Дата = СтрЗаменить(Дата,"-","");
Дата = СтрЗаменить(Дата,":","");
//Получим номер ФН
НомерФН = "";
ТаблицаПараметровККТ = "";
Если ПараметрыПодключения.Драйвер.ПолучитьПараметрыККТ(ПараметрыПодключения.ИДУстройства,ТаблицаПараметровККТ)=Истина Тогда
Попытка
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТаблицаПараметровККТ);
Д = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НомерФН = Д.FNSerialNumber;
Исключение
КонецПопытки;
КонецЕсли;
ПараметрыПодключения.Драйвер.Отключить(ПараметрыПодключения.ИДУстройства);
Если ДанныеЧека.type="sellReturn" Тогда
n = "2";
Иначе
n = "1";
КонецЕсли;
qr = "t="+Дата+"&s="+Формат(ДанныеЧека.total,"ЧДЦ=2; ЧРД=.; ЧН=; ЧГ=")+"&fn="+НомерФН+"&i="+НомерЧека+"&fp="+ФискальныйПризнак+"&n="+n;
ДанныеЧека.Вставить("НомерЧека",НомерЧека);
ДанныеЧека.Вставить("НомерСмены",НомерСмены);
ДанныеЧека.Вставить("qr",qr);
КонецПроцедуры
Функция СоздатьСоединение(URL,Отказ,ОписаниеОшибки)
Результат = Новый Структура;
лURL = СокрЛП(НРег(URL));
Если Лев(лURL,7)="http://" Тогда
ЗащищенноеСоединение = Неопределено;
лURL = Сред(лURL,8);
ИначеЕсли Лев(лURL,8)="https://" Тогда
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL;
лURL = Сред(лURL,9);
Иначе
Отказ = Истина;
ОписаниеОшибки = "Неверный URL";
Возврат Результат;
КонецЕсли;
х = Найти(лURL,"/bitrix/tools/sale_check_print.php?hash=");
Если х=0 Тогда
Отказ = Истина;
ОписаниеОшибки = "Неверный URL";
Возврат Результат;
КонецЕсли;
Сервер = Лев(лURL,х-1);
лURL = Сред(лURL,х);
Результат.Вставить("Соединение", Новый HTTPСоединение(Сервер,,,,,,ЗащищенноеСоединение));
Результат.Вставить("Запрос", Новый HTTPЗапрос(лURL));
Возврат Результат;
КонецФункции
Процедура УстановитьСтатусыЧеков(HTTP,Чеки,Отказ,ОписаниеОшибки)
Если Чеки.Количество()=0 Тогда
Возврат;
КонецЕсли;
printed = Новый Массив;
Для Каждого Стр Из Чеки Цикл
printed.Добавить(Новый Структура("uuid,code,qr",Стр.uuid,0,Стр.qr));
КонецЦикла;
kkm_1 = Новый Структура;
kkm_1.Вставить("zn",HTTP.zn);
kkm_1.Вставить("status","ok");
kkm_1.Вставить("printed",printed);
kkm = Новый Массив;
kkm.Добавить(kkm_1);
data = Новый Структура;
data.Вставить("kkm",kkm);
data.Вставить("status","ok");
data.Вставить("api_version",2);
data.Вставить("mode","dispatcher");
data.Вставить("api_version",2);
Тело = Значение_В_JSON(data);
HTTP.Запрос.УстановитьТелоИзСтроки(Тело,КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать);
Попытка
Ответ = HTTP.Соединение.ОтправитьДляОбработки(HTTP.Запрос);
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
Возврат;
КонецПопытки;
Тело = Ответ.ПолучитьТелоКакСтроку();
Если Ответ.КодСостояния<>200 Тогда
Отказ = Истина;
ОписаниеОшибки = Тело;
Возврат;
КонецЕсли;
Данные = JSON_В_Значение(Тело);
Если ТипЗнч(Данные)<>Тип("Структура") Тогда
Отказ = Истина;
ОписаниеОшибки = "Ошибка разбора JSON : "+Тело;
Возврат;
КонецЕсли;
Если Данные.Свойство("ack") Тогда
Для Каждого Стр Из Данные.ack Цикл
//Удалим запись, т.к. сайт установил статус чека
Набор = РегистрыСведений.ИнтернетМагазин_НапечатанныеЧеки.СоздатьНаборЗаписей();
Набор.Отбор.uuid.Установить(Стр);
Попытка
Набор.Записать();
Исключение
Отказ = Истина;
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Функция ПолучитьНапечатанныеЧеки()
Набор = РегистрыСведений.ИнтернетМагазин_НапечатанныеЧеки.СоздатьНаборЗаписей();
Набор.Прочитать();
Результат = Набор.Выгрузить();
Возврат Результат;
КонецФункции
В этом примере чек сначала распечатывается на ККТ, а потом создаётся и проводится документ "Чек ККМ".