# Служба доставки

> Курьер выставляет счёт в момент доставки, клиент платит со своего телефона. Через Telegram-бота или своё приложение, с номером заказа и метаданными, с возвратами и обработкой недоставленных заказов.

## Коротко

Курьер выставляет счёт прямо у двери: через Telegram-бота или из вашего курьерского приложения. Клиент достаёт телефон, сканирует QR или подтверждает счёт, пришедший в приложение Kaspi. Деньги идут напрямую на Kaspi-счёт компании, курьер не возит наличные. Номер заказа кладёте в `externalOrderId`, курьера и маршрут — в `metadata`, и отчётность собирается сама.

## Сценарий

Заказ приняли на сайте или по телефону, оплата — при доставке. Как это было раньше: курьер берёт наличные, не хватает сдачи, в конце дня сдаёт деньги в кассу, часть теряется, отчёт появляется только в конце месяца.

Что нужно: курьер у двери нажимает одну кнопку, клиент платит с телефона, деньги сразу уходят на счёт компании, диспетчер видит оплату заказа в реальном времени.

## Схема работы по шагам

1. **Курьер приехал.** Передаёт заказ, подтверждает сумму (она может отличаться, если что-то заменили).
2. **Выставляется счёт.** Курьер даёт команду `/invoice` в Telegram-боте или нажимает «Оплата» в своём приложении. За этим стоит вызов `POST /api/v1/invoices`.
3. **Клиент платит.** Два варианта: курьер показывает QR на экране либо через `kind: "phone"` счёт приходит push-уведомлением в приложение Kaspi.
4. **Приходит подтверждение.** Отправляется вебхук `invoice.paid`, в приложении курьера заказ становится оплаченным. На практике это занимает несколько секунд.
5. **Курьер отдаёт заказ** и едет по следующему адресу.
6. **Диспетчер видит картину.** В кабинете или в вашей системе видно, какие заказы на маршруте оплачены.

## Какие методы API нужны

| Что делает | Метод |
|---|---|
| Счёт в момент доставки | `POST /api/v1/invoices` |
| Счета на маршрут заранее | `POST /api/v1/invoices/bulk`, 1-100 |
| Проверить статус | `GET /api/v1/invoices/{id}` |
| Клиент отказался | `POST /api/v1/invoices/{id}/cancel` |
| Товар вернули | `POST /api/v1/invoices/{id}/refund` |
| Узнать об оплате | Вебхук `invoice.paid` |

## QR или счёт на телефон

| Ситуация | Что выбрать |
|---|---|
| Клиент у двери, у курьера есть экран | QR |
| Заказ принимает другой человек, а платит заказчик | Счёт на телефон |
| У двери неудобно, плохая погода | Счёт на телефон |
| У клиента нет приложения Kaspi | QR или ссылка `payUrl` |
| В сумме есть тиыны | QR (у счёта на телефон целые тенге) |

При отправке счёта на телефон номер должен быть в формате `7XXXXXXXXXX`. Полная разница: [QR-счёт или счёт по телефону](/kb/ru/qr-vs-phone).

**Окно сканирования QR ограничено.** Если клиент не отсканировал сразу, окно может закончиться — тогда закрываете старый счёт через `cancel` и создаёте новый. Точное время берите из поля `expiresAt`, не зашивайте константу в код.

## Номер заказа и метаданные

Если правильно расставить пометки при создании счёта, отчётность соберётся сама:

- **`externalOrderId`** — ваш номер заказа. Это поле возвращается в вебхуке и видно в списке и в выгрузке. Вопрос «к какому заказу относится платёж» решается одним поиском.
- **`metadata`** — произвольный JSON. Кладите туда идентификатор курьера, номер маршрута, смену, зону, время доставки.
- **`description`** — текст, который видит клиент. Пишите в формате «Заказ №1204, доставка». У QR-счёта 100 символов, у счёта на телефон — 60.

Если нужна отчётность по курьерам, хватит идентификатора курьера в `metadata` — при выгрузке CSV группируете по этому полю. Подробнее: [Metadata и номер заказа](/kb/ru/metadata-and-orders).

## Вариант без кода: Telegram-бот

Писать курьеру отдельное приложение необязательно — хватает Telegram-бота. Команды:

| Команда | Что делает |
|---|---|
| `/invoice` | Создаёт счёт |
| `/last` | Показывает последние счета |
| `/today` | Итоги за день |
| `/status` | Статус одного счёта |
| `/cancel` | Отменяет счёт |
| `/support` | Написать в поддержку |

Достаточно, чтобы у курьера на телефоне был Telegram. Бот привязывается из кабинета. Подробнее: [Команды Telegram-бота](/kb/ru/telegram-bot-commands).

При небольшом объёме доставок подойдёт и такой вариант: диспетчер создаёт счёт вручную в [кабинете](https://qut.kz/app) и отправляет ссылку курьеру.

## Возвраты

Если клиент вскрыл заказ и вернул его, деньги возвращаются через `POST /invoices/{id}/refund` с телом `{ amount?, reason? }`. Без `amount` возврат полный, с `amount` — частичный (например, за одну позицию).

Чтобы не вернуть дважды: если ответ на запрос неизвестен (оборвалась сеть, таймаут), не повторяйте сразу. Сначала прочитайте список возвратов через `GET /invoices/{id}`. Подробнее: [API возвратов](/kb/ru/refunds-api).

Право на возврат лучше оставить диспетчеру, а не курьеру: уберите scope `refunds:write` из ключа, которым пользуются курьеры.

## Недоставленный заказ

Клиента нет дома, он не берёт трубку или отказался от заказа. Порядок:

1. **Счёт ещё не создан** — делать ничего не нужно, помечаете заказ недоставленным в своей системе.
2. **Счёт создан, но не оплачен** — закрываете его через `POST /invoices/{id}/cancel`. Открытые счета не должны копиться.
3. **Клиент оплатил, а потом отказался** — делаете возврат.
4. **Пришла поздняя оплата** — это случается. Если деньги придут по счёту `cancelled` или `expired`, событие `invoice.paid` придёт с признаком `late: true`. Тогда либо доставляете заказ повторно, либо возвращаете деньги. Предусмотрите эту ветку в своей системе: [Поздняя оплата](/kb/ru/late-payment).

## Особые замечания

- **Курьер не возит наличные** — в этом основная выгода. Но часть клиентов всё равно захочет заплатить наличными, оставьте оба способа.
- **Номер кассира виден клиенту** в уведомлении о счёте. Это нормальная работа Kaspi. Виден номер кассира компании, а не личный номер курьера.
- **Не давайте API-ключ курьеру.** Курьерское приложение обращается к вашему серверу, а сервер — к нам. Ключ не должен попадать в мобильное приложение.
- **Интернет.** На части адресов связь плохая. Если создание счёта не удалось, в приложении курьера должна быть кнопка повтора — но защитите её `Idempotency-Key`, чтобы не появились дубли.
- **Если сумма изменилась**, закройте старый счёт и создайте новый. Менять сумму существующего счёта нельзя.
- **Сначала песочница.** На ключе `qp_test_…` прогоните весь цикл: счёт, симуляция оплаты, возврат, отмена.

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

**Нужно ли курьеру отдельное устройство?** Нет. Telegram-бот работает на любом телефоне. Если у вас есть своё приложение, в него добавляется кнопка «Оплата».

**Может ли курьер увидеть чужие счета?** Если привязать API-ключ к конкретному кассиру, он видит только счета, прошедшие через этого кассира, на остальные вернётся 404.

**Деньги приходят на счёт курьера?** Нет. Деньги приходят напрямую на Kaspi-счёт компании.

**Что делать, если клиент передумал у двери?** Если счёт не оплачен, закрываете его через `cancel`. Если оплачен — делаете возврат.

**Можно ли создать счета на весь маршрут заранее?** Технически да, через `bulk`. Но окно сканирования QR ограничено, поэтому счёт лучше создавать в момент доставки.
