# Парковки и шлагбаумы

> QR на экране у выезда или счёт по номеру билета, вебхук открывает шлагбаум через IP-реле. Динамический и печатный QR, номер машины в metadata, борьба с задержкой.

## Коротко

На парковке схема такая: водитель подъезжает к выезду → вводит номер билета или его госномер считывает камера → ваш сервер считает сумму по времени стоянки и создаёт счёт в Qut Pay → на экране появляется QR → водитель сканирует и подтверждает в Kaspi → к вам приходит вебхук → сервер через IP-реле открывает шлагбаум. QR создаётся **заново на каждый выезд**: окно сканирования около трёх минут. Водитель платит, не выходя из машины.

## Как это работает

| Шаг | Кто | Что делает |
|---|---|---|
| 1 | Водитель | Подъезжает к выезду, вводит номер билета |
| 2 | Контроллер парковки | Запрос на сервер: номер билета, время въезда, госномер |
| 3 | Ваш сервер | Считает сумму и делает `POST /api/v1/invoices` |
| 4 | Экран | Показывает QR и таймер |
| 5 | Водитель | Сканирует в Kaspi и подтверждает |
| 6 | Qut Pay | Шлёт на сервер `invoice.paid` |
| 7 | Ваш сервер | Отправляет команду на IP-реле |
| 8 | Шлагбаум | Открывается, событие пишется в журнал |

После подтверждения вебхук обычно приходит в пределах пяти секунд. На выезде даже это долго: сзади стоит очередь. Поэтому обязательно прочитайте раздел про задержку ниже.

## Какой метод API используется

Основной — `POST /api/v1/invoices` с `kind: "qr"`:

```
POST https://api.qut.kz/api/v1/invoices
X-API-Key: qp_live_…
Idempotency-Key: park-exit-4471

{
  "amount": 600,
  "kind": "qr",
  "description": "Парковка: билет 4471, 2 ч 15 мин",
  "externalOrderId": "park-4471",
  "metadata": {
    "ticket": "4471",
    "plate": "123ABC02",
    "gate": "exit-2",
    "entered_at": "2026-09-14T09:12:00Z"
  }
}
```

Нужны `id`, `qrImageUrl`, `expiresAt`. Остальное: `GET /api/v1/invoices/{id}` — статус, `POST /api/v1/invoices/{id}/cancel` — водитель уехал не заплатив, `POST /api/v1/invoices/{id}/refund` — шлагбаум не открылся.

Если у вас есть абонементы, ежемесячную оплату можно поставить на расписание подписками. Но помните: подписка **выставляет счёт по расписанию**, а подтверждает оплату клиент сам каждый раз.

## Кладите госномер в metadata

В `metadata` обязательно положите: **номер билета**, **госномер машины**, **какой шлагбаум**, **время въезда**. Когда придёт вебхук, вы сразу знаете, какой шлагбаум открывать.

Это же нужно и для разбора споров: на реплику «я оплатил, а он не открылся» вы находите счёт по госномеру и показываете всю последовательность событий. В `externalOrderId` кладите номер билета — так проще искать.

Если парковок несколько, заведите на каждую свой API-ключ: отчётность разделится по точкам.

## Динамический и печатный QR

**Динамический QR (основной путь).** На экране на каждый выезд показывается новый счёт. Сумма посчитана именно для этой машины, оплата привязана к конкретному билету, шлагбаум открывается автоматически. Используйте этот путь.

**Печатный QR (запасной).** У выезда можно повесить постоянный бумажный код, но это не должен быть QR счёта: у счёта окно около трёх минут, и назавтра он перестанет работать. Для постоянного кода используйте **ссылку на оплату** `qut.kz/p/<slug>`, есть и вариант с открытой суммой.

У печатного кода есть слабое место: оплата не привязана к конкретному билету, поэтому шлагбаум сам не откроется. Его открывает охранник вручную или оператор, увидев оплату в кабинете. Это запасной вариант — на случай отказа автоматики или для маленькой парковки.

| | Динамический QR | Печатная ссылка на оплату |
|---|---|---|
| Сумма | Посчитана точно | Вводит клиент или фиксированная |
| Привязка к билету | Есть | Нет |
| Шлагбаум открывается сам | Да | Нет, вручную |
| Срок жизни | Около 3 минут | Не истекает |
| Где уместно | Основной путь | Запасной, небольшая парковка |

## Что нужно со стороны железа

- **Экран и клавиатура на выезде** — показать QR и ввести номер билета.
- **Сервер в облаке** — считает сумму, создаёт счета, принимает вебхуки, шлёт команду на шлагбаум. API-ключ лежит только здесь.
- **IP-реле или контроллер** — подключается ко входу «открыть» у шлагбаума. Сервер шлёт команду, реле замыкает контакт.
- **Связь** — интернет на парковке обязателен. Стоит предусмотреть резервный GSM-канал: без сети шлагбаум работать не будет.

**Не выставляйте реле напрямую в интернет.** Оно должно принимать команды только от вашего сервера. И не кладите API-ключ в реле или локальный контроллер.

## Задержка критична: вебхук и опрос вместе

На выезде даже несколько секунд — это долго. Поэтому держите два канала сразу:

1. **Вебхук — основной.** Пришёл `invoice.paid` — шлагбаум открывается.
2. **Опрос статуса — страховка.** После создания счёта контроллер каждые 2-3 секунды запрашивает `GET /api/v1/invoices/{id}`. Кто первым увидит `paid`, тот и открывает — считайте это одним событием и не открывайте дважды.

Поллер на нашей стороне ходит каждые три секунды, а счета моложе трёх минут проверяются на каждом круге — то есть статус свежего счёта обновляется чаще всего. Про скорость в целом: [Оплата подтверждается медленно](/kb/ru/slow-payments).

Если вебхук не приходит вовсе, причину видно в журнале доставок: [Вебхук не приходит](/kb/ru/webhook-not-arriving).

## На что обратить внимание

- **Идемпотентность.** Если водитель нажмёт «QR» несколько раз, новых счетов быть не должно. Передавайте номер билета в `Idempotency-Key`.
- **Обработчик вебхука должен быть идемпотентным.** Пара `(invoice.id, status)` обрабатывается один раз, иначе шлагбаум откроется дважды и следом проедет чужая машина.
- **Не открылся шлагбаум — возврат.** Деньги пришли, а реле не сработало (пропал интернет, отказало железо) — делайте `refund`. Это самый частый спор на парковке.
- **Поздняя оплата.** Если деньги придут на истёкший счёт, событие придёт с признаком `late: true`. Водителя уже нет — возвращайте деньги.
- **Оставьте охраннику ручное открытие.** При обрыве связи парковка не должна вставать.
- **Пишите в журнал каждое событие**: счёт создан, оплачен, команда на реле ушла, шлагбаум открылся. В спорной ситуации помогает только он.
- **Сначала песочница.** Прогоните всю цепочку ключом `qp_test_…`: счёт → `simulate` → вебхук → команда на реле: [Чем песочница отличается от боевого режима](/kb/ru/sandbox-vs-live).

## Вопросы и ответы

**Можно один раз напечатать QR и повесить на выезде?** QR счёта — нет, у него окно около трёх минут. Для постоянного кода подойдёт ссылка на оплату, но тогда шлагбаум автоматически не откроется.

**У нас нет билетов, госномер читает камера. Подойдёт?** Да. Порядок расчёта суммы и создания счёта тот же, просто в `metadata` вместо билета кладёте госномер.

**Если я продаю абонементы, деньги будут списываться сами?** Нет. Подписка **выставляет счёт** по расписанию, а оплату клиент каждый раз подтверждает в Kaspi сам.

**Водитель не успел оплатить, и время QR вышло.** Создаёте новый счёт — на экране должна быть кнопка «Показать заново». Старый счёт останется `expired`, деньги по нему не спишутся.

**Что будет, если пропадёт интернет?** Не работают ни создание счёта, ни вебхуки. Поэтому заранее предусмотрите резервный GSM-канал и ручное открытие охранником.
