# Интеграция под ключ с ИИ-ассистентом

Этот документ — задание для ИИ-ассистента в проекте мерчанта: Claude Code, Cursor или любого другого, который пишет код по инструкции. В нём по порядку работ перечислено, что построить для приёма платежей через STONS, какие решения принять и как, и что проверить в конце. Каждое правило ссылается на документ API, в котором оно записано; при расхождении прав документ API.

## Словарь

Кабинет — веб-приложение STONS, в котором владелец проекта управляет проектом. Кабинет и API называют некоторые вещи по-разному; эта документация пользуется терминами из первого столбца.

| Термин | В кабинете | Что это |
| --- | --- | --- |
| кабинет | «Кабинет» | Веб-приложение STONS по адресу `https://app.stons.io`, в котором владелец проекта управляет проектом |
| мерчант | проект («Проекты») | Сайт, бот или сервис, который принимает платежи со своими ключами API и настройками |
| владелец проекта | учётная запись, создавшая проект | Выпускает ключи API и задаёт адрес вебхуков, адреса возврата, токены, которые принимает проект, и кто платит комиссии. Видит доставки вебхуков и отправляет недоставленные заново |
| оператор | поддержка | Сотрудник STONS: проверяет проекты, ведёт вайтлист выплат, подтверждает выплаты, которые ждут человека, назначает комиссии и лимиты, разрешает проекту ограниченные режимы — постоянный адрес на пользователя и прямые сборы на собственный кошелёк проекта |
| инвойс | счёт, в разделе «Платежи» | Просьба заплатить конкретную сумму до дедлайна: `POST /v1/invoices` |
| ключ API | «Ключ API» | `sk_<16 шестнадцатеричных символов>_<43 символа base64url>`, передаётся в заголовке `X-Api-Key`. Показывается один раз, при выпуске |
| секрет вебхуков | «Секрет подписи вебхуков» | Ключ подписей HMAC-SHA256 у вебхуков и у редиректов со страницы оплаты. Показывается один раз, при создании проекта. Используется как строка, без преобразований |
| актив | токен и сеть | Идентификатор токена в сети, `<ТИКЕР>_<СЕТЬ>`: `USDT_TRON`, `USDC_SOLANA` |
| страница оплаты | ссылка на оплату | Собственная страница сервиса для одного инвойса, по ссылке `checkout_url` из инвойса |
| подтверждение | — | Блок, добавленный поверх блока с переводом. Перевод засчитывается, когда у него столько подтверждений, сколько требует его актив |
| пополнение | — | В режиме `per_user`: перевод на постоянный адрес пользователя, который не оплачивает ни один инвойс. Зачисляется на баланс проекта, а `deposit.confirmed` приходит с `invoice_id`, равным `null` |
| выплата | — | Перевод с горячего кошелька сервиса на адрес из вайтлиста мерчанта: `POST /v1/payouts` |
| вайтлист | — | Адреса для выплат мерчанта, по активам. Их вносит оператор; новый адрес становится доступен после задержки, по умолчанию через 24 часа |

## Шаг 0. Что спросить у человека

Прежде чем писать код, получите у владельца проекта:

1. **Ключ API.** Владелец выпускает его в настройках проекта в кабинете — см. [аутентификацию](https://stons.io/ru/docs/authentication.md).
2. **Секрет вебхуков.** Кабинет показывает его один раз, вместе с первым ключом, при создании проекта — см. [вебхуки](https://stons.io/ru/docs/webhooks.md#подпись).
3. **Прошёл ли проект проверку.** Пока проект не одобрен, инвойсы не создаются: `403 merchant_not_approved`. Ключи при этом уже работают, так что код писать можно — см. [коды ошибок](https://stons.io/ru/docs/errors.md#перечень).
4. **Режим адресов** проекта: `per_invoice` — новый адрес под каждый инвойс, `per_user` — постоянный адрес пользователя. Проект создаётся в `per_invoice`; `per_user` — ограниченный режим, его разрешает оператор по проектам, и только после этого проект можно перевести в него. Режим может измениться позже, и смена решает, где будет собран **следующий** платёж: уже выданные адреса остаются при своём режиме и зачисляются как раньше — см. [адреса](https://stons.io/ru/docs/addresses.md#режимы). Два отказа значат разное: `422 address_mode_disabled` — платформа приостановила режим для всех ([режим на паузе](https://stons.io/ru/docs/addresses.md#режим-на-паузе)); `422 mode_not_allowed` — этому проекту режим не разрешён. Спрашивайте у владельца, в каком режиме проект сейчас, а не считайте режим неизменным.
5. **Как показывать оплату:** своей страницей в продукте или страницей оплаты. Чтобы задать верный вопрос, используйте правила шага 3.
6. **Токены:** какие активы продукт предлагает. Приём работает в TRON, в семи EVM-сетях, в TON и в Solana — в USDT, USDT0, USDC и нативной монете каждой сети; `USDC_TRON` оператор выключил целиком — см. [сети и токены](https://stons.io/ru/docs/networks.md). **Не зашивайте этот список в код.** Активы, которые проект может принять прямо сейчас, с сетью, числом знаков и минимальной суммой каждого отдаёт `GET /v1/assets`; какие токены принимает проект, решают его настройки в кабинете — см. [список активов](https://stons.io/ru/docs/invoices.md#список-активов).
7. **Кто платит комиссию сервиса** — настройка проекта «Кто платит комиссии». Если платит покупатель, сумма к оплате больше цены — см. [комиссии](https://stons.io/ru/docs/invoices.md#комиссии).
8. **Для страницы оплаты:** адреса возврата и отмены, которые владелец задаёт в настройках проекта как «После успешной оплаты» и «Вернуться в магазин / при неудаче» — см. [адреса возврата](https://stons.io/ru/docs/checkout.md#адреса-возврата).
9. **Нужны ли выплаты.** Если нужны, каждый адрес для выплат оператор должен заранее внести в вайтлист — см. [выплаты](https://stons.io/ru/docs/payouts.md#куда-можно-платить).

Когда обработчик вебхуков готов, направьте проект на него сами: `PATCH /v1/project` с полем `webhook_url`, тем же ключом — в кабинет заходить никому не нужно ([настройки проекта](https://stons.io/ru/docs/authentication.md#настройки-проекта)). `GET /v1/project` сначала покажет, что задано сейчас и одобрен ли проект. Пока адрес не задан, проект не получает событий, и события того времени позже не досылаются — см. [доставку](https://stons.io/ru/docs/webhooks.md#доставка).

Тем же ключом задаются адреса возврата страницы оплаты (`return_url`, `cancel_url`) и выбираются принимаемые токены (`GET` и `PUT /v1/project/assets`). Два ограничения, которые стоит знать заранее: адрес вебхука можно заменить, но не удалить, а секрет подписи не возвращает ни один маршрут — попросите его у владельца один раз, при создании проекта.

Не придумывайте значения за человека: ключ и секрет существуют только у него.

## Шаг 1. Переменные окружения

Ключ и секрет — только на сервере продукта, в менеджере секретов или переменных окружения. Их нет в браузере, мобильном приложении, репозитории и логах — см. [чек-лист](https://stons.io/ru/docs/checklist.md#ключи-и-доступ). Имена переменных выбирает проект, например:

```sh
PAYMENTS_API_URL=https://api.stons.io
PAYMENTS_API_KEY=sk_...
PAYMENTS_WEBHOOK_SECRET=...
```

Адрес API — с `https://`: по `http://` ключ ушёл бы открытым текстом ещё до перенаправления — см. [аутентификацию](https://stons.io/ru/docs/authentication.md#сеть).

## Шаг 2. Клиент API

- Базовый адрес — `https://api.stons.io/v1`, ключ — в заголовке `X-Api-Key`, тела запросов и ответов — JSON с `Content-Type: application/json`.
- Описание API в формате OpenAPI 3.1 — `https://api.stons.io/v1/openapi.json`. Тела запросов принимают только описанные поля: неизвестное поле получает `400 validation_failed`.
- **Суммы — строки** в единицах актива: `"49.9"` в запросе, `"49.900000"` в ответе. У суммы актива в ответе после точки ровно столько цифр, сколько знаков у актива, поэтому без точки она даёт целое число в минимальных единицах. В сумме запроса знаков после точки не больше, чем у актива — см. [суммы](https://stons.io/ru/docs/invoices.md#суммы).
- Ошибка — тело `{"error": {"code", "message", "details"}}`. Решения принимайте по `error.code`, а не по тексту `message` — см. [коды ошибок](https://stons.io/ru/docs/errors.md).
- `429 rate_limited` — повторить через число секунд из заголовка `Retry-After`, см. [лимиты запросов](https://stons.io/ru/docs/authentication.md#лимиты-запросов).
- `500 internal_error` и `503 service_unavailable` — повторить позже с тем же `order_id` или `reference`. Что делать при остальных кодах — в [перечне кодов](https://stons.io/ru/docs/errors.md#перечень).
- `X-Request-Id` из ответа сохраняйте в логах: по нему поддержка находит запрос.

## Шаг 3. Выбрать, как показывать оплату

Примените первое подходящее правило:

- **Если монету и сеть должен выбрать покупатель**, создайте инвойс без `asset` и отправьте покупателя по `checkout_url` (шаг 5Б). У такого инвойса нет адреса до выбора, поэтому показать его на своей странице нельзя. Если у сервиса нет страницы оплаты, запрос получит `422 asset_required`: тогда примените следующее правило — см. [токен выбирает покупатель](https://stons.io/ru/docs/invoices.md#токен-выбирает-покупатель).
- **Если цены у продукта в долларах, а монета известна заранее**, создайте инвойс с `asset` и `price_currency: "USD"`, передав цену в `amount`. Сервис сам пересчитает её по курсу и вернёт `address`, `amount_expected` и `quote.rate` в том же ответе — считать курс самим не нужно и не следует. Чтобы показать «≈ 0,45 SOL» до создания, спросите `GET /v1/rates` — см. [цену в долларах в выбранной монете](https://stons.io/ru/docs/invoices.md#цена-в-долларах-в-выбранной-монете).
- **Если у продукта свой экран оплаты**, создайте инвойс с `asset` и покажите на нём `address` и `amount_expected` (шаг 5А).
- **Иначе** создайте инвойс с `asset` и отправьте покупателя по `checkout_url` (шаг 5Б). Если `checkout_url` равен `null`, страницы оплаты у сервиса нет: используйте шаг 5А.

Независимо от выбора:

- **Если режим `per_user`**, передавайте `external_user_id` в каждом инвойсе. Постоянный адрес пользователя без инвойса выдаёт `POST /v1/addresses` — см. [адреса](https://stons.io/ru/docs/addresses.md#постоянный-адрес-пользователя).
- **Если продукт зачисляет пополнения `per_user`** на балансы пользователей, проверяйте каждый `deposit.confirmed` с `invoice_id`, равным `null`, запросом `GET /v1/deposits/{tx_hash}` и зачисляйте пользователю, только когда у перевода с `log_index` из события `status` = `confirmed` и `credited` = `true`, — см. [проверку перевода](https://stons.io/ru/docs/addresses.md#проверить-перевод).

## Шаг 4. Создать инвойс

`POST /v1/invoices` — см. [создание инвойса](https://stons.io/ru/docs/invoices.md#создать-инвойс).

```sh
curl -s -X POST https://api.stons.io/v1/invoices \
  -H "X-Api-Key: $PAYMENTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id": "ord_1001", "asset": "USDT_TRON", "amount": "49.9", "expires_in": 3600}'
```

- `order_id` — номер заказа в продукте, 1–200 видимых ASCII-символов без пробелов. Это ключ идемпотентности: при таймауте повторяйте запрос **с тем же** `order_id` и теми же полями — вернётся тот же инвойс с кодом `200` вместо `201`, второй не создастся. Тот же `order_id` с другой суммой, активом, пользователем или адресами возврата получает `409 order_id_conflict` — см. [идемпотентность](https://stons.io/ru/docs/invoices.md#идемпотентность).
- `expires_in` — необязательно: от 300 (5 минут) до 259200 секунд (72 часа), по умолчанию 3600.
- Сумма не меньше минимального депозита актива, иначе `422 amount_below_minimum`.
- Сохраните у заказа `invoice_id`: по нему дальше проверяется статус.

Без `asset` поле `amount` — цена в долларах США, а необязательный `currencies` — из каких активов выбирает покупатель. В ответе `asset`, `network`, `address` и суммы в активе — `null`, а цена лежит в `price`:

```sh
curl -s -X POST https://api.stons.io/v1/invoices \
  -H "X-Api-Key: $PAYMENTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id": "ord_1002", "amount": "49.9"}'
```

## Шаг 5А. Оплата на своей странице

Покажите покупателю из ответа:

- `address` — **целиком**, без сокращений вида «начало…конец»: на сокращённые адреса охотятся подменой похожим адресом — см. [показ адреса](https://stons.io/ru/docs/addresses.md#показ-адреса);
- `amount_expected` и актив. Это сумма к оплате: если комиссию сервиса платит покупатель, она больше цены заказа, а сама цена — в `amount_requested`, см. [комиссии](https://stons.io/ru/docs/invoices.md#комиссии);
- сеть актива по названию из `network_name` — TRON, одна из EVM-сетей, TON или Solana — с предупреждением, что перевод другого токена или в другой сети не зачисляется автоматически — см. [чек-лист](https://stons.io/ru/docs/checklist.md#отображение);
- срок оплаты, `expires_at`;
- `payment_uri` — ссылка, открывающая кошелёк с заполненным переводом, если она не `null`: покупателю не придётся ни копировать адрес, ни набирать сумму. Сейчас её даёт TON; в Solana и остальных сетях она `null`, и адрес с суммой копируют со страницы;
- QR-код — попросите его параметром `include=qr` при создании или чтении инвойса и покажите `qr.address` как `src` у `<img>`. В нём тот же адрес, и это та же картинка, которую рисует наша страница оплаты, — библиотека QR на вашей стороне не нужна, см. [QR-код](https://stons.io/ru/docs/invoices.md#qr-код).

Что не меняется от инвойса к инвойсу — число подтверждений актива (`confirmations`), его метку (`icon`), стандарт токена и минимальную сумму — берите один раз из `GET /v1/assets` и держите у себя: по `confirmations` рисуется «2 из 20», пока перевод набирает подтверждения.

Проекту могут быть включены прямые сборы: деньги каждого платежа пересылаются на собственный кошелёк проекта за вычетом комиссии сервиса, а не хранятся у нас. Режим разрешает проекту оператор; кошелёк владелец проекта вписывает сам в кабинете, а сотрудник сервиса его одобряет — до одобрения адрес денег не получает. В API от этого не меняется ничего, и товар в любом случае отдаётся на `confirmed`; меняется смысл двух вещей: баланс такого проекта остаётся нулевым, потому что деньги уже на его кошельке, а `settled` значит этот кошелёк, а не хранилище сервиса. В активе, по которому действующего кошелька у проекта нет, платёж уходит в хранилище, и проект получает его заявкой на вывод, как любой другой, — так что нулевой баланс тоже не обещание. Не делайте интеграцию, которая ждёт роста баланса, — см. [куда уходят деньги](https://stons.io/ru/docs/invoices.md#куда-уходят-деньги).

Пришедшие деньги читаются запросом `GET /v1/balance`: баланс по реестру по каждому активу, что ещё не подтверждено, и то и другое в долларах — см. [баланс](https://stons.io/ru/docs/balance.md).

Статус узнавайте из вебхуков (шаг 6) и запросом `GET /v1/invoices/{id}`. Если вебхук не дошёл, пройдите [ленту изменений](https://stons.io/ru/docs/invoices.md#лента-изменений) — шаг 7. Экран, который опрашивает инвойс, держит `ETag` последнего ответа и присылает его обратно в `If-None-Match`: «ничего не изменилось» тогда стоит одного `304` без тела, см. [условные чтения](https://stons.io/ru/docs/authentication.md#условные-чтения).

## Шаг 5Б. Страница оплаты

Отправьте покупателя по `checkout_url` из ответа. Если инвойс создан без `asset`, страница сначала даёт покупателю выбрать монету и сеть. Страница показывает сумму, адрес, QR-код и статус, а когда у инвойса появляется исход, возвращает покупателя на адрес возврата или отмены — см. [страницу оплаты](https://stons.io/ru/docs/checkout.md).

- Для отдельного инвойса адреса можно переопределить полями `return_url` и `cancel_url` в `POST /v1/invoices`, но только в пределах сайтов проекта, иначе `422 redirect_url_not_allowed` — см. [адреса возврата](https://stons.io/ru/docs/checkout.md#адреса-возврата).
- Пока инвойс не оплачен, страница показывает кнопку «Вернуться в магазин» на адрес отмены со `status=created`, `status=seen` или `status=partially_paid`. Инвойс ещё можно оплатить: заказ по такому переходу не отменяйте — см. [переход до исхода](https://stons.io/ru/docs/checkout.md#переход-до-исхода).

## Шаг 6. Обработчик вебхуков

Сервис шлёт `POST` на адрес вебхуков проекта. Обработчик — см. [вебхуки](https://stons.io/ru/docs/webhooks.md):

1. Читает **сырое тело** запроса, до разбора JSON.
2. Проверяет подпись: `X-Signature: sha256=<hex>` — это HMAC-SHA256 секретом вебхуков по строке `<X-Timestamp>.<сырое тело>`. Отклоняет вебхук, если `X-Timestamp` отличается от текущего времени больше чем на 5 минут, и сравнивает подписи за постоянное время. Код проверки на Node.js или Python и пример для проверки реализации возьмите из [подписи](https://stons.io/ru/docs/webhooks.md#подпись).
3. Отбрасывает дубли по `id` события: одно событие может прийти несколько раз.
4. Отвечает `2xx` за 10 секунд, в том числе на типы событий, которые не обрабатывает, а работу выполняет асинхронно: без ответа `2xx` вебхук доставляется повторно.
5. **Не доверяет телу.** Для `invoice.*` и для `deposit.*` с `invoice_id` запрашивает `GET /v1/invoices/{id}`, для `deposit.*` без `invoice_id` — `GET /v1/deposits/{tx_hash}`, для `payout.*` — `GET /v1/payouts/{id}`, и действует по ответу API — см. [главное правило](https://stons.io/ru/docs/webhooks.md#главное-правило).

Порядок доставки не гарантирован: `invoice.confirmed` может прийти раньше `invoice.seen`. Ориентируйтесь на состояние из API.

## Шаг 7. Когда отдавать товар

По статусу из `GET /v1/invoices/{id}` — см. [статусы](https://stons.io/ru/docs/invoices.md#статусы):

| Статус | Что делать |
| --- | --- |
| `created`, `seen` | Ждать. `seen` — перевод замечен, но ещё может откатиться |
| `partially_paid` | Не отдавать товар. До срока пришла часть суммы: если продукт показывает оплату сам, покажите покупателю `amount_remaining` и тот же адрес — см. [частичную оплату](https://stons.io/ru/docs/invoices.md#частичная-оплата) |
| `confirmed`, `settled` | Отдать товар. Переплата — тоже `confirmed`, `amount_received` больше `amount_expected` |
| `underpaid` | Срок вышел, пришла часть суммы: решает продукт |
| `expired` | Срок вышел без платежа: закрыть заказ |
| `paid_late` | Платёж пришёл после срока: решает продукт |

- Свою цену сверяйте с `amount_requested` у инвойса, созданного с `asset`. У инвойса, созданного без `asset`, сверяйте с `price.amount`: `amount_requested` до выбора — `null`, а после — цена, переведённая по курсу, см. [цену и курс](https://stons.io/ru/docs/invoices.md#цена-и-курс).
- `expired` и `underpaid` ещё могут стать `paid_late`, если деньги придут позже — см. [страницу оплаты](https://stons.io/ru/docs/checkout.md#страница-для-покупателя).
- Выдачу товара делайте идемпотентной по `order_id`: повтор вебхука, второе событие того же изменения или редирект не должны выдать товар второй раз.

## Шаг 8. Возврат со страницы оплаты

Если используется страница оплаты, покупатель приходит на адрес возврата после `confirmed`, `settled` или `paid_late`, а на адрес отмены — после `expired` или `underpaid` либо по кнопке «Вернуться в магазин». Параметры — `invoice_id`, `order_id`, `status`, `ts` и `signature`. Обработчик — см. [подпись редиректа](https://stons.io/ru/docs/checkout.md#подпись):

1. Проверяет подпись: HMAC-SHA256 секретом вебхуков по строкам `redirect`, `ts`, `invoice_id`, `status`, `order_id`, соединённым символом `\n`, и окно `ts` в 5 минут. Код проверки и пример — в разделе по ссылке.
2. Проверяет, что `invoice_id` — инвойс этого `order_id`.
3. Берёт статус из `GET /v1/invoices/{id}`, а не из параметра. **Товар по редиректу не отдаётся.**

## Шаг 9. Выплаты — если нужны

`POST /v1/payouts` — см. [выплаты](https://stons.io/ru/docs/payouts.md):

- Платить можно только на адреса из вайтлиста мерчанта и только после их задержки. Адрес для выплаты никогда не берётся из истории транзакций.
- `reference` — ключ идемпотентности: повтор с тем же `reference`, активом, адресом и суммой возвращает ту же выплату с кодом `200` вместо `202`. Другие значения получают `409 reference_conflict` — см. [идемпотентность](https://stons.io/ru/docs/payouts.md#идемпотентность).
- `pending_review` — не ошибка: выплата принята и ждёт решения человека, а `review_reasons` говорит почему.
- `503 payouts_halted` — повторить позже с тем же `reference`.
- Исход берите из `GET /v1/payouts/{id}`. Вебхуки `payout.*` — сигнал сделать этот запрос, и не у каждой смены статуса он есть: какие статусы окончательные и какие переходы присылают вебхук — в [статусах выплат](https://stons.io/ru/docs/payouts.md#статусы).

## Шаг 10. Перед запуском

Пройдите [чек-лист перед продом](https://stons.io/ru/docs/checklist.md) целиком и проверьте весь путь на небольшой сумме: инвойс, оплата, вебхук, проверка статуса, выдача товара.

## Чего не делать никогда

- Не зашивайте в код число знаков и не превращайте суммы в числа с плавающей точкой: храните строки или целые числа в минимальных единицах — см. [суммы](https://stons.io/ru/docs/invoices.md#суммы).
- Не сокращайте адрес нигде, где он показывается или пишется в лог — см. [показ адреса](https://stons.io/ru/docs/addresses.md#показ-адреса).
- Не приводите адрес к нижнему регистру и не нормализуйте его иначе. В Solana адрес — base58, и регистр — его часть: адрес, приведённый к нижнему регистру, — это адрес, ключа к которому ни у кого нет. Храните и сравнивайте адрес ровно таким, каким его вернул API, — см. [идентификаторы и знаки после запятой](https://stons.io/ru/docs/networks.md#идентификаторы-и-знаки-после-запятой).
- Не отдавайте товар по телу вебхука или по редиректу без `GET /v1/invoices/{id}` — см. [главное правило вебхуков](https://stons.io/ru/docs/webhooks.md#главное-правило) и [страницы оплаты](https://stons.io/ru/docs/checkout.md#главное-правило).
- Не отдавайте товар при `seen`.
- Не разбирайте и не сериализуйте тело вебхука заново перед проверкой подписи — см. [подпись](https://stons.io/ru/docs/webhooks.md#подпись).
- Не создавайте второй инвойс или вторую выплату после таймаута: повторяйте запрос с тем же `order_id` или `reference`.
- Не берите адрес для выплаты из истории транзакций — см. [выплаты](https://stons.io/ru/docs/payouts.md#куда-можно-платить).
- Не кладите ключ API и секрет вебхуков в браузер, мобильное приложение, репозиторий и логи — см. [аутентификацию](https://stons.io/ru/docs/authentication.md).
- Не принимайте решений по `error.message` — см. [коды ошибок](https://stons.io/ru/docs/errors.md).

## Самопроверка

Прежде чем сообщить, что интеграция готова, подтвердите в коде каждый пункт:

- [ ] Ключ и секрет читаются из окружения сервера и не пишутся в логи.
- [ ] Каждый вызов API идёт на `https://`, передаёт `X-Api-Key` и обрабатывает `error.code`, `429` с `Retry-After`, а также `500` и `503 service_unavailable` повтором с тем же ключом идемпотентности.
- [ ] Суммы везде — строки или целые числа в минимальных единицах, в том числе в базе продукта.
- [ ] Инвойс создаётся с постоянным `order_id`, а `invoice_id` сохраняется у заказа.
- [ ] Страница показывает адрес целиком, `amount_expected`, актив и сеть — или покупатель уходит по `checkout_url`.
- [ ] Обработчик вебхуков проверяет подпись по сырому телу с окном в 5 минут, отбрасывает дубли по `id`, отвечает `2xx` за 10 секунд и проходит пример из [подписи](https://stons.io/ru/docs/webhooks.md#подпись).
- [ ] Товар отдаётся, только когда `GET /v1/invoices/{id}` возвращает `confirmed` или `settled`, и один раз на `order_id`.
- [ ] У продукта есть решение для `underpaid`, `paid_late` и переплаты.
- [ ] При `partially_paid` товар не отдаётся.
- [ ] Обработчики адресов возврата и отмены проверяют подпись и сами не отдают товар и не отменяют заказ.
- [ ] Выплаты, если они есть, создаются с постоянным `reference`, а исход берут из `GET /v1/payouts/{id}`.
- [ ] Весь путь проверен на небольшой сумме, и [чек-лист перед продом](https://stons.io/ru/docs/checklist.md) пройден.
