# BramaPay — быстрый старт для ИИ-агента

Самодостаточное руководство для серверной интеграции магазина. API v2 и протокол вебхуков v2 — отдельные контракты. Это не готовое production-приложение и не подтверждение доступности Live.

## Исходные руководства

- [Быстрый старт](https://docs.bramapay.com/ru/getting-started/quick-start)
- [Проверка подписи](https://docs.bramapay.com/ru/webhooks/signature-verification)
- [Дедупликация событий и заказов](https://docs.bramapay.com/ru/webhooks/deduplication)
- [Создание платёжной сессии v2](https://docs.bramapay.com/ru/checkout/payment-sessions-v2)

## Пример серверных переменных окружения

Это пример имён для приложения магазина, а не настройки развёртывания BramaPay. Оставьте секреты пустыми в .env.example и настройте их безопасное хранение на сервере. Не вставляйте секреты в ИИ-чат, frontend, репозиторий или логи. Начните с локальных примеров; Test настраивается отдельно, автоматический переход на Live запрещён. Это руководство не разрешает создавать удалённые сессии, переводить средства или выполнять deploy.

```dotenv
BRAMAPAY_API_ORIGIN=https://api.bramapay.com
BRAMAPAY_TEST_PROJECT_KEY=
BRAMAPAY_WEBHOOK_KEY_ID=
BRAMAPAY_WEBHOOK_SECRET=
```

## Сверка суммы и идентичности заказа

Берите цену из серверного заказа. Десятичные значения "25" и "25.00" численно равны: используйте точную десятичную арифметику, а не равенство исходных строк или JavaScript Number. Сверяйте сохранённый ценовой контракт: стоимость заказа, payable USD и точная сумма токена — разные значения. Не рассчитывайте ожидаемую сумму токена самостоятельно. После неопределённого результата сохраняйте externalId, точное тело и Idempotency-Key; не обходите 409 новым ключом.

# Быстрый старт

Создайте **тестовую платёжную сессию v2**, откройте страницу оплаты и подтвердите один тестовый заказ. Нужен сервер магазина: запросы, хранение секретов и проверка оплаты выполняются на нём. Это учебный пример, а не готовое приложение. До обработки событий реализуйте [полную проверку подписи](https://docs.bramapay.com/ru/webhooks/signature-verification). Полный контракт описан в [руководстве по сессиям](https://docs.bramapay.com/ru/checkout/payment-sessions-v2); [счета v1](https://docs.bramapay.com/ru/api-reference/invoices) поддерживаются для существующих интеграций.

## 1. Подготовьте проект

Зарегистрируйтесь, подтвердите email, выберите проект и режим **Test**. Зарегистрируйте подконтрольный вам тестовый кошелёк и явно разрешите USDT в TRON Shasta. Регистрация кошелька Live не создаёт регистрацию Test. Не передавайте приватный ключ или фразу восстановления.

Задайте `successUrl`, `cancelUrl` и точные `allowedRedirectDomains`. Добавьте активный публичный HTTPS-адрес вебхука, сохраните одноразовый `secret` и `keyId`. Реализуйте проверку подписи до проверки соединения. Создайте секретный API-ключ Test для этого проекта и храните его на сервере. См. [настройку проекта](https://docs.bramapay.com/ru/projects/readiness) и [URL возврата](https://docs.bramapay.com/ru/projects/return-urls).

Режим Test должен быть доступен. Если он недоступен, обратитесь в поддержку и проверяйте обработчик на локальных примерах; не переключайтесь автоматически на Live. Тестовые токены не оплачивают реальные заказы и не пополняют сервисный баланс Live.

## 2. Создайте одну тестовую сессию

**До отправки** сохраните заказ, ожидаемые проект и режим, точное тело запроса и ключ идемпотентности. После тайм-аута с неизвестным результатом повторяйте то же тело с тем же ключом. Не обходите конфликт новым ключом. Денежные значения передаются десятичными строками, а не числами с плавающей точкой.

```bash
curl --request POST 'https://api.bramapay.com/v2/payment-sessions' \
  --header 'X-Api-Key: YOUR_TEST_PROJECT_SECRET_KEY' \
  --header 'Idempotency-Key: order-demo-1042-attempt-1' \
  --header 'Content-Type: application/json' \
  --data '{"amountUsd":"25.00","currency":"USD","externalId":"order-demo-1042"}'
```

Проект и режим определяет ключ. Не передавайте `projectId`, `mode`, ID кошельков или переопределение URL возврата. Дополнительные поля и варианты комиссии описаны в [контракте сессии](https://docs.bramapay.com/ru/checkout/payment-sessions-v2) и [правилах комиссии покупателя](https://docs.bramapay.com/ru/billing/buyer-fee-pricing).

## 3. Сохраните ответ

До открытия оплаты свяжите с заказом ID сессии, проект, режим, внешний ID заказа, валюту, сумму и неизменяемый снимок цены. Ниже **частичный вымышленный ответ**, а не полная схема или реквизиты: домен зарезервирован для примеров, токен доступа ненастоящий.

```json
{
  "sessionId": "00000000-0000-4000-8000-000000000001",
  "projectId": "prj_00000000000000000000000000000000",
  "externalId": "order-demo-1042",
  "mode": "TEST",
  "paymentMode": "TEST",
  "amountUsd": "25",
  "currency": "USD",
  "status": "OPEN",
  "checkoutUrl": "https://checkout.example/en/checkout/00000000-0000-4000-8000-000000000001#token=EXAMPLE_NOT_A_REAL_CAPABILITY",
  "routes": [],
  "attempts": []
}
```

`routes: []` означает отсутствие доступного маршрута. Не придумывайте его и не просите перевод. Новые попытки на размещённой странице оплаты используют USDT; запись в реестре не гарантирует доступность для сессии. Ранее выданные попытки сохраняют реквизиты.

## 4. Откройте оплату

Используйте возвращённый `checkoutUrl` без изменений. Фрагмент содержит токен доступа: исключите полный URL из журналов и аналитики. Секретный API-ключ не должен попадать в браузер. Не собирайте ссылку самостоятельно и не переносите токен в строку запроса.

Покупатель выбирает доступную сеть и **один раз** отправляет точную сумму на текущий адрес, в указанном токене и до срока. QR содержит только адрес. Не округляйте, не разбивайте перевод, не рассчитывайте сумму токенов самостоятельно и не просите доплатить разницу. Смена маршрута не продлевает срок. См. [первый тестовый платёж](https://docs.bramapay.com/ru/wallets/first-test-payment).

## 5. Проверьте подтверждение

До обработки JSON выполните [полную проверку исходного тела](https://docs.bramapay.com/ru/webhooks/signature-verification): ограничьте размер, проверьте время в пределах 300 секунд, подписанные ID и тип события, выбор секрета по ID ключа, точный конверт протокола v2 и HMAC сравнением за постоянное время. Требуйте `X-Idempotency-Key === X-Event-Id`. Сохраняйте нескомпрометированные старые секреты для существующих доставок. Пространство подписи — буквально `vaultless.webhook` (историческое имя протокола, менять его не нужно); версии API и протокола вебхуков независимы.

Событие оплаты сессии — только `payment_session.paid`. `integration.test`, `webhook.test`, переход браузера и событие дочернего счёта не разрешают выполнение заказа сессии. Событие Test не оплачивает заказ Live. После проверки подписи и структуры получите сессию со своего сервера:

```bash
curl 'https://api.bramapay.com/v2/payment-sessions/SESSION_ID' \
  --header 'X-Api-Key: YOUR_TEST_PROJECT_SECRET_KEY'
```

Требуйте `status === "PAID"` из авторизованного ответа и совпадение сохранённых сессии, заказа, проекта, режима, валюты и цены по исходному контракту. Сравнивайте десятичные значения точно. При комиссии из настроек проекта `amountUsd` сохраняет стоимость заказа; явный `BUYER_FEE_V1` имеет другую семантику. Не путайте стоимость заказа, сумму USD к оплате и точную сумму токенов.

Сохраняйте дедупликацию события и однократное выполнение заказа в одной транзакции БД, с уникальными ограничениями для события и заказа и очередью исходящих заданий. Возвращайте 2xx после надёжного сохранения; временные ошибки чтения или БД должны допускать повтор. См. [подтверждение заказа](https://docs.bramapay.com/ru/security/payment-verification) и [повторную доставку](https://docs.bramapay.com/ru/webhooks/retries).

## 6. Перед переходом на Live

Настройте отдельные кошельки, секреты и вебхуки Live; успешно проверьте **каждый текущий адрес и конфигурацию Live**. Проверьте [готовность проекта](https://docs.bramapay.com/ru/projects/readiness), маршруты и действующую ставку. Создание новых платежей Live разрешено только при **B + C > 0**, где B — фактический сервисный баланс, C — одобренный кредит. По умолчанию C = 0. Уже выданные платежи продолжают обрабатываться и могут увеличить долг сверх порога. Для пополнения используйте отдельные реквизиты [Billing](https://docs.bramapay.com/ru/billing/ledger-topups).

На локальных примерах проверьте неверные подписи, режим, заказ и тип события, повторы событий и выполнения, отсутствие маршрутов, истечение срока и неизвестный результат создания. Обычный 503 не доказывает отказ: нужен проверенный `PAYMENT_CREATION_DECLINED` с совпадающей идентичностью, квитанцией отказа, `retryable: false` и `sessionCreated: false`. Сохраните квитанцию.

Таймер не доказывает окончательность: своевременный перевод может подтвердиться позже. Используйте серверный статус, `monitoringComplete` и `isFinal`. Ошибочные, поздние и дополнительные переводы требуют [разбирательства](https://docs.bramapay.com/ru/invoices/mismatches); не запрашивайте деньги повторно автоматически и не обещайте возврат. Успешный тест не доказывает доступность Live.

---

# Проверка подписи

Собирайте точные Buffer-байты до JSON middleware, ограничивая чтение 256 KiB. Отклоняйте повторные заголовки подписи по исходному списку HTTP; только затем приводите имена к нижнему регистру. Выбирайте секрет из доверенной карты по `X-Key-Id`; неизвестный ID отклоняйте. До проверки ID — лишь подсказка поиска. Храните старые нескомпрометированные секреты для существующих доставок. Синхронизируйте часы сервера.

Обязательные заголовки: `X-Webhook-Version`, `X-Event-Id`, `X-Delivery-Id`, `X-Event-Type`, `X-Key-Id`, `X-Timestamp`, `X-Attempt`, `X-Idempotency-Key`, `X-Signature-SHA256`. Namespace подписи остаётся буквально `vaultless.webhook`. Подписывается JSON.stringify упорядоченного массива ниже: версия — число 2, остальные значения envelope — строки.

Функция предполагает, что HTTP-обработчик уже отклонил повторные заголовки, ограничил размер тела и выбрал доверенный секрет.

```ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyBramaPayWebhook(
  rawBody: Buffer,
  headers: Record<string, string | undefined>,
  secret: string,
) {
  const required = (name: string) => {
    const value = headers[name.toLowerCase()];
    if (!value) throw new Error(`Missing ${name}`);
    return value;
  };

  const version = required("x-webhook-version");
  const eventId = required("x-event-id");
  const deliveryId = required("x-delivery-id");
  const eventType = required("x-event-type");
  const timestamp = required("x-timestamp");
  const keyId = required("x-key-id");
  const attempt = required("x-attempt");
  const signature = required("x-signature-sha256");

  if (version !== "2" || required("x-idempotency-key") !== eventId)
    throw new Error("Invalid webhook envelope");
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300)
    throw new Error("Stale webhook");
  if (!/^[0-9a-f]{64}$/.test(signature) || !/^[1-9]\d*$/.test(attempt))
    throw new Error("Invalid signature encoding or attempt");

  const input = JSON.stringify([
    "vaultless.webhook",
    2,
    eventId,
    deliveryId,
    eventType,
    timestamp,
    keyId,
    attempt,
    rawBody.toString("utf8"),
  ]);
  const expected = Buffer.from(createHmac("sha256", secret).update(input).digest("hex"), "hex");
  const actual = Buffer.from(signature, "hex");
  if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
    throw new Error("Invalid webhook signature");
  return { eventId, eventType, keyId };
}
```

Разбирайте JSON и проверяйте схему и подписанный тип события только после успеха проверки. Одной подписи недостаточно для выдачи: сверяйте сессию, проект, режим, заказ, валюту и цену с авторизованным PAID-ответом. Отклоняйте Test-событие для Live-заказа. Тест связи может получить 2xx после проверки, но не оплачивает заказ. При временном сбое чтения/хранения возвращайте non-2xx для повтора. См. [дедупликацию](https://docs.bramapay.com/ru/webhooks/deduplication) и [сессии](https://docs.bramapay.com/ru/api-reference/payment-sessions).

---

# Дедупликация событий и заказов

Повторы и ручная доставка могут повторить корректное событие. `X-Event-Id` — логическая идентичность события, `X-Delivery-Id` относится к доставке на endpoint. Дедуплицируйте событие и бизнес-заказ. Номера попытки или delivery ID недостаточно.

После проверки исходной подписи и авторизованного PAID-ответа со сверкой заказа/проекта/режима/валюты/цены выполните одну транзакцию БД: вставьте уникальный `(merchant_scope, eventId)`, отметьте связанный заказ оплаченным под уникальным ограничением заказа/сессии и добавьте выдачу в надёжный outbox. Принятое событие или оплаченный заказ не создают повторную выдачу. Сначала commit, затем небольшой 2xx. Временный сбой чтения/хранения должен допускать повтор.

Нельзя выдавать заказ за сессию и повторно за дочерний счёт. Test-события, treasury-пополнения, тесты связи, redirects и записи проверки не оплачивают Live-заказ.

---

# Создание платёжной сессии v2

Сессия — один заказ; попытка создаётся только после выбора маршрута. В кабинете выберите проект и режим, откройте Платёжные запросы → Создать. JWT владельца позволяет создание без API-ключа при готовности проекта. Для интеграции используйте секрет проекта.

## Создайте одну сессию на заказ

**До отправки** сохраните тело запроса, ID заказа, ожидаемые проект и режим, а также ключ идемпотентности. После тайм-аута или потери ответа повторяйте тот же запрос с тем же ключом.

```bash
curl --request POST 'https://api.bramapay.com/v2/payment-sessions' \
  --header 'X-Api-Key: YOUR_PROJECT_SECRET_KEY' \
  --header 'Idempotency-Key: order-demo-1042-attempt-1' \
  --header 'Content-Type: application/json' \
  --data '{
    "amountUsd": "25.00",
    "currency": "USD",
    "externalId": "order-demo-1042",
    "displayOrderNumber": "1042",
    "description": "Example service order",
    "expiresInSeconds": 1800
  }'
```

| Поле | Контракт |
|---|---|
| `amountUsd` | Положительная десятичная строка, не более шести знаков после точки. Не число с плавающей точкой. |
| `currency` | Только `USD`. Фиксированное соотношение учёта стейблкоинов не является рыночным курсом или гарантией погашения. |
| `externalId` | Обязательное поле, 1–200 символов; постоянный идентификатор заказа в проекте и режиме. |
| `displayOrderNumber` | Необязательный публичный текст, до 100 кодовых точек Unicode. |
| `description` | Необязательный публичный текст, до 1000 кодовых точек Unicode. Без учётных данных, секретов выдачи и персональных данных клиента. |
| `expiresInSeconds` | Необязательное целое число 1–86400. Значение по умолчанию определяется серверной политикой срока счёта. Полученный срок фиксирован. |

Не передавайте в этот endpoint `projectId`, `mode`, ID кошельков, выбранный способ оплаты и переопределения URL возврата. Контекст задают API-ключ и настройки проекта. Для явного переноса комиссии используйте другой контракт: `baseAmountUsd`, `feePayer: "BUYER"`, `pricingVersion: "BUYER_FEE_V1"`, без `amountUsd`. Не смешивайте эти варианты.

Успешный ответ содержит `sessionId`, `externalId`, `mode`/`paymentMode`, `amountUsd`, `currency`, `status`, `expiresAt`, `checkoutUrl`, `routes`, `attempts` и, когда применимо, сведения о цене и мониторинге. Сохраните связь сессии с заказом и неизменяемые ценовые параметры. Новая сессия может ещё не иметь платёжных попыток.

### Обработайте ответ создания

После успешного ответа сохраните `sessionId`, `projectId`, `mode`, `externalId`, `amountUsd`, `currency` и снимок расчёта комиссии в заказе до перехода на `checkoutUrl`. Ниже неполный вымышленный ответ, а не реальные платёжные реквизиты. Возможен `routes: []`; не придумывайте маршрут и не запрашивайте перевод, если доступных способов нет.

> Этот же пример уже приведён выше.

## Откройте оплату

Направьте покупателя на возвращённый `checkoutUrl` без изменений. Во фрагменте URL находится токен доступа к сессии: не записывайте полный URL в логи и аналитику. Не конструируйте ссылку самостоятельно и не переносите токен в query-параметры.

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

## Проверьте webhook и достоверный статус

Событие оплаты сессии — `payment_session.paid`; для счетов v1 используется `invoice.paid`. `integration.test` и `webhook.test` проверяют соединение, а не оплату. Test-событие не должно выполнять заказ Live.

Проверьте [подпись v2](https://docs.bramapay.com/ru/webhooks/signature-verification) на исходном теле запроса до его разбора для бизнес-обработки. Порядок подписываемых данных:

```js
JSON.stringify([
  "vaultless.webhook", 2, eventId, deliveryId, eventType,
  timestamp, keyId, attempt, rawBodyAsUtf8String
])
```

Используйте HMAC-SHA256, сравнение с постоянным временем, секрет по `X-Key-Id` и проверку свежести времени. Версия — число `2`, остальные значения конверта — строки. Проверяйте `X-Idempotency-Key === X-Event-Id`. Храните старые нескомпрометированные ключи для ранее созданных доставок: ротация не переподписывает их.

### Полный пример проверки подписи на Node.js

Передавайте исходные байты HTTP-запроса как `rawBody` до JSON middleware. В Node HTTP собирайте Buffer-фрагменты с ограничением 256 КиБ; в Express для этого маршрута установите `express.raw({type: "application/json", limit: "256kb"})` до JSON-парсера. Приведите имена заголовков к нижнему регистру и отклоняйте повторяющиеся заголовки подписи. Выбирайте `secret` из своего доверенного списка по `x-key-id`; неизвестный ключ отклоняйте. До проверки подписи заголовок — только подсказка для поиска ключа. Синхронизируйте часы сервера.

> Идентичный полный verifier уже приведён выше в разделе проверки подписи. Используйте эту единственную реализацию.

Только после успешной проверки разберите JSON, проверьте его структуру и тип события, затем запросите авторизованный статус, как показано ниже. При временной ошибке чтения статуса или хранилища возвращайте не 2xx, чтобы доставка повторилась. Неверную подпись отклоняйте без обработки. Проверенный тест соединения получает 2xx, но не запускает выдачу товара.

Запрашивайте статус с сервера тем же секретным ключом проекта:

```bash
curl 'https://api.bramapay.com/v2/payment-sessions/SESSION_ID' \
  --header 'X-Api-Key: YOUR_PROJECT_SECRET_KEY'
```

Требуйте `status === "PAID"`; сверьте ID сессии, `externalId`, проект, режим, валюту и ожидаемую стоимость по сохранённому ценовому контракту. Сравнивайте десятичные суммы точно. При переносе комиссии настройкой проекта `amountUsd` интеграции сохраняет стоимость заказа, а `baseAmountUsd`/`payableAmountUsd` раскрывают составляющие; у явного buyer-контракта другая семантика. Стоимость заказа, сумма с комиссией и точная сумма токенов — разные величины.

Рекомендуемая обработка на стороне магазина; это псевдокод вашего приложения, а не методы API BramaPay:

```text
проверить raw-подпись, время, тип и идентификатор события
найти сохранённую связь заказа с сессией и проверить статус через API
в одной транзакции базы данных:
  вставить (merchant_scope, eventId) с уникальным ограничением
  если событие уже обработано: ничего не выполнять повторно
  иначе отметить связанный заказ оплаченным и поставить выдачу в очередь один раз
вернуть небольшой ответ 2xx после надёжного сохранения
```

Устраняйте дубли и по событию, и по выполнению заказа. Возврат браузера, поиск транзакции, запись для ручной проверки и непроверенный клиентский статус недостаточны.

## Маршруты, страница оплаты и событие оплаты

- Ссылка оплаты имеет вид `/{locale}/checkout/{sessionId}#token=…`. Страница оплаты убирает токен из истории браузера и хранит его под ключом этой сессии, чтобы перезагрузка и другие вкладки могли его восстановить. Если хранилище браузера заблокировано, для повторного открытия нужна исходная полная ссылка.
- Публичная страница оплаты вызывает `GET /v2/checkout/:id` и `POST /v2/checkout/:id/attempts` с заголовком `X-Checkout-Token` и телом вида `{ "asset": "USDT", "network": "TRON" }`, без cookie и авторизации магазина. Серверной интеграции магазина эти endpoints не нужны.
- Попытка создаётся при первом выборе маршрута; повторный выбор возвращает те же адрес, точную сумму и срок. Ранее выданные попытки остаются в списке, даже если выдача новых маршрутов недоступна.
- Статус сессии не зависит от маршрута, открытого у покупателя: если подтвердился маршрут A, а покупатель смотрит B, сессия становится `PAID`, а `winningAttemptId` указывает на A.
- Для попыток сессии выдачу заказа выполняют по `payment_session.paid`, а не по дочернему `invoice.paid`. Тело события содержит `sessionId`, `attemptId`, `invoiceId` (выигравшая попытка), `externalId`, `projectId`, `mode`, `paymentMode`, `status: "PAID"`, `amountUsd`, `currency`, `amountPaid`, `amountExpected`, `txHash` и `chain`; в Test также тестовую сеть и `test: true`. Суммы передаются строками.
- `displayOrderNumber` и `description` показываются покупателю как обычный текст: HTML и Markdown не интерпретируются. Без публичного номера заказа страница оплаты не подставляет вместо него `externalId`. Цены в описании не влияют на сумму платежа.
- Раздел **Платёжные запросы** в кабинете ограничен выбранными проектом и режимом и показывает попытки, состояние мониторинга, выигравшую попытку и классификацию поступлений. Ранее выданные счета v1 остаются в истории старых счетов; их ссылки и интеграции продолжают работать.

## Обработайте ошибки и повторы

| Ситуация | Действие интеграции |
|---|---|
| Тайм-аут, потеря ответа, общий 5xx | Результат может быть неизвестен. Сохраните исходные тело и ключ, повторяйте тот же запрос. Не создавайте новый заказ. |
| Конфликт 409 | Проверьте ключ, тело, внешний заказ и готовность проекта. Не обходите конфликт новым ключом. |
| 401 | Проверьте секретный ключ и его проект/режим; не публикуйте ключ в диагностике. |
| 429 | Увеличьте интервал и учитывайте доступные сведения о повторе. |
| `PAYMENT_CREATION_DECLINED` | Только проверенный ответ с `retryable: false`, `sessionCreated: false`, совпадающей идентичностью запроса и квитанцией отказа подтверждает окончательный отказ. Одного HTTP 503 недостаточно. Сохраните квитанцию. |
| Истечение срока, поздний, лишний или несовпадающий перевод | Не просите платить повторно автоматически. Проверьте статус и обратитесь в поддержку с ID, сетью и хешем транзакции. Автовозврат и постоянный процесс ручного подтверждения не обещаются. |

Webhooks могут доставляться повторно; успешная доставка не гарантируется. За цикл выполняется до пяти попыток. Для повторяемых ошибок интервалы составляют 30 секунд, 5 минут, 30 минут и 2 часа. Другие ошибки могут завершить цикл сразу. Неудачную доставку на активный endpoint можно повторить вручную после паузы; повтор не разрешает выдавать заказ заново. См. [повторы доставки](https://docs.bramapay.com/ru/webhooks/retries).

Таймер не определяет окончательность результата. Перевод, включённый в блок в пределах срока, может подтвердиться позже. Используйте серверные `status`, `monitoringComplete`, `isFinal`; не обещайте время подтверждения и не считайте каждый ответ об истечении срока окончательным отсутствием оплаты.

API возвращает `status` сессии как `OPEN`, `PAID` или `EXPIRED`. Закрытие передаётся отдельно в `closedAt`; CLOSED и CONFIRMING — вычисляемые фильтры кабинета, а не значения этого поля API. Первый подходящий подтверждённый перевод выигрывает; дополнительные и поздние поступления не разрешают вторую выдачу. `reviewCandidates` — неатрибутированные переводы на адрес, которые могут принадлежать другим заказам. Поиск транзакции не означает ручное подтверждение или возврат.
