> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Платёжные провайдеры

> Настройте Telegram Stars, CryptoBot, YooKassa или ручные платежи — учётные данные, валюты, вебхуки, тестирование и поведение возвратов у каждого

Omniflow проводит деньги через единый контракт, основанный на объявляемых возможностях. Каждый
адаптер реализует одни и те же шесть методов — имя, возможности, создание, опрос, возврат, проверка
вебхука — и три необязательных уточнения: списание с сохранённого способа оплаты, восстановление
счёта по ссылке мерчанта и объявление валют, в которых он проводит расчёты. Всё, что находится ниже
этого контракта (сверка, возвраты, чеки, операторская лента событий), работает с собственной записью
Omniflow `payment_intents`, а не с объектной моделью конкретного провайдера.

Объявление возможностей вместо особой обработки каждого провайдера — это то, что позволяет экрану
объяснить, *почему* что-то не предлагается, а не просто отказать. Тариф с ценой только в `RUB` не
покажет Telegram Stars, а у платежа через CryptoBot не будет кнопки возврата, потому что оба факта
приходят от самого адаптера.

Эта страница — руководство по настройке. Конечный автомат заказа, классификации расчётов и то, что
порождает оплаченный заказ, описаны в [заказах и платежах](https://omniflow-vpn.mintlify.site/ru/commerce/orders-payments.md).

## Четыре адаптера

| Адаптер | Слаг | Валюты | Вебхуки | Опрос | Возвраты | Рекуррентные платежи |
| - | - | - | - | - | - | - |
| Telegram Stars | `telegram_stars` | только `XTR` | Нет — расчёт происходит через аутентифицированный поток обновлений бота | Нет | Только полное списание и только при наличии токена бота и определяемого плательщика | Нет |
| CryptoBot | `cryptobot` | 16 фиатных кодов | Да, HMAC-SHA256 по сырому телу | Да | **Нет** | Нет |
| YooKassa | `yookassa` | `RUB`, `USD`, `EUR` | Да, без подписи — проверяются повторным запросом | Да | Полные и частичные | Заявлены |
| Manual | `manual` | Любые | Нет | Нет | Заявлены, регистрируются как запись в ожидании | Нет |

### Как адаптер становится доступным

Адаптеры собираются из переменных окружения при старте процесса, и в API, и в воркере, поэтому у
двух процессов не может оказаться разного набора провайдеров — иначе получился бы отказ, при котором
воркер не может списать продление через того провайдера, которым платил клиент.

* `manual` и `telegram_stars` регистрируются **всегда**.
* `cryptobot` регистрируется, когда задан `APP_CRYPTOBOT_TOKEN`.
* `yookassa` регистрируется, когда задан `APP_YOOKASSA_SHOP_ID` или `APP_YOOKASSA_SECRET`, и затем
  отказывается собираться, если присутствуют не оба значения.

Пустые учётные данные оставляют адаптер незарегистрированным, а не регистрируют сломанный, — поэтому
у установки, которая не продаёт через YooKassa, нет адаптера YooKassa, падающего при первом
использовании. Запрос ненастроенного провайдера отвечает `422 provider_unavailable`.

### Какие способы предлагаются клиенту

Способ оплаты появляется, только если установка его настроила **и** тариф имеет цену в валюте, в
которой этот способ проводит расчёты. Выбор никогда не откатывается к несовместимому адаптеру, а
заказу, который уже полностью покрыт кошельком, не предлагается ни один провайдер. У пополнений
кошелька нет прайс-листа тарифа, поэтому они предлагают все включённые адаптеры, поддерживающие
валюту.

Список, который видит клиент, отсортирован по слагу провайдера — `cryptobot`, `manual`,
`telegram_stars`, `yookassa`, — и обе поверхности используют одну и ту же сортировку, поэтому способ
оплаты никогда не окажется на одной позиции в боте и на другой в браузере. Поле порядка отображения
в панели этого не меняет.

> [!WARNING]
>
> Адаптер `manual` регистрируется всегда, и переключателя, который бы его скрыл, нет. Он также не
> объявляет никаких ограничений по валюте, поэтому подходит к любой цене тарифа. Установка, которая
> никогда не собиралась принимать банковские переводы, всё равно покажет **manual** как способ
> оплаты каждому клиенту и в боте, и в веб-панели, а каждое созданное им намерение платежа будет
> ждать решения оператора, которое можно принять только на поверхности с токеном. См.
> [Ручные платежи](#ручные-платежи).

> [!NOTE]
>
> На `/admin/settings` есть карточка **Payment providers**, которая хранит `enabled`, порядок
> отображения, запечатанные учётные данные и секрет вебхука для каждой пары `(provider, merchantId)`.
> Ничто из этого не настраивает работающий адаптер: `ConfigureProviders` читает только переменные
> окружения, а клиентский список использует порядок отображения, зашитый в код. У сохранённых учётных
> данных нет ни одного читателя. Настраивайте провайдеров в `.env` и перезапускайте.
> [Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md) описывают, на что экраны настроек
> влияют, а на что нет.

## Telegram Stars

Проводит расчёты в Telegram Stars — валюте без дробной части с кодом `XTR` — через аутентифицированный
Bot API. Это единственный канал, которому не нужен ни счёт мерчанта, ни внешнее подключение, и
единственный, чьи деньги полностью остаются внутри Telegram.

**Почему путь расчёта здесь другой.** Telegram не подписывает обновления Bot API для публичного
HTTP-маршрута. Принимать «вебхук Stars» на `/v1/payments/webhooks/telegram_stars` означало бы
принимать неаутентифицированные деньги, поэтому адаптер не объявляет возможность вебхуков, а
публичный маршрут отклоняет этого провайдера ещё до разбора чего-либо. Расчёт приходит только через
собственный аутентифицированный поток обновлений бота.

**1. Задайте токен бота**

`APP_TELEGRAM_TOKEN` — тот же токен, который использует процесс бота. В API он включает адаптер
Stars и вход через Telegram; в воркере — Stars для продлений. О том, как его получить, см.
[Telegram](https://omniflow-vpn.mintlify.site/ru/integrations/telegram.md).

**2. Назначьте версии тарифа цену в XTR**

Тариф без цены в `XTR` невозможно купить за Stars, потому что выбор провайдера сопоставляется с
валютами цен тарифа. `APP_DEFAULT_CURRENCY=XTR` делает установку, работающую только на Stars,
согласованной везде.

**3. Купите в боте**

На экране заказа появляется кнопка **счёта**. Бот перечитывает заказ, отказывает, если валюта не
`XTR` или не осталось внешней суммы к оплате, и отправляет счёт Telegram с идентификатором заказа
в качестве полезной нагрузки и включённой защитой от пересылки.

**4. Следите за ответом на pre-checkout**

Telegram присылает `pre_checkout_query`. Omniflow подтверждает его, только если указанный заказ
принадлежит этому клиенту, валюта совпадает, итог в точности равен внешней сумме и заказ ещё
открыт. Всё остальное получает отказ и сообщение об устаревших данных, а не молчаливое принятие.

По `successful_payment` обновление записывается как событие провайдера с ключом
`charge:<telegram_payment_charge_id>`, из-за чего переотправленное обновление становится пустой
операцией с классификацией `duplicate`. В качестве сырого тела сохраняется собственная проекция
Omniflow — `{updateId, orderId, chargeId, amountMinor, currency}`, — а не обновление Telegram: ни
содержимого сообщения, ни данных аккаунта. Идентификатор списания записывается в намерение платежа
*до* расчёта, поэтому повторная попытка снова найдёт намерение, а у возврата будет нужная ссылка.
Списание, чьё намерение принадлежит другому заказу, отклоняется с `order_mismatch`.

**Возвраты.** `refundStarPayment` требует и идентификатор списания, и пользователя Telegram, который
платил. Плательщик сохраняется как несекретные метаданные чека, если платёж запущен с поверхности,
которая знает аккаунт Telegram. Возвраты возможны только на полную сумму списания: частичный запрос
отклоняется, а не сообщается как проведённый.

**Тестирование.** Песочницы нет. Назначьте дешёвой версии тарифа цену в `XTR` и купите её со своего
аккаунта Telegram; собственное тестовое окружение Telegram здесь не рассматривается.

> [!WARNING]
>
> Два ограничения Stars, которые нужно учитывать. `Poll` не поддерживается, поэтому платёж через
> Stars нельзя сверить, и кнопка **Обновить** у клиента для него ничего не делает — поток обновлений
> остаётся единственным путём расчёта. И платёж через Stars, начатый из *веб*-панели, не записывает
> идентификатор пользователя Telegram, поэтому такое списание вообще нельзя вернуть через адаптер.
> Покупки за Stars, которые могут потребовать возврата, следует делать в боте.

## CryptoBot

Создаёт крипто-счета, номинированные в фиате, через `pay.crypt.bot`. Оператор назначает цену в
фиатной валюте, а клиент платит криптовалютой — именно поэтому это тот канал, который уже используют
большинство магазинов, живущих внутри Telegram.

**Валюты:** `USD`, `EUR`, `RUB`, `BYN`, `UAH`, `GBP`, `CNY`, `KZT`, `UZS`, `GEL`, `TRY`, `AMD`,
`INR`, `AZN`, `AED`, `PLN`. Telegram Stars намеренно отсутствуют — они никогда не проводятся через
CryptoBot.

**1. Создайте приложение в @CryptoBot**

Откройте [@CryptoBot](https://t.me/CryptoBot), перейдите в **Crypto Pay**, создайте приложение и
скопируйте его API-токен. CryptoBot держит отдельного бота для тестовой сети со своими токенами;
токен testnet работает, только пока `APP_CRYPTOBOT_TESTNET` включён, а токен основной сети —
только пока он выключен.

**2. Настройте Omniflow**

```env theme={null}
APP_CRYPTOBOT_TOKEN=…
APP_CRYPTOBOT_TESTNET=true
```

`APP_CRYPTOBOT_TESTNET` по умолчанию равен **`true`**, что направляет адаптер на
`https://testnet-pay.crypt.bot/api`. Для продакшена задайте `false`; если оставить значение по
умолчанию, настоящие счета не будут создаваться никогда.

**3. Направьте вебхук на Omniflow**

В настройках приложения включите вебхуки и укажите URL
`https://<your-public-host>/v1/payments/webhooks/cryptobot`. Маршрут не аутентифицирован по
необходимости и защищён проверкой подписи, описанной ниже.

**4. Купите что-нибудь недорогое**

В тестовой сети создайте заказ и оплатите счёт. Заказ должен рассчитаться за считанные секунды;
если этого не произошло, посмотрите запись о приёме в `/admin` → System → Webhooks и помните, что
минутный сверщик всё равно опросит счёт.

**Проверка вебхука.** Заголовок `crypto-pay-api-signature` декодируется из hex и сравнивается за
постоянное время с `HMAC-SHA256(key = SHA256(apiToken), body)` по точному сырому телу. Изменённое
тело отвергается. Идентификатор дедупликации — `{updateType}:{invoiceID}:{updateID}`, он стабилен
при повторах одного и того же тела.

**Восстановление и опрос.** Если `CreateIntent` повторяется после потерянного ответа, адаптер
запрашивает последние 100 счетов и принимает тот, чья полезная нагрузка соответствует заказу, — на
один заказ второго счёта не будет. Опрос использует `getInvoices?invoice_ids=…` и обслуживает как
фоновый сверщик, так и кнопку **Обновить** у клиента. Счета создаются с `expires_in=3600`.

> [!WARNING]
>
> **CryptoBot не поддерживает возвраты.** Адаптер возвращает «unsupported», а платёжный сервис
> отказывает ещё до обращения к провайдеру. Если вы продаёте через CryptoBot, ваш путь возврата —
> зачисление в кошелёк: операторская корректировка кошелька клиента с причиной и ключом
> идемпотентности, которая записывается в книгу проводок и видна клиенту. См.
> [кошелёк и книгу проводок](https://omniflow-vpn.mintlify.site/ru/commerce/wallet-ledger.md).

## YooKassa

Российский эквайер, работа через `https://api.yookassa.ru/v3` с HTTP Basic-аутентификацией. Это
единственный адаптер, поддерживающий частичные возвраты, и единственный, который заявляет поддержку
рекуррентных платежей.

**1. Получите идентификатор магазина и секретный ключ**

В личном кабинете YooKassa создайте секретный ключ для API. Вам нужны идентификатор магазина и
ключ вместе.

**2. Настройте Omniflow**

```env theme={null}
APP_YOOKASSA_SHOP_ID=…
APP_YOOKASSA_SECRET=…
APP_PUBLIC_URL=https://shop.example.com
```

Если задать одно из первых двух значений без другого, запуск завершится ошибкой. `APP_PUBLIC_URL`
**обязателен** всегда, когда настроена YooKassa, потому что размещённой платёжной странице нужна
абсолютная ссылка для возврата; бот отказывается стартовать без неё.

**3. Укажите URL уведомлений**

В личном кабинете направьте HTTP-уведомления на
`https://<your-public-host>/v1/payments/webhooks/yookassa`.

**4. Оплатите тестовый заказ**

Используйте тестовый магазин YooKassa и его тестовые карты. После подтверждения клиент попадает на
`{APP_PUBLIC_URL}/account/orders/{orderID}` — это возвратный URL, который Omniflow передаёт в
каждом создаваемом намерении платежа.

**Создание.** `POST /payments` с `capture: true`, подтверждением типа redirect, несущим возвратный
URL, `metadata.order_id` и ключом идемпотентности Omniflow в заголовке `Idempotence-Key`. YooKassa
соблюдает этот ключ 24 часа — именно это делает безопасной повторную попытку воркера.

**Проверка вебхука — самое важное.** YooKassa не подписывает свои уведомления. Поэтому Omniflow
относится к уведомлению как к недоверенной *подсказке*: проверка лишь разбирает конверт и требует
наличия типа уведомления с непустым идентификатором объекта, после чего `HandleWebhook` немедленно
заново запрашивает указанный платёж через аутентифицированный API и заменяет статус и сумму из
уведомления опрошенными значениями, прежде чем изменится хоть какое-то финансовое состояние. Если
этот запрос не удался, строка вебхука завершается как `failed` с `reconciliation_failed`, и ничего не
применяется. Атакующий, способный отправлять запросы на маршрут вебхука, не может сделать платёж
успешным.

Идентификатор события вебхука — `{event}:{objectID}:{first 8 bytes of sha256(body)}`, именно он
дедуплицирует повторную доставку.

**Возвраты.** `POST /refunds` с идентификатором платежа, суммой и ключом идемпотентности вызывающей
стороны. Поддерживаются и полные, и частичные — YooKassa единственный адаптер, у которого частичный
возврат доходит до провайдера.

**Рекуррентные платежи.** `ChargeSaved` отправляет платёж с сохранённым `payment_method_id` и
`capture: true` за один шаг, потому что рядом нет никого, кто мог бы подтвердить двухфазную
авторизацию.

> [!WARNING]
>
> Ни один путь в коде никогда не *создаёт* сохранённый способ оплаты: `Create` никогда не выставляет
> `save_payment_method`, поэтому идентификатор способа оплаты никогда не возвращается и его не к чему
> привязать, а список сохранённых способов остаётся пустым. Рекуррентные списания объявлены и
> подключены, но сейчас их невозможно активировать.
> [Рекуррентные платежи](https://omniflow-vpn.mintlify.site/ru/commerce/recurring-payments.md) описывают ровно то, какие части работают.

## Ручные платежи

Адаптер без внешней системы. `Create` возвращает намерение платежа, у которого ссылка провайдера —
это идентификатор заказа, а статус — `requires_action`; опрашивать нечего и вебхук проверять нечем.
Расчёт происходит, когда его подтверждает оператор, — именно это делает адаптер пригодным для
банковских переводов, наличных и любых каналов, которые Omniflow не интегрирует.

Поскольку деньги перемещались вне системы, ответственность несёт само подтверждение: оно отклоняется
без указанного оператора, причины и корректного ключа идемпотентности.

```bash theme={null}
curl -X POST https://shop.example.com/v1/admin/payments/$PAYMENT_ID/manual-decision \
  -H "Authorization: Bearer $APP_OPERATOR_TOKEN" \
  -H "Idempotency-Key: 2026-08-13-manual-7f3c9a" \
  -H "Content-Type: application/json" \
  -d '{"approved": true, "operatorID": "…", "reason": "Bank transfer 4412 received"}'
```

Решение записывается в `manual_payment_approvals`, уникально для каждого намерения платежа, поэтому
второе решение, противоречащее первому, отклоняется с сообщением «manual payment already has a
different final decision». Подтверждение проводит **полную** сумму намерения через тот же путь
расчёта, что используют все остальные провайдеры. Отклонение фиксирует решение и не проводит ничего.

Клиент видит передачу платежа в режим `manual`: панель сообщает, что подтверждать будет оператор, а
экран заказа в Telegram говорит то же самое, пока заказ в ожидании.

> [!WARNING]
>
> Операторского экрана для ручных платежей нет. `POST /v1/admin/payments/{paymentID}/manual-decision`
> существует только на поверхности с bearer-токеном — нет ни маршрута `/v1/panel`, ни кнопки в
> `/admin`, ни чего-либо в операторских инструментах Telegram. Если вы принимаете ручные платежи, вам
> понадобится скрипт или HTTP-клиент и операторский токен. См.
> [интеграционный API](https://omniflow-vpn.mintlify.site/ru/integrations/api.md).

## Приём вебхуков

Один публичный маршрут обслуживает каждый адаптер, объявляющий поддержку вебхуков:
`POST /v1/payments/webhooks/{provider}`. Он не несёт учётных данных — подлинность обеспечивается
проверкой подписи адаптера, а не токеном в URL, — а тело ограничено 64 КиБ (`413 payload_too_large`
сверх этого).

Что происходит с запросом, по порядку:

1. Отказ, если названный адаптер не зарегистрирован или не объявляет поддержку вебхуков. Именно это
   закрывает маршрут для `telegram_stars` и `manual`.
2. Вычисляется дайджест тела и запускается проверка адаптера. Неудача **всё равно записывается** —
   строкой с `signature_valid = false` и идентификатором события `invalid:<digest>` — и отвечает
   `400 webhook_rejected`. Отклонённый вебхук, не оставивший следа, был бы неотличим от того, который
   вообще не приходил.
3. Вставляется строка события. Если сохранённая строка уже обработана или проигнорирована, ответ —
   `{"status": "duplicate"}`, и на этом всё. Это защита от дублей, привязанная к собственному
   идентификатору события провайдера, а не к какому-либо заголовку Omniflow.
4. Только для YooKassa — платёж заново запрашивается через аутентифицированный API.
5. Проверенное, но неуспешное событие завершается как `ignored`, а не `failed`. Истёкший счёт — не
   ошибка.
6. Находится намерение платежа, расчёт проводится по общему идемпотентному пути, и возвращается `200`
   с классификацией.

С каждым событием сохраняется: провайдер, его идентификатор события, прошла ли проверка подписи,
дайджест тела, сырое тело и **очищенная проекция заголовков, содержащая только `content-type` и
`user-agent`**. Сохранение заголовков авторизации или подписи означало бы хранение учётных данных
провайдера в базе. Строки удаляются через 30 дней после приёма.

Операторы читают историю приёма в `/admin` → System → Webhooks (`system.read`). Сырое тело там
никогда не возвращается: это собственная полезная нагрузка провайдера, в ней могут быть платёжные
данные, а для ответа на вопрос «пришло ли это и было ли принято» достаточно статуса.

> [!NOTE]
>
> Действие **Replay** для события вебхука возвращает его статус к `received` и пишет строку аудита,
> но строки в статусе `received` никто не потребляет — воркера повторной обработки нет. Пропущенный
> расчёт восстанавливается сверкой, а не повтором.

## Сверка: когда вебхук так и не пришёл

Вебхук может потеряться, задержаться или быть заблокированным. Сверка спрашивает у провайдера, что
произошло на самом деле, и применяет ответ через тот же идемпотентный расчёт, поэтому она может
работать рядом с вебхуком, не зачисляя деньги дважды.

* **Автоматически.** Проход `payment_reconcile` на воркере раз в минуту опрашивает до 100 намерений
  платежа, которые находятся в `pending` или `processing`, относятся к `cryptobot` или `yookassa`,
  имеют ссылку провайдера и не обновлялись минуту.
* **Клиентом.** `POST /v1/account/orders/{orderID}/refresh`, а также экран заказа в Telegram при
  открытии. Сверка срабатывает, только пока заказ ещё ждёт оплаты, и молча ничего не делает для
  адаптера, который нельзя опросить.
* **Оператором.** `POST /v1/admin/payments/{paymentID}/reconcile` с операторским токеном.

`/admin/finance` → **Stuck** перечисляет намерения платежа, которые находятся в пути дольше, чем
должен занимать любой поддерживаемый провайдер.

> [!WARNING]
>
> Кнопка **Reconcile** на `/admin/finance` пишет событие аудита (`panel.payment.reconciled`) и больше
> ничего — панель не держит платёжный сервис и никогда не опрашивает провайдера заново. То же
> относится к форме **Refund** в панели, которая записывает `panel.refund.issued`, не создавая
> возврата, не двигая книгу проводок и не меняя состояние заказа. Работающие пути —
> `POST /v1/admin/payments/{paymentID}/reconcile` и `POST /v1/admin/payments/{paymentID}/refunds` на
> поверхности с операторским токеном.

## Возвраты по провайдерам

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

```bash theme={null}
curl -X POST https://shop.example.com/v1/admin/payments/$PAYMENT_ID/refunds \
  -H "Authorization: Bearer $APP_OPERATOR_TOKEN" \
  -H "Idempotency-Key: 2026-08-13-refund-7f3c9a" \
  -H "Content-Type: application/json" \
  -d '{"amountMinor": 49900, "currency": "RUB", "reason": "Customer request within 14 days"}'
```

Запрос отклоняется ещё до обращения к провайдеру, если: отсутствует ключ идемпотентности или причина,
намерение платежа не в статусе `succeeded`, валюта не совпадает, сумма не положительна или превышает
намерение, адаптер не объявляет возвраты, у намерения нет ссылки провайдера или уже зарезервированные
возвраты вместе с этой суммой превысили бы то, что провайдер получил. Изменения возвратов
сериализуются для каждого намерения рекомендательной блокировкой, а переиспользование ключа с другими
параметрами отклоняется.

| Провайдер | Что делает возврат |
| - | - |
| YooKassa | Полный или частичный, исполняется у провайдера |
| Telegram Stars | Только полное списание и только если был записан платящий аккаунт Telegram. Частичный запрос отклоняется, а не округляется молча |
| Manual | Записывает возврат в статусе `pending`; движение денег происходило вне Omniflow, и возврат — тоже |
| CryptoBot | Невозможен. Отказ на уровне адаптера — используйте зачисление в кошелёк |

Кошельковую часть заказа никогда нельзя вернуть провайдеру: сумма ограничена тем, что провайдер
действительно получил. Полный возврат переводит заказ в `refunded`, частичный — в
`partially_refunded`, и оба ставят в очередь уведомление оператору.

## Как убедиться, что провайдер действительно работает

Надёжной проверки подключения внутри панели нет. `POST /v1/panel/settings/providers/{provider}/test`
действительно выполняет настоящую проверку только на чтение — `getMe` для CryptoBot, один платёж из
списка для YooKassa, `getMe` для Stars, ничего для `manual` — со своим дедлайном в 20 секунд, и эта
проверка не может создать платёж. Но результат, который она пытается сохранить (`passed`, `failed`,
`unsupported`), не входит в набор значений, принимаемых колонкой (`unknown`, `healthy`, `failing`),
поэтому запись падает и запрос отвечает `500 operation_failed` независимо от того, сработали
учётные данные или нет. Результат проверки остаётся в логе сервера API; колонка вебхуков в панели
точно так же всегда показывает `unknown`, потому что здоровье вебхуков никто не фиксирует.

Что делать вместо этого:

1. Выставьте версию тарифа на продажу по минимально возможной цене и купите её сами — в тестовой
   сети, если она у провайдера есть.
2. Посмотрите в `/admin` → System → Webhooks строку приёма и её статус.
3. Посмотрите на `/admin/finance/{orderId}` ленту событий намерения платежа — `created`,
   `status_changed` и любая классификация несоответствия видны там.
4. Верните деньги через поверхность с операторским токеном — это проверит вторую половину интеграции.

## Работа с деньгами, общая для всех провайдеров

* **Суммы везде выражены целыми числами в минорных единицах**, а показатель степени берётся из
  валюты: `XTR`, `JPY` и `KRW` — без дробной части, у большинства остальных их две.
* **Каждое изменение у провайдера несёт ключ идемпотентности Omniflow.** Клиентский ключ —
  `order-<orderID>-<provider>`: он привязан к обоим, потому что ключ одного лишь заказа связал бы
  вторую попытку с другим способом оплаты с намерением первого адаптера. Продления используют
  `renewal:<cycleKey>:<attempt>`. На `/v1/admin` ключ ваш, от 8 до 128 символов.
* **Платёж, не соответствующий своему заказу, всё равно фиксирует событие.** Несовпадение валюты,
  дубль, недоплата, переплата и позднее поступление имеют собственные классификации, и ни одна из них
  не может выдать право на услугу. Пополнение — исключение: оно ничего не должно, кроме самих денег,
  поэтому пере- или недоплата зачисляет ровно то, что пришло.
* **`wallet_unavailable`** стоит уметь распознавать: провайдер получил деньги, но кошельковой части,
  на которую рассчитывал заказ, больше нет, поэтому заказ остаётся в ожидании. Автоматически это не
  разрешается — ситуация появляется в ленте событий заказа и требует оператора.
* **HTTP-вызовы к провайдерам ограничены**: клиентский таймаут 15 секунд и лимит ответа 1 МиБ.

Справочник по конфигурации для всех этих переменных — в
[конфигурации](https://omniflow-vpn.mintlify.site/ru/getting-started/configuration.md); как платёж выглядит со стороны клиента, описано в
[веб-панели](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md) и в [Telegram-боте](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.