Оповещения о чеках
с версии 400После каждого пробитого чека касса отправляет его вашей программе одним HTTP-запросом.
Работает с версии кассы 2.2.09.400. Касса сама подключается к вашей программе, поэтому они должны быть в одной сети, а порт программы открыт в брандмауэре. Регистрировать программу на сервере не нужно. Для проб подойдёт демо-касса: она шлёт те же оповещения.
Включение на кассе
- Настройки → Обмен → Отправлять чеки во внешнюю программу.
- В поле IP программы, можно с портом укажите адрес:
192.168.1.10,192.168.1.10:8080или целикомhttp://host:порт. Путь/yakassa/checkкасса добавит сама. - Нажмите Тест : проверить связь с программой. Касса отправит событие
pingи покажет итог. - Итог по каждому чеку виден в журнале на экране кассы: Настройки → Системные функции → Показать логи.
Запрос
/yakassa/checkв вашу программу| Параметр | Значение |
|---|---|
| Заголовок | Content-Type: application/json; charset=utf-8 |
| Тело | JSON в UTF-8, поля со значением null не передаются |
| Авторизация | нет |
| Успех | любой ответ 2xx, тело ответа касса не читает |
| Таймауты | соединение 3 секунды, отправка и ответ 5 секунд |
| Повторы | нет, одна попытка на чек |
| Порядок | по одному, в порядке пробития |
Отправляется каждый чек после фискализации: продажа, возврат продажи, покупка, возврат покупки, и пробитый кассиром, и пришедший обменом.
Не отправляются операции со сменой, внесение и изъятие, оплата без чека, повторная печать.
Отправка идёт после того, как чек уже пробит, и на него не влияет. Если программа выключена, касса работает как обычно, а этот чек программа уже не получит.
Тело запроса
| Поле | Тип | Что это |
|---|---|---|
event | String | receipt чек пробит, ping кнопка «Тест» |
kkmCode | String | лицевой счёт кассы. Сразу после установки может не прийти, различать кассы надёжнее по result.kkmID |
source | String | откуда чек, см. ниже |
result | Object | фискальные реквизиты, суммы в сомах |
receipt | Object | сам чек, суммы в тыйынах |
source | Откуда чек |
|---|---|
TERMINAL | кассир на экране кассы |
TCP | документ пришёл по TCP |
HTTP | документ пришёл через сервер |
STATIC_QR | оплата статическим QR: документ сделал сервер YaKassa. Эту метку ставит только сервер |
INTENT | другое приложение на этом же устройстве |
Событие ping приходит на тот же адрес, в нём нет чека. Просто ответьте 2xx.
{ "event": "ping", "kkmCode": "123456789" }Если вы сами шлёте чеки на кассу, оповещение придёт и по ним: result.id совпадёт с id вашего документа.
Блок result
Тот же формат, что ответ на статус документа.
| Поле | Тип | Что это |
|---|---|---|
id | String | id чека. Ключ для защиты от дублей |
status | String | всегда OK |
fdNumber | String | номер фискального документа |
fpd | String | фискальный признак |
shiftNumber | String | номер смены |
receiptNumber | String | номер чека внутри смены |
gnsDate | String | дата и время, ГГГГММДДTччммсс, местное время кассы |
qr | String | ссылка на проверку в ГНС |
rnKKM, fnKKM | String | регистрационный номер и номер фискального модуля |
kkmID | String | id кассы на сервере |
amount | Number | сумма чека, сом |
cashless | Boolean | true: часть или вся сумма оплачена безналом |
print | Object | реквизиты печатного чека, см. печатные поля. Кроме них operation и title |
Блок receipt
Тело чека, как оно ушло на фискализацию. Все суммы в тыйынах: 1 сом = 100 тыйын.
| Поле | Тип | Что это |
|---|---|---|
uuid | String | id чека, равен result.id |
source | String | то же, что source наверху |
operation | String | INCOME, INCOME_RETURN, EXPENDITURE, EXPENDITURE_RETURN |
totalSum | Integer | сумма чека: 12500 это 125 сом |
totalCashSum | Integer | оплачено наличными |
totalCashlessSum | Integer | оплачено безналом |
goods | Array | позиции чека |
taxSums | Array | налоги: type VAT или ST, code код ставки, sum сумма |
taxSystem | Integer | код системы налогообложения, словами в result.print.sno |
fdNumber, shiftNumber, fnNumber | String | ФД, смена, ФМ |
kkmID, tin | String | id кассы и ИНН |
sellType | String | режим продаж: SIMPLE, PRODUCTS2, CURRENCY2 |
rrn | String | RRN банковской операции, если была карта |
originFdNumber, originFnSerialNumber | Number, String | ФД и ФМ чека-основания, только у возврата |
Поля позиции goods[]:
| Поле | Тип | Что это |
|---|---|---|
name | String | наименование |
price | Number | цена за единицу, тыйын |
quantity | Number | количество, может быть дробным |
cost | Integer | стоимость позиции, округлена до тыйына |
vat, st | String | коды ставок НДС и НСП |
category | String | секция, если задана |
calcItemAttributeCode | String | 0 товар, 1 услуга |
sgtin | String | код маркировки, чаще пустой |
taxSums | Array | налоги по позиции, только когда в чеке есть секции |
Поля, которых нет в таблицах, служебные: не опирайтесь на них.
Пример
Чек на 125 сом наличными на демо-кассе. Порядок полей может отличаться.
{
"event": "receipt",
"kkmCode": "123456789",
"source": "TERMINAL",
"result": {
"id": "8b9aecaf-5e80-4d78-a9d4-361eebed0ec2",
"status": "OK",
"amount": 125.0,
"cashless": false,
"fdNumber": "7584",
"fpd": "97834247394950",
"shiftNumber": "677",
"receiptNumber": "1",
"gnsDate": "20261102T055817",
"qr": "https://…/ticket?date=20261102T055817&type=3&…&sum=12500",
"rnKKM": "0000000000000000",
"fnKKM": "0000000000000000",
"kkmID": "65f1234567890abcdef12345",
"print": {
"title": "ДЕМО -> НЕ ФИСКАЛЬНЫЙ",
"operation": "Продажа",
"storeName": "Магазин",
"companyName": "ОсОО Ромашка",
"tin": "00000000000000",
"address": "720000, Бишкек",
"sno": "Общий налоговый режим",
"receiptNumber": "1"
}
},
"receipt": {
"uuid": "8b9aecaf-5e80-4d78-a9d4-361eebed0ec2",
"source": "TERMINAL",
"operation": "INCOME",
"sellType": "SIMPLE",
"totalSum": 12500,
"totalCashSum": 12500,
"totalCashlessSum": 0,
"fdNumber": "7584",
"fnNumber": "0000000000000000",
"shiftNumber": "677",
"kkmID": "65f1234567890abcdef12345",
"tin": "00000000000000",
"taxSystem": 0,
"taxSums": [ { "code": "0", "sum": 0, "type": "VAT" }, { "code": "0", "sum": 0, "type": "ST" } ],
"goods": [
{ "name": "Товар", "price": 12500, "quantity": 1, "cost": 12500, "vat": "0", "st": "0",
"category": "Секция 1", "calcItemAttributeCode": "0", "sgtin": "" }
]
}
}Как написать приёмник
- Отвечайте сразу, обрабатывайте потом. Дольше 5 секунд, и касса запишет «нет ответа», хотя данные вы уже получили.
- Дубли отсекайте по
result.id. Чек, который вы сами отправили обменом, придёт дважды: в ответе обмена и в оповещении. - Повторов нет. Чек, пробитый при выключенной программе, вы не получите. Чек своего документа обмена можно найти на сервере.
- HTTP, не HTTPS. Адрес с
https://касса примет, но нужен сертификат, которому доверяет Android. - Разбирайте по именам полей. Незнакомые поля и события пропускайте, но отвечайте 2xx.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/yakassa/check":
self.send_response(404)
self.end_headers()
return
length = int(self.headers.get("Content-Length") or 0)
body = json.loads(self.rfile.read(length).decode("utf-8"))
if body.get("event") == "receipt":
r = body["result"]
print(r["kkmID"], r["id"], "ФД", r["fdNumber"], r["amount"], "сом")
# здесь запись в свою очередь, тяжёлая работа после ответа
self.send_response(200)
self.end_headers()
ThreadingHTTPServer(("0.0.0.0", 8095), Handler).serve_forever()Проверить приёмник без кассы:
curl -X POST http://localhost:8095/yakassa/check \
-H "Content-Type: application/json; charset=utf-8" \
-d '{ "event": "ping", "kkmCode": "123456789" }'