Skip to main content

Справочник команд

Каждая команда — это JSON, отправленный через execute. Ответ приходит один раз, тоже в виде JSON. Данные команд те же, что и в локальном API терминала: sale.create принимает ровно то же тело, что и POST /api/sales. Все команды, с запросами на Kotlin и примерами ответов, есть также в справочнике API, на вкладке «Интеграция Android».

Запрос​

{
"v": 1,
"requestId": "7f3c…",
"idempotencyKey": "b2e1…",
"command": "sale.create",
"payload": { }
}
ПолеЧто это
vВерсия контракта. Сейчас 1.
requestIdИдентификатор этой отправки. Вы используете его в cancel и находите в событиях.
idempotencyKeyКлюч команды. Обязателен для команд, которые что-то изменяют (см. таблицу). Если вы его не передали, SDK создаст ключ сам.
commandИмя команды.
payloadДанные команды. В командах чтения они становятся параметрами поиска.

Ответ​

{
"v": 1,
"requestId": "7f3c…",
"command": "sale.create",
"httpStatus": 200,
"replayed": false,
"success": true,
"data": { "saleId": "…", "receiptNumber": 42, "fiscalCode": "…" },
"error": null,
"timestamp": "2026-09-28T13:12:23.076Z"
}
  • success и error есть в ответе всегда. При ошибке в error есть code и message; коды приведены ниже.
  • httpStatus, command и replayed отсутствуют в ошибках, которые возвращают SDK и связь с POSfix (POSFIX_*, INVALID_RESPONSE, ENGINE_NOT_READY, RESPONSE_TOO_LARGE, INTERNAL_ERROR), поэтому читайте их через opt….
  • replayed: true означает, что ответ взят из журнала: команда с тем же ключом уже была выполнена.

Команды​

КомандаЧто делаетКлюч обязателен
statusСостояние терминала и активирован ли POSfixнет
sale.createФискальная продажа: чек в SFS, оплата, печатьда
sale.listПродажи с фильтром по дате, статусу, кассирунет
sale.getОдна продажа с её позициями (payload.id)нет
sale.reprintПечатает чек повторно: оригинал, если он не вышел, иначе копию DUPLICAT, только один раз (payload.id)да
cash.in / cash.outВнесение / выдача наличных с сервисным чекомда
report.x / report.zX-отчёт / Z-отчёт. Z закрывает и банковский деньда
report.periodicПериодический отчёт за интервалда
fiscal.operationsЖурнал фискальных операцийнет
fiscal.dailySummaryИтоги текущего фискального днянет
print.textНефискальный текст на принтерда
card.voidОтменяет оплату картой до Z-отчётада
card.refundВозвращает оплату картой после Z-отчётада
catalog.products / catalog.categories / catalog.healthКаталог терминаланет
reference.vatRates / reference.cashiersСтавки НДС / кассирынет
loyalty.identifyКлиент программы лояльности по телефону или картенет
request.resultОтвет на ранее отправленную команду по её ключуда

status​

Отвечает всегда, даже на неактивированном терминале.

{
"apiVersion": 1,
"activated": true,
"headlessMode": true,
"terminal": {
"fiscalStatus": "ready",
"hasOpenShift": true,
"hasCardTerminal": true,
"version": "1.0.34+42"
}
}

terminal и headlessMode отсутствуют, пока POSfix не активирован. hasCardTerminal показывает, можно ли на этом устройстве платить картой.

sale.create​

Продажа на 90 леев: две позиции, оплачено 50 леев наличными и 40 леев картой.

{
"items": [
{ "name": "Pizza Margherita", "unitPrice": 70.00, "quantity": 1, "vatCode": "B" },
{ "name": "Apă plată 0,5 l", "unitPrice": 10.00, "quantity": 2, "vatCode": "B" }
],
"payments": [
{ "type": "1", "amount": 50.00 },
{ "type": "2", "amount": 40.00 }
],
"amountReceived": 90.00,
"printReceipt": true
}
  • vatCode: буква ставки НДС, настроенной на терминале. Список выдаёт reference.vatRates.
  • payments[].type: код способа оплаты по ACPS — 1 наличные, 2 карта, 5 талоны на питание, 7 другой платёжный инструмент. Полный список — в локальном API.
  • amountReceived должен быть равен сумме оплат. Если нужна сдача, передайте сумму, полученную наличными, а POSfix рассчитает сдачу.
  • Оплата 2 или 5 без rrn запускает карточный терминал. Если rrn заполнен, POSfix считает, что оплата уже проведена в другом месте. Отмену и возврат тоже выполняйте там: card.void и card.refund ищут только среди оплат, принятых POSfix.
  • cartDiscount: { "percent": 10 } или { "absolute": 5.00 } на весь чек.
  • loyalty: { "memberId": "…" } из loyalty.identify. Продажа начисляет клиенту баллы.

В ответе, среди прочего, есть: saleId, receiptNumber, reportNumber, fiscalCode, total, change, vatBreakdown, paymentBreakdown и cardPaymentResult (rrn, authCode, cardMask, chequeNumber, isMiaQr). Сохраните chequeNumber и rrn: первый понадобится для card.void, второй — для card.refund. chequeNumber может отсутствовать, потому что его возвращают не все банки.

card.void и card.refund​

{ "command": "card.void", "payload": { "chequeNumber": "000123", "amount": 40.00, "reason": "Clientul a renunțat" } }
{ "command": "card.refund", "payload": { "originalRrn": "627110323023", "amount": 40.00 } }

Отмена возможна до Z-отчёта, возврат — после него, а amount может быть и частью оплаты. Ни та, ни другая операция не выдаёт фискальный чек. Если терминалу неизвестен такой чек или RRN, ответ — NOT_FOUND.

Команды чтения​

В sale.list, fiscal.operations и catalog.* поля из payload становятся параметрами поиска локального маршрута:

{ "command": "sale.list", "payload": { "dateFrom": "2026-09-28", "limit": 20 } }

loyalty.identify​

{ "command": "loyalty.identify", "payload": { "phoneOrBarcode": "069123456" } }

В ответе есть memberId, name, tierName, discountPercent, pointsBalance и bonusAvailable. Скидку клиента применяете вы сами, в cartDiscount, чтобы знать точную сумму до того, как запросите оплату. Затем передайте loyalty.memberId в sale.create, и баллы начислятся сами. Поиск работает только при наличии интернета. Если клиент не найден, возвращается LOYALTY_MEMBER_NOT_FOUND.

Ключ идемпотентности​

Ключ защищает вас от двойного чека. Используйте новый ключ для каждой новой продажи и тот же ключ, когда повторяете ту же продажу. Ключ остаётся привязан к своему первому ответу, даже если это ошибка: после отклонённой карты следующая попытка — это новая продажа с новым ключом.

  • Тот же ключ и тот же payload: вы получаете сохранённый ответ с replayed: true. Чек не выдаётся второй раз.
  • Тот же ключ и другой payload: IDEMPOTENCY_KEY_REUSE_MISMATCH.
  • Тот же ключ, пока первая команда ещё выполняется: REQUEST_IN_PROGRESS. Ничего не выполняется второй раз.
  • request.result с тем же ключом: вы узнаёте, что произошло, ничего не выполняя. REQUEST_IN_PROGRESS означает, что команда ещё выполняется; NOT_FOUND — что команда с этим ключом не поступала.

Ключи разделены по приложениям: другое приложение не видит ваших ответов. В SDK вызов posfix.requestResult(key) { … } отправляет request.result.

Пример. Вы отправили продажу, а ваше приложение упало до ответа. После перезапуска отправьте request.result с ключом продажи:

  • success: true: чек выдан, не отправляйте продажу снова.
  • REQUEST_IN_PROGRESS: продажа ещё выполняется, например ждёт карту. Спросите снова через несколько секунд.
  • NOT_FOUND: продажа не дошла до POSfix. Её можно отправить снова, с тем же ключом.

Коды ошибок​

Коды, специфичные для интеграции:

КодКогда возникает
TERMINAL_NOT_ACTIVATEDPOSfix не активирован на этом терминале. Активируйте его один раз, на его экране.
VALIDATION_ERRORНе хватает обязательного поля, например idempotencyKey или payload.id.
UNKNOWN_COMMANDКоманды с таким именем не существует.
INVALID_JSONЗапрос не является JSON-объектом.
SALE_CANCELLEDПродажа остановлена через cancel до выдачи фискального чека. Одобренные оплаты картой отменены.
COMMAND_TIMEOUTКоманда выполняется дольше 150 с. Она продолжается в POSfix: результат вы узнаете через request.result.
NOT_FOUNDrequest.result: нет команды с этим ключом. card.void / card.refund: терминалу неизвестен такой чек или RRN.
REQUEST_IN_PROGRESSКоманда с тем же ключом ещё выполняется. Спросите снова через request.result.
LOYALTY_MEMBER_NOT_FOUNDКлиент не существует или сервис программы лояльности не отвечает.
ENGINE_NOT_READYPOSfix не запустился за 45 с после отправки команды.
RESPONSE_TOO_LARGEОтвет больше 400 КБ. Запрашивайте меньше строк с помощью limit и offset.
POSFIX_NOT_CONNECTEDSDK: вы отправили команду до connect().
POSFIX_DISCONNECTEDSDK: POSfix остановился, пока команда ждала ответа. Проверьте результат через request.result.
INVALID_RESPONSESDK: не удалось прочитать ответ POSfix.
INTERNAL_ERRORНепредвиденная ошибка в POSfix. Проверьте результат через request.result, прежде чем отправлять команду повторно.

Коды, которые приходят из локального API и встречаются чаще всего:

КодКогда возникает
FISCAL_DAY_EXPIREDФискальный день истёк: отправьте report.z.
TERMINAL_NOT_FISCALТерминал ещё не фискализирован.
BUSYИдёт другая фискальная операция. Повторите через несколько секунд с тем же ключом.
CARD_PAYMENT_DECLINEDБанк отклонил оплату, или клиент отказался от неё на терминале.
CARD_PAYMENT_ERRORОплата картой не прошла по другой причине, не из-за отказа банка.
CARD_TERMINAL_UNAVAILABLEНа этом устройстве нельзя платить картой.
DUPLICATE_LIMIT_REACHEDДубликат чека уже напечатан.
IDEMPOTENCY_KEY_REUSE_MISMATCHТот же ключ, отправленный с другим payload.
MEV_ERROR / MEV_TIMEOUT / MEV_UNAVAILABLESFS отклонила чек или не ответила.

Остальные — в Справочнике API.

Без SDK​

Если вы не хотите зависеть от SDK, используйте интерфейс AIDL напрямую:

  1. Скачайте файлы AIDL и распакуйте их в src/main/aidl/.
  2. Добавьте в манифест <queries><package android:name="md.posfix.posfix" /></queries>. Без этого на Android 11+ привязка возвращает false.
  3. Привяжитесь через Intent("md.posfix.integration.BIND").setPackage("md.posfix.posfix") с BIND_AUTO_CREATE. На Android 14+ добавьте также BIND_ALLOW_ACTIVITY_STARTS, иначе POSfix не сможет открыть платёжный экран банка.
  4. Вызовите execute(requestJson, callback). Это асинхронный вызов: результат приходит в IPosfixCallback.onResult.

Кроме execute, в интерфейсе есть getApiVersion(), cancel(requestId) и registerListener / unregisterListener для событий.