PayKeeper Must-Have для MVP — детальный разбор
Детализация 7 hard-блокеров из BR 2.4. Каждый пункт — что есть сейчас, что не хватает, какой API/payload ожидаем, как это влияет на MVP, и какие вопросы задать команде PayKeeper.
Блокеры про cross-device push (планшет → терминал), обратную синхронизацию каталога и автоматизацию онбординга ЛК — новые, добавлены после обсуждения архитектуры и lifecycle ЛК. Sandbox убран (договоримся о dev-доступе отдельно). Блокер про terminal→store mapping перенесён в workaroundable (см. BR 2.4 §3.7).
Источники: OpenAPI-спеки в
_assets/paykeeper/*.yaml+gap-analysis.md+business-summary.md+vision-pos-platform.md+ бэклог PayKeeper (файлдоработки кассы 3 в 1 (2).xlsx).
Блокер 1. Cross-device push: внешний планшет → терминал PayKeeper
Контекст и почему это блокер для MVP
Архитектура которую хотим:
┌──────────────────────────┐ ┌──────────────────────────────┐
│ Наш планшет │ │ PayKeeper касса 3-в-1 │
│ (frontend-only app) │ │ (стоковый терминал с их app) │
│ │ │ │
│ • Каталог / меню │ ??? │ • Экран «ожидают оплаты» │
│ • Сборка заказа │ ───────► │ • Кассир нажимает «Оплатить» │
│ • Работа с клиентом │ │ • Приём карты / СБП / ApplePay│
│ • Без PayKeeper SDK │ │ • Фискализация + ОФД │
│ • Без оплат вообще │ │ │
└──────────────────────────┘ └──────────────┬───────────────┘
│ webhook
▼
Наш Order Service
Ключевое ограничение: на нашем планшете нет PayKeeper-app, нет SDK. Коммуникация с ихним терминалом должна идти через их серверы или локальный протокол.
Что мы проверили — 10 возможных механизмов
Все проверено по OpenAPI-спекам + gap-analysis.md + vision-pos-platform.md:
| № | Механизм | Есть? | Детали |
|---|---|---|---|
| M1 | Open tab / очередь заказов | ❌ | Нет концепта «открытый счёт», «pending», «черновик» |
| M2 | Pre-register invoice (создать ID заранее, терминал подхватывает) | ❌ | paykeeper://mobile.app/invoice/ создаёт invoice в момент вызова; нет POST /invoices/register чтобы создать заранее |
| M3 | QR-сканер на терминале, читающий deep-link | ❌ | mPOS показывает QR клиенту (СБП), но не сканирует входящие |
| M4 | Web-checkout (как YooKassa) | ❌ | Нет веб-страницы оплаты |
| M5 | Локальный API на mPOS-устройстве | ❌ | mPOS — standalone app, не слушает входящие |
| M6 | IMS-каталог как «костыль» для заказа | ❌ | Это справочник товаров, не заказов — позиции в чеке будут неправильные |
| M7 | Сущность «заказ» в API | ❌ | Нет ни в одной спеке (только payment, receipt, product) |
| M8 | Push/WebSocket на терминал | ❌ | Только исходящие webhook от PayKeeper к нам, не наоборот |
| M9 | ECR/ESP/BLE/NFC локальный протокол | ❌ | В device/install есть поле connection_type (Ethernet/BT/WiFi), но протокол не документирован |
| M10 | SDK / plug-in для mPOS-app | ❌ | Нет — только deep-link вызов снаружи |
Вывод: ни один из 10 механизмов не реализован. Схема «отдельный планшет → стоковый PayKeeper-терминал» архитектурно невозможна на текущем API.
Прямые цитаты из документации PayKeeper
Из paykeeper-app-mpos.yaml:
«Приложение PayKeeper может быть вызвано из внешнего приложения для быстрой инициализации приёма платежа… Для вызова приложения используются deep link’и»
Из gap-analysis.md:
«сценарий «кассир берёт терминал и бьёт заказ сам» не поддерживается. Все заказы инициируются из нашего приложения, терминал только принимает оплату по готовому
cart»
Из business-summary.md:
«Минимальный сценарий который работает сегодня: все платежи инициируются только из нашего приложения, терминал PK — это только “плати-и-печатай чек”, никакой самостоятельной работы»
Что просим у PayKeeper
Минимальный набор — push invoice к terminal
1. POST /api/v1/invoices/push — наш backend пушит готовый invoice на конкретный терминал.
POST https://api.paykeeper.ru/v1/invoices/push
Authorization: Bearer {api_token}
Content-Type: application/json
{
"terminal_id": "mpos_T_00042",
"external_invoice_id": "nirbi-4281",
"expires_in_seconds": 1800,
"cart": [
{
"name": "Капучино M + сироп карамель",
"price": 350,
"quantity": 1,
"tax": "vat20",
"item_type": "goods",
"measure": "pcs"
},
{
"name": "Круассан",
"price": 200,
"quantity": 1,
"tax": "vat20",
"item_type": "goods",
"measure": "pcs"
}
],
"pay_amount": 550,
"allowed_payment_methods": ["card", "sbp", "cash"],
"customer_phone": "+79123456789",
"customer_email": "customer@example.com",
"callback_url": "https://nirbi.ru/webhooks/paykeeper",
"metadata": {
"nirbi_order_id": "...",
"nirbi_store_id": "...",
"nirbi_table_number": 5
}
}
Response 201 Created:
{
"result": "success",
"data": {
"invoice_id": "pk_inv_789",
"status": "pending",
"expires_at": "2026-04-22T10:30:00Z"
}
}2. Экран «ожидают оплаты» в mPOS-app — когда на терминал поступает invoice, он появляется в списке. Кассир видит:
- Позиции (название + цена + количество)
- Итого
- Кнопки «Принять оплату» / «Отклонить»
- Опциональную информацию (номер стола, телефон клиента)
3. Уведомление о новом invoice’е — один из вариантов:
- Push-уведомление (FCM для Android)
- Звуковой сигнал через long-polling
- WebSocket-подписка со стороны mPOS
- Периодический polling
GET /terminals/{id}/pendingкаждые 3-5 сек
4. Lifecycle-webhook’и — уведомляют нас о судьбе invoice’а:
// invoice.delivered — появился на экране терминала
{ "event_type": "invoice.delivered", "invoice_id": "pk_inv_789", "terminal_id": "..." }
// invoice.accepted — кассир нажал «Принять оплату»
{ "event_type": "invoice.accepted", "invoice_id": "pk_inv_789", "user_id": "..." }
// invoice.expired — TTL вышел, никто не принял
{ "event_type": "invoice.expired", "invoice_id": "pk_inv_789" }
// invoice.rejected — кассир отклонил
{ "event_type": "invoice.rejected", "invoice_id": "pk_inv_789", "reason": "..." }
// payment.success — клиент оплатил (тот же existing webhook, + ссылка на invoice_id)
{ ..., "invoice_id": "pk_inv_789" }5. Отмена invoice с нашей стороны — DELETE /api/v1/invoices/{invoice_id} если заказ отменился.
Почему обходы не работают
- Single-device (наш app на их кассе): решает проблему, но ломает архитектуру MVP — зачем тогда отдельный планшет вообще? Превращает кассира в официанта.
- Ручной ввод суммы на терминале: нарушает 54-ФЗ — в чеке должны быть позиции, а не «прочие услуги 550₽».
- Mock-товары в IMS-каталоге: создавать «товар» Order-#4281 на каждый заказ = гигантский мусор в их системе + всё равно нарушение 54-ФЗ (позиции ненастоящие).
- QR с deep-link’ом: их mPOS не сканирует QR с deep-link’ами (только свои СБП-QR).
Что известно из бэклога PayKeeper (файл доработки кассы 3 в 1.xlsx от Рената)
В бэклоге ✓ (реализовано) помечены пункты которые прямо противоречат выводу публичных OpenAPI-спек:
| Строка | Формулировка из бэклога | Что подсказывает |
|---|---|---|
| 58 | «Подключение сайта или мобильного приложения» ✓ | Внешняя система умеет работать с кассой |
| 59 | «Подключение iiko» ✓ | iiko — внешний POS-софт — уже подключён к PayKeeper |
| 60 | «Подключение Yclients» ✓ | Ещё одна внешняя CRM подключается |
| 61 | «Подключение МойСклад» ✓ | Внешний учётный сервис |
| 62 | «Подключение Битрикс24» ✓ | Внешняя CRM |
| 63 | «Фискализация чеков по оплатам на сайте» ✓ | Оплаты с сайта фискализируются через PayKeeper |
| 64 | «Оплата заказов с сайта или внешней CRM на кассе» ✓ | Внешний источник → касса — формально это наш сценарий |
| 83 | «Создание заказов и отправка на оплату на кассу» ✓ (раздел «Мобильное приложение PayKeeper») | Их собственный mobile app создаёт заказы и отправляет на кассу |
Плюс цитата из vision-pos-platform.md:
«Paykeeper — ровно то же, чем сейчас являются Ingenico или АТОЛ Ф для iiko: умная железка без своего интеллекта»
Эта аналогия намекает на ECR-стиль интеграции: iikoFront подключается к Ingenico/АТОЛ по USB/Ethernet и командует «оплатить N₽». Если PayKeeper позиционирует себя аналогично — должен быть механизм с тем же интерфейсом.
Три возможных сценария как это устроено
| Сценарий | Описание | Подходит нам? |
|---|---|---|
| А. Same-device | iiko-приложение (iiko.Waiter / iiko.Kiosk) ставится на кассу PayKeeper 3-в-1 как сторонний Android-app и через внутренний deep-link paykeeper://... инициирует оплату. Оба приложения на одном устройстве. | ❌ Нет — у нас планшет отдельный |
| Б. ECR/cross-device API | PayKeeper-касса поддерживает режим «сторонний POS» — слушает команды по локальной сети или через backend-API от внешней системы. Именно так iiko работает с Ingenico/АТОЛ. | ✅ Да, идеально подходит. Просим документацию. |
| В. Online-оплата (remote fiscalization) | «Оплата с сайта» = клиент платит на веб-странице, PayKeeper фискализирует в облаке, выдаёт электронный чек. Физический терминал не участвует. | ⚠ Частично — только для online-заказов (доставка / самовывоз с предоплатой), не для прилавка |
По публичным спекам PayKeeper ECR / serial / WebSocket / local API не упоминается. Это может значить:
- Таких механизмов нет (тогда iiko работает по сценарию А или В)
- Они есть, но оформлены отдельным «интеграционным договором» вне публичного API-пакета (типично для премиум-партнёров)
Диагностические вопросы на встречу (приоритет!)
Эти 5 вопросов должны окончательно определить сценарий:
- «iiko.Waiter или iiko.Kiosk устанавливается на вашу кассу 3-в-1, или iiko подключается отдельным устройством через сеть / кабель?» — отличает Сценарий А от Б
- «Какой конкретно протокол/API используется в подключении iiko? Это deep-link внутри одного Android-устройства, ECR по USB/Ethernet, или REST-API на вашем backend?» — получаем конкретику
- «Строка 64 бэклога “Оплата заказов с сайта или внешней CRM на кассе” — это про оплату онлайн (клиент платит на сайте), или физическая касса принимает заказы от внешней CRM?» — отличает Сценарий В от Б
- «Если бы мы хотели подключить НАШ POS-софт аналогично iiko — по какой документации? Есть ли открытая инструкция для партнёров, или это индивидуальная интеграция по договору?» — прямой вопрос на применимость
- «Поддерживает ли ваша касса 3-в-1 ECR-интерфейс (как у Ingenico/АТОЛ) для сторонних POS-систем?» — если да, это готовое решение
Дополнительные вопросы (из прежней версии)
- Архитектурный: если механизма из п.1-5 нет для партнёров — есть ли в roadmap поддержка split-device-модели?
- Quick-win альтернатива: может быть есть возможность добавить QR-сканер в mPOS-app чтобы принимать deep-link’и из QR-кодов?
- Short-term workaround: если cross-device не делают — готовы ли официально поддерживать single-device сценарий (наш софт на их Android-кассе 3-в-1)?
Решение по MVP (варианты)
| Вариант | Архитектура | Когда применяется |
|---|---|---|
| A | Используем Сценарий Б (ECR или cross-device API PayKeeper как для iiko) | Подтвердилось на встрече что это есть и доступно партнёрам |
| B | Ждём разработки публичного cross-device push API | Если механизма А нет — зависит от готовности PK приоритезировать |
| C | Single-device: наш софт на кассе PayKeeper 3-в-1 | Если A и B недоступны — меняем архитектуру MVP |
| D | Plan B (iiko API, PayKeeper отложен) | Если все варианты выше отпадают |
Решение зависит от того, что услышим на встрече с Ренатом.
Блокер 2. Webhook на возвраты / отмены / чеки коррекции
Текущее состояние
В lk-paykeeper-vi-mpos.yaml документирован один webhook — на успешную оплату:
Endpoint: POST {merchant_configured_url} (партнёр указывает свой URL в ЛК)
Payload (form-urlencoded):
id — payment ID в PayKeeper
sum — сумма
clientid — наш client ID, переданный при инициации
orderid — наш order ID
key — HMAC-SHA1(id+sum+clientid+orderid+secret)
client_email — email клиента
client_phone — телефон клиента
service_name — название услуги
obtain_datetime — когда банк подтвердил
ps_id — ID платёжного канала
fop_receipt_key — ключ чека для URL
user_id, user_login — кассир
mpos_terminal_serial_number — серийник железа
mpos_merchant_id — ID ТСП
mpos_terminal_id — логический ID терминала
Других webhook-ов в API нет. Возврат делается через deep-link paykeeper://mobile.app/refund/ — асинхронный, результат не сигнализируется обратно. Только на экране mPOS.
Почему блокер
Без webhook’ов на возврат/отмену/коррекцию мы не узнаём:
- Когда кассир провёл возврат → наш
Order Serviceне закроет заказ со статусомrefunded - Когда клиент закрыл экран оплаты → заказ висит в
newвечно - Когда ФНС выпустила чек коррекции → мы не знаем об этом вообще
Следствия:
- Бухгалтерия не может свести день — в ERP висят «успешные» заказы, которые по факту возвращены
- Клиент жалуется «вы сняли дважды» — не можем проверить без ручной выгрузки CSV
- Compliance — чек коррекции не отражён в нашем журнале, налоговая может задать вопросы
Обход: только ежедневная reconciliation через CSV — не real-time, дорого для бухучёта.
Что просим у PayKeeper
Четыре новых webhook’а (или один универсальный с полем event_type):
2.1. POST /webhooks/refund_success
{
"event_type": "refund_success",
"original_payment_id": 12345,
"refund_id": 67890,
"refund_amount": 300.00,
"original_amount": 500.00,
"remaining_refundable": 200.00,
"refund_receipt_key": "abc123",
"refund_fn_number": "9960...",
"refund_fd_number": 43,
"refund_fp_sign": "3791...",
"cart": [...],
"refunded_at": "2026-04-22T10:30:00Z",
"initiated_by_user_id": "...",
"initiated_by_user_login": "...",
"mpos_terminal_id": "...",
"mpos_merchant_id": "...",
"key": "HMAC..."
}2.2. POST /webhooks/refund_failed
{
"event_type": "refund_failed",
"original_payment_id": 12345,
"attempted_amount": 300.00,
"reason": "INSUFFICIENT_FUNDS_ON_TERMINAL" | "NETWORK_ERROR" | "CARD_REJECTED",
"failed_at": "2026-04-22T10:30:00Z",
"key": "HMAC..."
}2.3. POST /webhooks/payment_cancelled
Сценарий: клиент открыл экран оплаты, но закрыл не оплатив.
{
"event_type": "payment_cancelled",
"invoice_id": "...",
"clientid": "...",
"orderid": "...",
"sum": 500.00,
"cancelled_at": "2026-04-22T10:35:00Z",
"reason": "USER_CANCELLED" | "TIMEOUT",
"key": "HMAC..."
}2.4. POST /webhooks/correction
Сценарий: выпущен чек коррекции (по инициативе ФНС или кассира).
{
"event_type": "correction",
"correction_receipt_key": "...",
"correction_type": "self" | "by_order",
"original_payment_id": 12345,
"correction_amount": 100.00,
"correction_fn_number": "...",
"correction_fd_number": 44,
"correction_fp_sign": "...",
"correction_reason": "...",
"issued_at": "2026-04-22T11:00:00Z",
"key": "HMAC..."
}Вопросы на встречу
- Какой проще реализовать — 4 отдельных webhook или один универсальный с полем
event_type? - Retry-policy: если мы не ответили 200 OK — через сколько retry? Сколько раз? Куда складывать недоставленные?
- Можно ли подтверждать получение асинхронно (ACK через отдельный endpoint)?
- Формат сигнатуры
key— тот же HMAC-SHA1 сsecret, что и для success?
Блокер 3. GET /payments — API списка платежей (reconciliation)
Текущее состояние
Нет эндпоинта для получения списка платежей. В спеках есть:
GET /products/— каталог (не платежи)POST /products/import— массовый импорт товаровGET /shopsв Partner API — список ТСП (не платежи)
Никакого способа программно получить «какие платежи прошли у этого терминала с X по Y».
Единственная альтернатива упомянутая в business-summary.md — ручная выгрузка CSV из ЛК. Не автоматизируется.
Почему блокер
Это fallback когда webhook потерялся. Сценарии потери webhook:
- Рестарт нашего сервиса в часы работы
- Network outage между PayKeeper и нами
- Наш endpoint вернул 500 и PayKeeper перестал ретраить
- Ошибка сети у мобильного интернета терминала
Без reconciliation-API мы никогда не уверены что журнал платежей в ERP полный. Это напрямую бьёт по бухгалтерии — «выручка» в нашей системе ≠ «выручка» в ОФД.
Что просим у PayKeeper
GET /api/v1/payments/
Query параметры:
from — ISO datetime, начало периода (обязательный)
to — ISO datetime, конец периода (обязательный)
status — success | failed | refunded | cancelled | all (default: all)
terminal_id — filter по терминалу (optional)
merchant_id — filter по ТСП (optional)
limit — пагинация (default: 100, max 1000)
cursor — cursor-based пагинация (рекомендуется)
Response:
{
"result": "success",
"data": {
"payments": [
{
"id": 12345,
"sum": 500.00,
"clientid": "...",
"orderid": "...",
"status": "success",
"obtain_datetime": "...",
"fop_receipt_key": "...",
"fn_number": "...",
"fd_number": 42,
"fp_sign": "...",
"shift_number": 1,
"receipt_number_in_shift": 3,
"mpos_terminal_id": "...",
"mpos_merchant_id": "...",
"refund_status": "none" | "partial" | "full",
"refunded_amount": 0
}
],
"next_cursor": "..." | null
}
}Альтернатива (минималистичная): можем работать даже с простым списком без cursor-пагинации, если from/to ограничены сутками — объём платежей в точке за день ≤ 1000.
Вопросы на встречу
- Реально ли это добавить к маю 2026? Или есть промежуточный вариант (например, выгружать тот же CSV через URL по API-токену)?
- Есть ли уже внутренний endpoint такого типа, просто не опубликованный в партнёрской спеке?
- Формат ответа — наш предложенный JSON или их привычный? Готовы адаптироваться.
Блокер 4. Фискальные реквизиты в webhook
Текущее состояние
В текущем webhook’е (см. блокер 2) из фискального набора есть только fop_receipt_key — ключ для формирования URL на страницу чека:
https://{tsp}.server.paykeeper.ru/receipt/{id}/{fop_receipt_key}
На эту страницу можно зайти и глазами увидеть ФН/ФД/ФП. Но это HTML, парсинг нестабилен, формат не документирован.
Отсутствуют в payload:
fn_number— серийник фискального накопителяfd_number— номер фискального документа (в рамках ФН)fp_sign— фискальный признак документа (подпись ФНС)shift_number— номер сменыreceipt_number_in_shift— порядковый номер чека в сменеoffd_sync_status— статус отправки в ОФД
Почему блокер
54-ФЗ требует от нас, как от учётной системы, сопоставлять чеки в нашем журнале с чеками в ОФД по уникальной тройке (ФН, ФД, ФП). Без этих полей:
- Бухгалтерия не может доказать, что чек из нашей ERP = чек из ОФД. Сводить реестры приходится вручную по суммам и датам — ненадёжно.
- Налоговая проверка — не можем предъявить сквозную цепочку «заказ в ERP → платёж → чек в ОФД».
- Детектирование расхождений — если кассир выдал чек через ЛК вручную (минуя ERP), мы никак этого не поймём без ФД/ФП.
Что просим у PayKeeper
Расширить payload текущего webhookucceess новыми полями:
{
// ...существующие поля...
"fn_number": "9960000000123456",
"fd_number": 42,
"fp_sign": "3791356401",
"shift_number": 1,
"receipt_number_in_shift": 3,
"offd_sync_status": "sent" | "pending" | "failed",
"receipt_datetime": "2026-04-22T10:00:00Z" // время фискализации (может отличаться от obtain_datetime)
}Альтернатива если поля добавить в webhook сложно:
GET /api/v1/payments/{id}/fiscal-data
Возвращает те же фискальные поля отдельным запросом.
Вопросы на встречу
- Технически эти поля уже есть в их системе (чек же фискализируется) — это не новая логика, а только проброс в payload. Подтвердить.
- Можно ли сделать это в ближайшем спринте? Мы как партнёр готовы принять расширение payload без breaking-change предупреждения.
- Если будет отдельный endpoint вместо полей в webhook — какой rate-limit ожидать? Мы будем звать его на каждый webhook.
Блокер 5. API смен ФН
Текущее состояние
Ни одного endpoint’а про смены во всех 6 OpenAPI-спеках. Ни shift, ни смена, ни Z-report, ни X-report, ни FN-status.
mPOS-приложение кассы очевидно умеет открывать/закрывать смену (иначе бы не работала фискализация), но делается это только вручную через UI касса-в-руке. Никакого API/webhook.
Почему блокер
Главное ограничение 54-ФЗ: ФН блокирует кассу через 24 часа без закрытия смены.
Сценарии что происходит:
- Кассир открыл смену утром в 08:00
- Смена должна быть закрыта до 08:00 следующего дня
- Если кассир ушёл, не закрыв, или забыл — в 08:01 касса блокируется
- Клиенты в момент блокировки — просто разворачиваются и уходят
- Менеджер обнаруживает проблему через жалобы
Без API смен мы:
- Не знаем заранее что смена сейчас заблокируется
- Не можем программно закрыть смену (e.g. если все кассиры забыли)
- Не узнаём что смена открылась/закрылась — не можем зажечь индикатор в админке
- Не получаем Z-отчёт в структурированном виде (для бухучёта)
Что просим у PayKeeper
5.1. GET /api/v1/mpos/terminal/{terminal_id}/shift
Response:
{
"result": "success",
"data": {
"terminal_id": "...",
"shift_status": "open" | "closed",
"shift_number": 42,
"opened_at": "2026-04-22T08:00:00Z",
"opened_by_user_login": "cashier1",
"transactions_count": 17,
"total_sum": 8500.00,
"total_cash": 3000.00,
"total_card": 5500.00,
"time_remaining_seconds": 28800, // до 24h-блокировки
"fn_expiry_warning": false,
"fn_expiry_date": "2027-04-01T00:00:00Z" // когда сам ФН (железка) истекает
}
}5.2. POST /api/v1/mpos/terminal/{terminal_id}/shift/close
Программное закрытие смены. Полезно для ночного auto-close в нашей системе.
Response:
{
"result": "success",
"data": {
"shift_number": 42,
"closed_at": "2026-04-22T20:00:00Z",
"z_report_fd_number": 43,
"z_report_fp_sign": "...",
"transactions_count": 17,
"total_sum": 8500.00,
"breakdown_by_method": {
"cash": 3000.00,
"card": 5500.00,
"sbp": 0
}
}
}5.3. Webhook shift_opened / shift_closed / shift_expiring
// shift_opened
{
"event_type": "shift_opened",
"terminal_id": "...",
"shift_number": 42,
"opened_at": "...",
"opened_by_user_login": "..."
}
// shift_closed (с Z-отчётом inline)
{
"event_type": "shift_closed",
"terminal_id": "...",
"shift_number": 42,
"closed_at": "...",
"z_report": {
"fd_number": 43,
"fp_sign": "...",
"transactions_count": 17,
"total_sum": 8500.00,
"breakdown": {...}
}
}
// shift_expiring — за 1 час до 24h-блокировки
{
"event_type": "shift_expiring",
"terminal_id": "...",
"shift_number": 42,
"opened_at": "2026-04-22T08:00:00Z",
"will_expire_at": "2026-04-23T08:00:00Z",
"minutes_remaining": 60
}Вопросы на встречу
- Смены — вообще есть у PayKeeper на уровне API, или только UI?
- Можем ли мы сейчас хотя бы узнать статус смены через внутренний запрос (неофициальный)?
- Возможность force-close программно — это новая логика, или mPOS это уже умеет?
- X-отчёт (промежуточный, без обнуления) тоже нужен или только Z?
Блокер 6. Обратная синхронизация каталога (PayKeeper → ERP)
Контекст
Каталог товаров в PayKeeper — редактируемый. Владелец или администратор мерчанта через ЛК PayKeeper могут:
- Добавлять новые товары (
POST /products/) - Менять цену, НДС, название (
PATCH /product/{id}) - Добавлять/редактировать категории-теги (
POST /tags/,PATCH /tag/{id}) - Делать массовый импорт через
POST /products/import - Синхронизировать из внешних систем (1С, Yclients, МойСклад, Битрикс24 — коннекторы у PK есть)
Текущее состояние
Нет ни одного способа узнать что в каталоге PayKeeper что-то поменялось:
| Механизм | Статус |
|---|---|
Webhook product.changed / category.changed | ❌ Нет |
Поле updated_at / last_modified на товарах | ❌ Нет (не можем сравнивать временные метки) |
Endpoint GET /products?modified_since={ISO} | ❌ Нет |
Флаг catalog_lock / read-only режим | ❌ Нет |
Единственная альтернатива: забирать весь каталог целиком через GET /products/ и сравнивать с нашим. Дорого и бессмысленно если товаров много.
Почему блокер
Если наш ERP периодически пушит каталог в PayKeeper (наш основной поток), а мерчант параллельно правит цены в ЛК — при следующем push’е мы их правки затрём. Типичные сценарии:
-
Владелец изменил цену в ЛК. В понедельник с 09:00 новая цена 350₽ в PK, в нашем ERP всё ещё 300₽. В 03:00 ночного пересчёта наш push прилетает — цена снова 300₽. Мерчант звонит: «вы сломали мой прайс».
-
Владелец добавил сезонный товар в ЛК (напр. «Яблочный пирог»). Через сутки наш push прилетает с полным каталогом без этого товара — PayKeeper удаляет товар из своего каталога, кассир больше не может его пробить.
-
Мерчант использует 1С-коннектор параллельно с нашим ERP. В ЛК товары из 1С и из нашей админки — перемешиваются и конфликтуют. Ни один из push’ей не знает про чужие правки.
-
Реальные чеки содержат товар которого нет в нашей ERP. Выручка в кассе 450₽ за «Торт», а в нашем каталоге «Торта» нет вообще. Бухгалтерия не может сопоставить. Compliance-риск.
Что просим у PayKeeper
Вариант 1 — push-модель (предпочтительно)
Webhook catalog.product.changed:
{
"event_type": "catalog.product.changed",
"action": "created" | "updated" | "deleted",
"product_id": "pk_prod_789",
"changed_fields": ["price", "name"],
"changed_by_user_id": "lk_user_456",
"changed_at": "2026-04-22T11:00:00Z",
"source": "lk_ui" | "ims_api" | "1c_connector" | "yclients",
"new_values": {
"name": "Торт Наполеон",
"price": 450
}
}Аналогично catalog.category.changed.
Вариант 2 — pull-модель (минимальный фолбэк)
Endpoint GET /products?modified_since={ISO}&limit=100&cursor=...:
Возвращает только товары, изменённые после указанной метки. Требует чтобы PayKeeper хранил updated_at на каждой записи.
Вариант 3 — catalog lock (радикальный)
POST /api/v1/catalog/lock / POST /api/v1/catalog/unlock — флаг который делает ЛК-редактирование запрещённым для этой ТСП. Только API-запись через наш IMS-ключ работает. UI показывает «каталог управляется внешней системой».
Это проще реализовать со стороны PayKeeper, но жёстко ограничивает мерчанта — не для всех ОК.
Обход на нашей стороне
Если ни один из вариантов от PayKeeper не появится:
- Контрактное SLA — в договоре с франшизой прописать: «редактирование каталога в ЛК PayKeeper запрещено, все правки через админку НИРБИ». Нарушение = мы не отвечаем за расхождения.
- Nightly full reconciliation — еженочно
GET /products/, diff с нашим каталогом, алерт в Slack/email оператору при расхождении. Оператор разбирается вручную. - Защитный механизм при push’е — перед push’ем сначала прочитать текущий каталог PayKeeper. Если есть товары которых нет у нас — не удалять их, а пометить в ЛК тегом
external_editи ждать ручного разрешения конфликта.
Минусы обхода: ручная работа + лаг до суток + всё равно возможны конфликты в момент между чтением и записью.
Вопросы на встречу
- Webhook на каталог — есть в roadmap? В бэклоге у вас есть «Обмен с 1С», «Подключение Yclients» — как они узнают о изменениях? Может быть тот же канал подойдёт нам.
- Можно ли флагом отключить ЛК-редактирование каталога для конкретной ТСП?
updated_atна товарах технически есть в БД? Если да — готовы ли отдать в API?- Когда мерчант делает правку через 1С-коннектор — как PayKeeper это различает от правки через ЛК? Есть ли внутренний audit log?
Блокер 7. Автоматизация онбординга ЛК
Контекст
Когда мы регистрируем новую ТТ через Partner API (POST /client/add), PayKeeper создаёт ТСП и возвращает paykeeper_id. Это ещё не готовый ЛК. Дальше происходит:
POST /client/add → paykeeper_id
↓ (AutoInstaller в фоне)
Провижининг ЛК (создаётся хост myshop.server.paykeeper.ru)
Настройка ОФД
Отправка железки-терминала мерчанту
Активация ЛК
Мерчант получает email с доступом
Мерчант логинится в ЛК
Мерчант вручную прописывает настройки webhook
От POST /client/add до момента «в ЛК всё настроено, webhook летит к нам» — проходят часы или дни, и в процессе есть ручные шаги.
Текущее состояние — три проблемы
Проблема 1. Нет webhook’а «ЛК готов»
PayKeeper не уведомляет нас когда провижининг ЛК завершён. Узнать готовность можно только:
- Polling
GET /api/requests— запрашиваем статус задачи из AutoInstaller каждые N секунд. Статусы:in_queue→in_progress→installed/failed. - Polling
GET /api/request_config_fiscal— то же для настройки ОФД. - Polling
GET /device/status— для статуса отгрузки/монтажа железа.
Работает, но неудобно — надо держать scheduler, крутить polling часами.
Проблема 2. Webhook URL прописывается мерчантом вручную
Чтобы к нашему backend’у прилетал webhook об оплате, в настройках ТСП должен быть прописан наш callback URL (напр. https://nirbi.ru/webhooks/paykeeper/{store_id}).
API чтобы сделать это программно — НЕТ. В POST /client/add можно передать только legal_name, site, контакты.
Значит мерчант должен:
- Открыть ЛК
- Зайти в «Настройки уведомлений»
- Вбить URL руками
- Сохранить
Если забыл — в нашем ERP ни одного платежа, хотя касса работает.
Проблема 3. HMAC-secret копируется мерчантом вручную
PayKeeper подписывает webhook’и HMAC-SHA1 по формуле:
key = SHA1(id + sum + clientid + orderid + secret)
Секрет генерируется на стороне PayKeeper при создании ТСП. Показывается в ЛК мерчанту. API для программного получения секрета — НЕТ. Мерчант должен:
- Открыть ЛК
- Скопировать secret из соответствующего поля
- Вставить в нашу админку в поле «секрет PayKeeper»
Без секрета мы не можем проверить подпись → либо принимаем все webhook’и без проверки (риск подмены), либо отбрасываем все (теряем платежи).
Почему блокер
Ручные шаги + человеческий фактор = потеря платежей. При массовом подключении франшизы (20+ ТТ за неделю) вероятность что оператор где-то забыл настроить webhook URL — высокая.
Симптом: касса работает, чеки пробиваются, деньги идут — а в нашем ERP на этой ТТ тишина. Обнаруживается на сверке в конце дня / недели, к тому моменту — десятки пропущенных платежей.
Что просим у PayKeeper
1. Расширение POST /client/add
Разрешить передавать webhook_url и external_id при создании ТСП, получать webhook_secret в ответе:
// Request
{
"legal_name": "ООО Кофейня №1",
"site": "...",
"contact_email": "...",
"contact_phone": "...",
"service_type": "...",
"webhook_url": "https://nirbi.ru/webhooks/paykeeper",
"external_id": "store_uuid_from_nirbi",
"webhook_success_path": "/success",
"webhook_refund_path": "/refund"
}
// Response
{
"paykeeper_id": "210209-027-44",
"webhook_url": "https://nirbi.ru/webhooks/paykeeper",
"webhook_secret": "auto-generated-hmac-secret",
"hostname": "myshop.server.paykeeper.ru",
"status": "provisioning"
}2. Webhook lk.created / onboarding.completed
Когда ЛК активирован и готов принимать платежи — уведомляют нас:
{
"event_type": "lk.created",
"paykeeper_id": "210209-027-44",
"hostname": "myshop.server.paykeeper.ru",
"external_id": "store_uuid_from_nirbi",
"activated_at": "2026-04-22T14:00:00Z"
}Избавляет от polling’а GET /api/requests.
3. GET /api/v1/client/{paykeeper_id}/secret
Программное чтение текущего secret’а — на случай потери / rotation’а:
{
"paykeeper_id": "210209-027-44",
"webhook_secret": "current-active-secret",
"rotated_at": "2026-04-22T14:00:00Z"
}Обход на нашей стороне (для MVP без изменений PayKeeper)
В админке НИРБИ после нажатия «Создать ТТ» — wizard с 4 шагами:
Шаг 1 — автоматический:
POST /client/add→ сохраняемpaykeeper_id- Polling
GET /api/requests→ ждёмinstalled - Polling
GET /device/status→ ждём отгрузку/монтаж - Показываем прогресс-бар оператору (может занять часы)
Шаг 2 — инструкция мерчанту:
- Генерируем email/SMS с доступами к ЛК
- Отправляем мерчанту: «зайдите в ЛК → Настройки → пропишите URL:
https://nirbi.ru/webhooks/paykeeper/{store_id}»
Шаг 3 — ввод secret:
- Оператор / мерчант вводит secret из ЛК в нашу админку
- Сохраняем в
store_paykeeper_mapping.webhook_secret
Шаг 4 — smoke-test:
- Просим мерчанта пробить тестовый чек (50₽)
- Если webhook пришёл за 30 сек → помечаем ТТ
active - Если не пришёл → показываем диагностику «проверьте настройки webhook в ЛК»
Плюс monitoring alert «по этой ТТ нет webhook’ов уже 24ч» — чтобы обнаружить забытые настройки.
Вопросы на встречу
- Расширение
POST /client/addдля webhook_url и external_id — есть в планах? - Уведомления о завершении провижининга ЛК — возможно сделать?
- API чтения secret — критично или есть принципиальная позиция «только в UI мерчанту»?
- Какой сейчас средний срок от
POST /client/addдо реально работающего ЛК? Часы? Дни? Это важно для UX нашего онбординг-wizard’а.
Итоговая таблица «что просим»
Hard blockers — запросы к PayKeeper
| № | Блокер | Статус по бэклогу PayKeeper |
|---|---|---|
| 1 | Cross-device push (планшет→терминал) | 🟡 Возможно уже есть — строки 64, 83, 59 бэклога помечены ✓ (CRM/iiko/mobile app). Уточнить у Рената. |
| 2 | Webhook возвратов/коррекций | 🟡 UI-операции в ЛК есть (возвраты, коррекции), но webhook’и не упомянуты. Уточнить. |
| 3 | GET /payments | 🟡 «Realtime данные об операциях» помечено ✓. Скорее всего API есть — уточнить публичен ли. |
| 4 | Фиск-поля в webhook | ⚠ Фискализация работает, но состав payload webhook’а по бэклогу не понятен. Уточнить. |
| 5 | API смен ФН | ❌ Отчёты (X/Z) есть, но программного API смен в бэклоге не видно. Блокер сохраняется. |
| 6 | Webhook на изменения в каталоге ЛК | ❌ Нет webhook’ов, нет updated_at, нет catalog-lock. Если мерчант / 1С / Yclients поменяют каталог — мы не узнаем, наш push затрёт. Блокер. |
| 7 | Автоматизация онбординга ЛК | ❌ 3 ручных шага при каждой новой ТТ: polling готовности, ввод webhook URL мерчантом, copy-paste secret’а. Не webhook’а lk.created, нет webhook_url в POST /client/add, нет API чтения secret’а. При массовом онбординге — высокий риск пропущенных платежей. |
Workaroundable — решаем у себя
| № | Что | Решение | BR ref |
|---|---|---|---|
| — | Модификаторы / иерархия / цены по ТТ / стоп-листы | Deep-link с готовым cart | BR 2.4 §3.1-3.4 |
| — | Роли кассиров | Маппим наши permissions в flat-flags | BR 2.4 §3.5 |
| — | Error details в bulk import | Мелкие батчи | BR 2.4 §3.6 |
| — | terminal_id → store_id mapping | Таблица store_paykeeper_mapping в Store Service + wrapper Partner API | BR 2.4 §3.7 |
От PayKeeper для п.6 — только 2 уточнения: стабильность ID и связь paykeeper_id ↔ mpos_merchant_id.
Что будет если PayKeeper не успеет (Plan B)
См. 02-ADR/ADR-016 Переход на PayKeeper — там зафиксировано: если ключевые API не готовы к запуску, активируем Plan B — интеграция по схеме iiko через iiko API. PayKeeper откладывается.
Из 7 блокеров критичны для Plan A (PayKeeper) в порядке убывания:
- Cross-device push (блокер 1) — архитектурный блокер. Если нет — MVP либо меняет архитектуру (single-device), либо уходит в Plan B. По бэклогу возможно уже есть — уточнить.
- API смен ФН (блокер 5) — прямой риск простоя, без обхода
- Фиск-поля в webhook (блокер 4) — compliance-блокер, без вариантов
- Webhook возвратов (блокер 2) — без него расходится учёт
- GET /payments (блокер 3) — reconciliation fallback
- Webhook на каталог (блокер 6) — без него правки в ЛК будут конфликтовать с нашим push’ем
- Автоматизация онбординга (блокер 7) — не критично для первой ТТ, но критично для масштабирования франшизы
Триггер Plan B: если PayKeeper не подтверждает разработку или наличие блокера 1 — ADR-016 Plan B активируется.
Альтернативный путь для Plan A (если PayKeeper отказывается от cross-device): пересмотр архитектуры MVP на single-device, где наш софт работает на кассе 3-в-1 PayKeeper как единое приложение. Теряем разделение «официант / кассир», но сохраняем PayKeeper как эквайера.
Ссылки
- BR 2.4
- Gap analysis (исходный)
- Business summary PayKeeper
- ADR-016 Plan B
- Презентация:
_assets/pres/withsheme/Главная презентация.html(слайд 7)