iKKM Bluetooth и USBserial API

Описание протокола

October 19 2020

Вступление

Протокол работы с iKKM по Bluetooth и USB совместим и повторяет Web API. Подробное описание Web API доступно по адресу http://www.ikkm.kz/apidoc .

Процесс обмена данными по Bluetooth или по USB Serial одинаков, поэтому далее по тексту, будет упомянут только Bluetooth, для USB будут указаны только особенности.

Данная документация подразумевает, что вы уже ознакомились с форматом запросов Web API и приступили к интеграции Bluetooth.

Поддержка Bluetooth API реализована параллельно с Web API. Если ваш iKKM поддерживает Bluetooth, вы можете включить Bluetooth API вместе с Web API, конфликтов в одновременном использовании не возникнет.

Для корректной работы с iKKM, рекомендуем не блокировать основной поток вашей программы, а использовать несколько потоков для обмена сообщениями по Bluetooth с iKKM.

Передача данных

Для подключения по Bluetooth: Отправка данных осуществляется по Bluetooth Socket RFCOMM. RFCOMM является потоковыми транспортом и еще известен как Serial Port Profile (SPP).

Для подключения по USB: используется режим CDC (USB Serial), специальных драйверов для USB не требуется, VID/PID:"067b:2303" Определяется iKKM как Prolific USB-to-Serial Comm Port

Обрамление передаваемых пакетов отсутствует, однако есть важная особенность - любое сообщение должно заканчиваться символом новой строки (\n или в HEX:0x0a), ответы от iKKM приходят так же - в конце 0x0a.

Размер пакета сообщения не имеет значения, так как iKKM будет накапливать все входящие пакеты до символа 0x0a. Однако рекомендуем не превышать размер каждого отдельного пакета 2048 байт. В большинстве случаев вам не потребуется проводить такого тюнинга, например в случае Android, система сама позаботится правильной длине пакетов.

Формат передаваемых сообщений JSON с кодировкой UTF-8.

Особенности API

Отправка запросов

Поддерживаются следующие методы: api, apicheck, apiprint, apibank, apideposit, apiwithdraw, apizreport, apixreport.

Методы получения справочников и отчетов, все что начинается с /dump/... не поддерживается, и ввиду дизайна системы не планируется в ближайшее время.

Формирование строки запроса аналогично с Web API если бы вы использовали /v2.. и передавали JSON в POST запросе. Метод указывается не в URL, а в самом JSON, в параметре method

Пример Web API:

GET /api/?key=12345678&sale=800&cash=1000 HTTP/1.1

Тоже самое для Bluetooth API:

{"method":"/api","key":"12345678","sale":"800","cash":"1000"}/n

Обратите внимание на наличие / в начале метода.

Далее, для ясности, все примеры будут приведены на Javascript. Рассмотрим пример отправки фискального чека:

    function saleprepare(_sum,_cash,_itemdata){
        var obj = {
            key: AppSettings.apikey,
            method: "/api",
            sale: _sum,
            cash: _cash,
            itemdata:_itemdata
        };
        var myJSON = JSON.stringify(obj);
        return myJSON;
    }
    
    property var _itemdata: [{
            "name": "Чай Green Frencho",
            "price": "140",
            "qty": "2",
            "sum": "280",
            "taxid":"2"
        }, {
            "name": "Стейк",
            "price": "8000",
            "qty": "2",
            "sum": "16000"
        }]

При передаче itemdata, передавайте параметр как часть основного JSON, как вложенную структуру, без каких-либо эскейпов.

Пример запроса оплаты по карте банка:

    function bankpurchase(_amount, _showSlotmenu, _customerSlip){
        var obj = {
            key: AppSettings.apikey,
            method: "/apibank",
            amount : _amount,
            showSlotMenu : _showSlotmenu,
            customerSlip : _customerSlip,
            message: "purchase",
            trackdocument: AppParams.bankTrackDocument,
            bankSlot: "1"
        }
        var myJSON = JSON.stringify(obj);
        return myJSON;
    }

Параметры _showSlotmenu, _customerSlip принимают значение в string "false" или "true".

Для простоты все значения передаются как String с обычными кавычками. Например amount передается как "1000" или с разделителем - точкой "1001.00", никаких пробелов и прочих символов не должно быть. (см типы данных http://ikkm.kz/apidoc/#3)

Роль Web-API ключа

Передавать Web-API ключ в запросах по Bluetooth обязательно, однако в Bluetooth API можно всегда получить актуальный Web-API ключ в методе /apicheck/getinfo, название параметра activeKey. При этом в самом запросе /apicheck/getinfo , Web-API ключ передавать не обязательно.

Причина следующая - при использовании Bluetooth API, отпадает необходимость Web-API ключа как средства безопастности, однако помимо безопастности Web-API ключ решает задачу идемпотентности, что позволяет избежать задвоений чеков и прочих неясностей операций.

Пример запроса получения информации iKKM:

    function getinfo(){
        var obj = {
            method: "/apicheck/getinfo"
        };
        var myJSON = JSON.stringify(obj);
        return myJSON;
    }

Получение ответов

Формат ответов JSON. Все ответы идентичны кроме ответа на запрос /apicheck/getinfo (см http://ikkm.kz/apidoc/#7-1-apicheck)

Стандартный ответ имеет вид:

 {"body":"0", "result":"200", "resultText":"z-report-started", "replyMethod":"/apizreport"}

Подробное описание ошибок и результатов можно найти на http://ikkm.kz/apidoc/#11-1

Исключение составляет только поле replyMethod, которое содержит ваш запрос на который ответил iKKM.

При разборе ответа, необходимо проверить что ответ JSON формата, затем проверить поле result, коды аналогичны HTTP кодам ответа.

Пример проверки ответа и списка основных ошибок:

    function errorApiHandler(obj){
        // returns true if ok!
        if (obj.result === "200" || obj.result === "203"){
            // here possibly add some action on 203', since it is a repeated operation
            return true;
        }
        var err = ikkmErrorList[obj.body]
        if (err !== undefined){
            pops.error(err)
        }
        else {
            pops.error("Неизвестная ошибка: "+obj.body)
        }
        return false;
    }

    Component.onCompleted: {
        ikkmErrorList = {
            "-1" : "неверный web-api ключ, настроить новый ключ на iKKM",
            "-2" : "происходит регистрация или банковская операция, повторите запрос позже",
            "-3" : "смена превысила 24 часа, необходимо закрыть смену",
            "-4" : "оффлайн период более 72 часов, решить проблему связи и разблокировать ккм",
            "-5" : "низкий заряд батареи ккм",
            "-6" : "принтер не готов, проверить бумагу",
            "-7" : "ошибка запроса, проверьте передаваемые параметры",
            "-8" : "ошибка метода",
            "-9" : "ошибка значений параметров передавать только цифры (кроме параметра print)",
            "-10": "сумма cash (bank, tara, credit) меньше sale (buy…), проверьте логику работы с API",
            "-11": "смена открыта другим кассиром, закрыть смену",
            "-12": "значение cash меньше чем расчетная итоговая сумма, может возникнуть если налог насчитывается поверх суммы",
            "-13": "банк или тара или кредит больше чем итоговая сумма (или чек уже оплачен наличностью) проверьте логику работы с API",
            "-14": "ошибка налога, указанный налог не найден",
            "-15": "нет наличности в кассе (возврат продажи, покупка), проверьте передаваемые параметры",
            "-16": "операцию может проводить только старший кассир, проверить пользователя",
            "-17": "ошибка обработки JSON запроса в itemdata, проверьте логику работы с API",
            "-18": "отклонен IP адрес хоста с которого выполнен запрос",
            "-19": "itemdata errors",
            "-20": "происходит регистрация или банковская операция, повторите запрос позже"
        }
    }

Push сообщения

Учитывая особенность Bluetooth соединения - способность начинать передавать данные в обе стороны, в iKKM добавлены Push сообщения.

Постоянно опрашивать iKKM не нужно. Теперь вы можете получить оповещение об отправке чека в ОФД, iKKM закончил выполнение отчета, результат банковской операции итд.

Оповещения от iKKM начинается с @... в поле replyMethod Пример:

{"replyMethod":"@ZREPORTdone"}

Описание Push сообщений:

  1. "@BANKdone" - закончена банковская операция или сверка с банком, других параметров не содержит.

  2. "@ZREPORTdone" - закончен Z или X отчет, других параметров не содержит.

  3. "@OFDdone" - чек отправлен в ОФД или чеку присвоен автономный признак. Содержит два дополнительных поля: ofdCode фискальный признак ОФД, номер чека cheqId. Следует ожидать данное событие только если ответ на запрос содержит result=200. Ошибки 400, а также повторения 203 не вызовут появления данного события.

  4. "@BANKresult" - результат платежа, transactionResult со значением true/false и номером чека в поле chequeId

      В режиме USB режиме, `transactionResult` возвращается только если операция `true`
    
  5. "@PRINTdata" - распечатанные данные в HTML формате, поле print. Новая строка в print заменена на <br> итд

      Отправка только если выбран внутренний или виртуальный принтер.
    

В хронологическом порядке оповещения типа @...done приходят в самом конце обработки уже после @BANKresult или @PRINTdata

Пример обработчика входящих оповещений:

	function handle_live_msg(obj){
        var r = obj.replyMethod
        switch (r){
        case "@BANKdone":
        case "@ZREPORTdone":
            pops.close()
            pops.info("Оповещение","Операция закончена")
            break;
        case "@OFDdone":
            pops.close()
            pops.info("Чек отправлен","Код ОФД: "+obj.ofdCode+", чек №"+obj.cheqId)
            break;
        case "@BANKresult":
            pops.close()
            pops.info("Результат платежа:",obj.transactionResult + " чек №"+obj.chequeId)
            break;
        case "@PRINTdata":
            sigGotCheque(obj.print)
            break;
        }
    }

Вы можете игнорировать данные оповещения если они вам не нужны, например "@PRINTdata". Или сохранять в вашем приложении чек и отправлять клиенту.

Возможно также блокировать интерфейс ввода данных или показывать экран загрузки до тех пор пока не получены оповещения @ZREPORTdone, @OFDdone, @BANKdone.

Примеры

USB Python: http://www.ikkm.kz/assets/docs/python-ikkm-usb.zip

-THE END-