Мэтчинг
Что такое мэтчинг
Section titled “Что такое мэтчинг”Мэтчинг закрывает вашу выплату получателю входящими депозитами игроков — своими или других мерчантов платформы. Выплата («сторона B»)
создаётся через POST /payment/pay-out с isMatching: true, депозиты («сторона A») — через POST /payment/pay-in с invoice-методом
sbp_invoice или c2c_invoice. Отдельных эндпоинтов «создать мэтчинг» нет.
Мерчант может быть на обеих сторонах сразу: выплаты и депозиты независимы, и их включает платформа по вашему запросу.
Все запросы merchant API подписываются заголовком X-Secure-Key (API-ключ кабинета); callback-и приходят с заголовком x-signature
(HMAC-SHA256 тела, см. Подпись запросов).
Выплата (сторона B)
Section titled “Выплата (сторона B)”Создание
Section titled “Создание”POST /payment/pay-outX-Secure-Key: <api-key>Content-Type: application/json
{ "amount": 15500, "currency": "RUB", "merchantTransactionId": "payout-100500", "isMatching": true, "timeoutInMin": 120, "callbackUrl": "https://merchant.example.com/callback", "requisite": { "sbpNumber": "79991234567", "bank": "SBER", "ownerName": "Иван Иванов" }}merchantTransactionId— ключ выплаты и ключ идемпотентности, не длиннее 255 символов. ПоляmatchingTransactionIdбольше нет.callbackUrl— адресhttp://илиhttps://; другой адрес →400.amount— в пределах лимитов выплаты мерчанта (по умолчанию от 1 000 до 1 000 000 ₽). Вне лимитов →403 MATCHING_AMOUNT_OUT_OF_LIMITS. Не больше двух знаков после запятой, иначе400.timeoutInMin— SLA выплаты: сколько минут выплата ждёт депозиты. Не меньше минимума платформы (30 мин по умолчанию), иначе403 MATCHING_TIMEOUT_OUT_OF_LIMITS.- Метод выводится из реквизита:
sbpNumber→sbp_invoice, иначеcardNumber→c2c_invoice. Ни одного →400 MISSED_PARAMS. - При создании с баланса сразу списывается тело и ставка мэтчинга по курсу на момент создания. Неиспользованное вернётся при закрытии.
- На один и тот же реквизит может быть открыто несколько выплат игрока. Каждая создаётся со своим
merchantTransactionIdи отдельным резервом; ограничения баланса и общего открытого объёма продолжают действовать. - Повтор с тем же
merchantTransactionId→409 MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID, в теле — существующая выплата; второго списания нет. Ключ общий для выплат мэтчинга и обычных выплат мерчанта: обычная выплата (isMatching=false) с ключом существующей выплаты мэтчинга и выплата мэтчинга с ключом существующей обычной выплаты получают тот же409, в теле — та выплата, что уже есть. - Ключи депозитов и выплат проверяются раздельно: депозит и выплату с одним
merchantTransactionIdплатформа примет обе. НоGET /payment/merchant/{id}по такому ключу вернёт выплату, а депозит по ключу будет не найти. Если вы и принимаете депозиты, и делаете выплаты, задавайте им разные ключи (например, с префиксомin-/out-); депозит по-прежнему доступен поid—GET /payment/{id}.
Ответ — один формат для создания, GET и callback-а
Section titled “Ответ — один формат для создания, GET и callback-а”{ "id": "9ee42dff-83ec-4a90-8dfb-1fc74d3afbe4", "merchantTransactionId": "payout-100500", "status": "active", "type": "pay-out", "method": "sbp_invoice", "currency": "RUB", "amount": 15500, "factFiatAmount": 10000, "crypto": "167567567", "fee": "1675675", "rate": 92.5, "requisite": { "sbpNumber": "79991234567", "bank": "SBER", "ownerName": "Иван Иванов" }, "isMatching": true, "slaDeadlineAt": "2026-09-28T12:00:00.000Z", "closingStartedAt": null, "closeReason": null, "cancelReason": null, "adjustedAt": null, "refundedCrypto": "0", "eventSeq": 0, "fills": [ { "fillId": "5b1c7a8e-3f0a-4c55-9d8e-0a1b2c3d4e5f", "amount": 5000, "status": "success", "settledAt": "2026-09-28T10:20:00.000Z", "source": "pay_in", "reversedAt": null }, { "fillId": "0d2f41c9-8b7e-4a0e-9b1d-3c5e7f9a1b2c", "amount": 5000, "status": "success", "settledAt": "2026-09-28T10:41:00.000Z", "source": "pay_in", "reversedAt": null }, { "fillId": "6a8c0e2f-4b6d-4f1a-8c3e-5d7f9b1d3f5a", "amount": 5500, "status": "in_progress", "settledAt": null, "source": "pay_in", "reversedAt": null } ], "createdAt": "2026-09-28T10:00:00.000Z", "updatedAt": "2026-09-28T10:41:00.000Z", "callbackUrl": "https://merchant.example.com/callback"}| Поле | Смысл |
|---|---|
status | active — выплата принимает депозиты или закрывается; success — оплачено ровно amount; success_changed — закрыта с другой суммой (SLA, закрытие на оплаченном, непогашаемый остаток, корректировка); canceled — ничего не оплачено |
amount / factFiatAmount | сумма выплаты / фактически оплачено получателю = сумма частей в success |
crypto, fee, rate | списано при создании: тело и ставка мэтчинга (минимальные единицы USDT, строкой), курс создания |
refundedCrypto | возвращено на баланс (все возвраты по выплате, минимальные единицы USDT) |
slaDeadlineAt | предельный срок ожидания депозитов; полная оплата или непогашаемый остаток могут завершить выплату раньше |
closingStartedAt | начало закрытия (SLA, «Закрыть на оплаченном» или закрытие оператором); при автоматическом финале может остаться null |
closeReason | filled · sla_recalculated · unfillable_tail · admin_recalculated · merchant_recalculated · externally_reduced · adjusted_after_close |
cancelReason | sla_expired_no_fill · admin · merchant · externally_reduced |
adjustedAt | последняя корректировка после закрытия |
eventSeq | номер последнего callback-события (0 — callback-ов ещё не было) |
fills[] | части выплаты (ниже) |
Части выплаты fills[]
Section titled “Части выплаты fills[]”fillId— непрозрачный идентификатор части; id сделок игроков и трейдеров вы не получаете.source:pay_in— депозит игрока,fallback— выплата трейдером платформы (если депозитов не хватило к таймеру fallback).status:in_progress— депозит оплачивается / проверяется или трейдер выполняет выплату;success— часть оплачена;reversed— часть была успешной и аннулирована (чек признан фейком или выплата трейдера отменена по претензии).reversedAt— момент аннулирования: заполнен только уreversed, у остальных частейnull(поле есть у каждой части).amountчасти сsource = fallbackплатформа может уточнить после проверки оплаты. После финала это приходит корректировкой (ниже).- Отменённые без успеха части в
fills[]не попадают. «Не оплачено и не в работе» =amount − Σ fills[].amount. - Сверка денег по выплате:
crypto + fee − refundedCrypto = Σ успешных частей × (1 + ставка мэтчинга) / rate(± единицы округления).
Callback-и выплаты
Section titled “Callback-и выплаты”- Только финал (
success/success_changed/canceled) и каждая корректировка после финала. При создании и на отдельные части callback-ов нет — части видны в GET. - Корректировка после финала —
success_changedсcloseReason = adjusted_after_closeи новымadjustedAt.factFiatAmount,refundedCryptoиfills[]в теле уже пересчитаны: часть может перейти вreversed(сreversedAt) или получить уточнённуюamount. ОбычноfactFiatAmountуменьшается, аrefundedCryptoрастёт — разница вернулась на баланс. Если платформа после финала подтвердит оплату части,factFiatAmountвырастет, сумма этой части повторно спишется с баланса, аrefundedCryptoуменьшится. Сверяйте выплату по последнему callback-у и формуле сверки выше. - Тело — снимок текущего состояния в момент отправки, поэтому
successи следомsuccess_changedмогут прийти с одинаковым телом. Обрабатывайте callback-и идемпотентно по паре (id,eventSeq) —eventSeqрастёт с каждым событием выплаты. - Повторная отправка по запросу в поддержку приходит новым событием (следующий
eventSeq) с текущим состоянием.
Автоматическое закрытие малого остатка
Section titled “Автоматическое закрытие малого остатка”Если все разрешённые чеки использованы, часть выплаты оплачена, а положительный остаток меньше текущего минимального размера fallback,
платформа автоматически закрывает выплату на оплаченном до SLA: status = success_changed, closeReason = unfillable_tail.
Это правило действует при включённых мэтчинге и fallback; остаток, равный минимуму fallback, под него не попадает. Открытый разбор,
ожидающая претензия о поздней оплате, чек в работе или hold, взятый либо карантинный fallback откладывают такой финал. Невзятые fallback
сначала отменяются; если часть успели взять, система ждёт её завершения.
amount сохраняет исходную сумму, factFiatAmount содержит только оплаченные части. Например, выплата на 4 750 ₽ с четырьмя успешными
чеками на 4 603 ₽ и остатком 147 ₽ при минимуме fallback 1 000 ₽ завершится с amount = 4750, factFiatAmount = 4603 и
closeReason = unfillable_tail. Неиспользованный USDT-резерв возвращается на внутренний баланс мерчанта и учитывается в refundedCrypto;
это не банковская довыплата 147 ₽ получателю. Финал приходит штатным callback-ом; суммы уже оплаченных частей не увеличиваются.
Контракт подготовлен локально 14.10.2026; этот раздел не подтверждает production rollout.
Закрыть на оплаченном
Section titled “Закрыть на оплаченном”POST /payment/pay-out/{id}/closeX-Secure-Key: <api-key>После вызова новые депозиты на выплату не принимаются, части в работе доживают, неиспользованный остаток вернётся на баланс. Вернуть
выплату в работу нельзя. Ответ 200 — выплата с заполненным closingStartedAt; повтор — 200 с текущим состоянием. Финал и callback приходят
после закрытия: success_changed с closeReason = merchant_recalculated, success (если успели оплатить всё) или canceled с
cancelReason = merchant (если ничего не оплачено). Чужая или несуществующая выплата — 404.
Отменить выплату
Section titled “Отменить выплату”POST /payment/pay-out/{id}/cancel отменяет выплату, только если в ней ничего не оплачено и ничего не в работе, иначе
409 MATCHING_PAYOUT_NOT_CANCELABLE (закройте на оплаченном). Успех — 200, canceled, cancelReason = merchant, списанное вернётся на баланс;
повтор по отменённой выплате — 200 с текущим состоянием.
Fallback-выплаты трейдеров
Section titled “Fallback-выплаты трейдеров”Выплаты трейдерам, которые платформа создаёт для части суммы, вы не видите нигде, кроме fills[] (source = fallback): их нет в списке
GET /payment, GET /payment/{id} и поиск по merchantTransactionId отвечают 404, callback-ов по ним нет, отмена и спор по ним → 403 MATCHING_FALLBACK_MANAGED_BY_PLATFORM. Если получатель не получил деньги по успешной части — нажмите «Получатель не получил деньги» у части в
кабинете или напишите в поддержку.
Депозит (сторона A)
Section titled “Депозит (сторона A)”Создание
Section titled “Создание”POST /payment/pay-in с method: "sbp_invoice" или "c2c_invoice". Новых полей в запросе нет; counteragent для invoice-методов игнорируется.
POST /payment/pay-inX-Secure-Key: <api-key>Content-Type: application/json
{ "amount": 5000, "currency": "RUB", "method": "sbp_invoice", "merchantTransactionId": "deposit-777", "callbackUrl": "https://merchant.example.com/callback", "errorUrl": "https://merchant.example.com/fail", "payerInfo": { "userId": "player-1", "ip": "203.0.113.10", "userAgent": "Mozilla/5.0", "fingerprint": "…", "registeredAt": "1728388185326" }}Возможные ответы:
| Ответ | Когда | Что делать |
|---|---|---|
status: "active" с реквизитом | сумма игрока подходит | показать реквизит, как обычно |
status: "pending" с блоком amountOptions, requisite: null | нужен выбор суммы | показать выбор (свой экран через API) или отправить игрока на paymentPageUrl |
сделка с методом sbp / c2c (классика) | мэтчинг не подошёл, у вас включена классика | обычная сделка, старый контракт |
403 | ничего не подошло, классика выключена | код в таблице ошибок ниже |
Если у вас выключен «Выбор суммы» (переключатель у мерчанта, включает платформа по запросу), новые invoice-запросы не создают
этап выбора: только active на запрошенную сумму, классика или 403. Уже открытые сессии выбора продолжают обрабатываться;
отключение их не отменяет. Идемпотентный повтор ранее созданной заявки возвращает её текущее состояние, в том числе pending.
callbackUrl— адресhttp://илиhttps://; другой адрес →400.merchantTransactionIdобязателен, не длиннее 255 символов и уникален среди ваших invoice-запросов. Повтор с тем жеamount,currency,method→200и текущее состояние той же сделки (в том числеcanceledили классика); с другим телом →409 MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID. Остальные поля (payerInfo,callbackUrl…) не сравниваются. Два одинаковых запроса одновременно дают одну сделку.amountдо выбора — запрошенная сумма, после выбора — выбранная. В запросе — не больше двух знаков после запятой, иначе400.
Поля ответа invoice-депозита сверх обычных: initialAmount (сумма, которую запросил игрок), amountChanged (итоговая отличается от
запрошенной), cancelDetail (amount_offer_declined · amount_offer_expired · no_matching_capacity), amountOptions (этап выбора, иначе
null), requisiteIssuedAt (выдача реквизита — окно оплаты считается от неё), creditReversedAt и creditReversedCrypto (сторно
зачисления, см. ниже; без сторно — null). Полей matchingId, matchingTransactionId, matchingTradeType у invoice-депозитов нет. В pending: requisite, paymentLifetime, rate, crypto — null, fee — "0" (ещё не посчитано, см. ниже),
paymentPageUrl заполнен.
Суммы до выдачи реквизита
Section titled “Суммы до выдачи реквизита”Курс, сумму в USDT и комиссию платформа считает в момент выдачи реквизита (requisiteIssuedAt). Пока реквизит не выдан, этих сумм
ещё нет:
| Состояние | requisiteIssuedAt | crypto | fee | rate | paymentLifetime |
|---|---|---|---|---|---|
pending — этап выбора суммы | null | null | "0" | null | null |
canceled до выдачи реквизита (заполнен cancelDetail) | null | "0" | "0" | null | окно оплаты из настроек |
реквизит выдан (active и дальше) | время выдачи | сумма | комиссия | курс | окно оплаты |
Фрагмент ответа в pending:
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "status": "pending", "amount": 5000, "crypto": null, "rate": null, "fee": "0", "requisite": null, "paymentLifetime": null, "requisiteIssuedAt": null, "cancelDetail": null, "amountOptions": { "requestedAmount": 5000, "ownAmountAvailable": false, "offers": [{ "offerId": "9c1e7b20d4f6", "amount": 5500, "diff": 500, "recommended": true }], "insistAvailable": true, "round": 1, "choiceExpiresAt": "2026-09-28T10:09:30.000Z", "sessionStatus": "open" }}Фрагмент ответа после отмены на этапе выбора (игрок отказался от вариантов):
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "status": "canceled", "amount": 5000, "crypto": "0", "rate": null, "fee": "0", "requisite": null, "paymentLifetime": 15, "requisiteIssuedAt": null, "cancelDetail": "amount_offer_declined", "amountOptions": null}Блок amountOptions
Section titled “Блок amountOptions”"amountOptions": { "requestedAmount": 5000, "ownAmountAvailable": true, "offers": [ { "offerId": "3fa85f64a1b2", "amount": 4800, "diff": -200, "recommended": false }, { "offerId": "9c1e7b20d4f6", "amount": 5500, "diff": 500, "recommended": true } ], "insistAvailable": false, "round": 1, "choiceExpiresAt": "2026-09-28T10:09:30.000Z", "sessionStatus": "open"}ownAmountAvailable: true— сумму игрока можно выбрать сразу («Оплатить 5 000 ₽»), варианты — рекомендации.ownAmountAvailable: false— сумма игрока сейчас не принимается, выбрать можно только вариант;insistAvailable: true— можно один раз «Оставить мою сумму» (платформа поищет получателя точно под неё).offers— до 3 вариантов по возрастанию суммы;recommended— подтвердится быстрее.offerIdнепрозрачный и стабилен в раунде.round: 2— «Оставить мою сумму» не нашло получателя, остались только варианты.choiceExpiresAt— последний момент, когда выбор принимается. Выбора не было иownAmountAvailable: true— платформа сама оставит сумму игрока, когда получатель под неё ещё есть: депозит станетactiveс реквизитом на исходную сумму, придёт callbackactive. Иначе (и всегда приownAmountAvailable: false) депозит отменится сcancelDetail = amount_offer_expired.sessionStatus:open·accepted·declined·expired.
Два пути показа выбора
Section titled “Два пути показа выбора”- Свой экран. Покажите
amountOptionsи вызовите эндпоинты выбора (ниже). Статус и блок можно перечитывать черезGET /payment/pay-in/{id}/amount-optionsилиGET /payment/{id}. - Редирект на
paymentPageUrl. Платёжная страница покажет выбор, реквизит и приём чека. Итог придёт callback-ом.
Выбор сделанный на странице и через API меняет одну и ту же сессию: кто первый выбрал, тот и выбрал.
Надёжный переход от суммы к оплате
Section titled “Надёжный переход от суммы к оплате”Сначала определите источник кнопок. GET /payment/matching/demand — предварительная подсказка пула, до создания депозита:
в ней нет tradeId и offerId. Клик выбирает сумму для POST /payment/pay-in. amountOptions — предложения уже созданного
депозита: клик подтверждается через POST /payment/pay-in/{id}/amount-options/accept. Эти два списка нельзя смешивать.
Ни показанная предварительная сумма, ни предложение с offerId не резервируют ёмкость. При создании и при accept платформа
проверяет её заново. GET amount-options читает сохранённый снимок; частый polling не пересчитывает доступность.
Пример создания после выбора предварительной суммы (заголовок X-Secure-Key передаётся только вашим сервером):
POST /payment/pay-inContent-Type: application/json
{"amount":5000,"currency":"RUB","method":"sbp_invoice","merchantTransactionId":"deposit-selection-example-1"}Сохраните merchantTransactionId до запроса. После потери ответа повторяйте тот же запрос с тем же ключом и параметрами:
первое создание отвечает 201, идемпотентный повтор invoice — 200 с той же сделкой. Не генерируйте новый ключ на каждый клик.
Проверяйте также status и method: invoice может пойти через обычный маршрут, описанный выше.
Ниже — сокращённые примеры ответов с синтетическими ID; не перечисленные поля остаются по основному контракту.
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "status": "pending", "paymentPageUrl": "https://pay.example.test/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "requisite": null, "amountOptions": { "requestedAmount": 5000, "ownAmountAvailable": true, "offers": [{"offerId":"9c1e7b20d4f6","amount":5500,"diff":500,"recommended":true}], "insistAvailable": false, "round": 1, "choiceExpiresAt": "2026-09-29T20:09:30.000Z", "sessionStatus": "open" }}pending — уже созданная заявка. Можно сразу перейти на возвращённый paymentPageUrl: Ohlapay сама покажет выбор или
ожидание. Не ждите callback pending — его нет. Если выбор находится на вашем сайте, храните вместе id, offerId, сумму,
choiceExpiresAt; блокируйте повторную отправку, пока запрос выполняется. Срок проверяйте с учётом серверного времени, затем
полагайтесь на результат API. До отправки покажите игроку итоговую сумму.
В серверном адаптере маршрута с платёжной страницей обработайте успешные pending и active как создание одной заявки:
сохраните её id и передайте браузеру переход на paymentPageUrl в принятом у вас формате. requisite: null в pending —
штатное состояние, а не ошибка создания. Не обращайтесь к полям реквизита до проверки статуса и не ждите автовыбора по таймеру,
чтобы разрешить переход. Принятый callback active сам по себе не означает, что браузер открыл платёжную страницу.
POST /payment/pay-in/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10/amount-options/acceptContent-Type: application/json
{"offerId":"9c1e7b20d4f6"}Для исходной суммы передайте {"ownAmount":true} вместо offerId, только если это предлагается сессией. Не передавайте оба поля.
Один offerId относится к одной сделке; ранее выданный этой сессией вариант может быть принят, если сумма всё ещё доступна.
Пример значимых полей успешного ответа 200 (реквизит намеренно не приведён):
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "status": "active", "amount": 5500, "initialAmount": 5000, "amountChanged": true, "amountOptions": null, "paymentPageUrl": "https://pay.example.test/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10"}Обрабатывайте ответы единым автоматом состояния:
| Ответ / состояние | Действие интегратора |
|---|---|
pending, открытые варианты, срок не истёк | Покажите выбор или перейдите на paymentPageUrl. |
pending без вариантов / sessionStatus=accepted | Покажите ожидание, ограничьте длительность запросов и перечитывайте GET /payment/{id}; не создавайте второй депозит. |
200, актуальный active | Покажите итоговую amount и перейдите на возвращённый paymentPageUrl. Не продолжайте показывать старые кнопки. |
409 MATCHING_AMOUNT_OPTION_UNAVAILABLE | Проверьте trade.id, примените trade.status; при pending замените варианты из trade.amountOptions и объясните, что сумму заняли. При canceled покажите завершение. Не повторяйте старый вариант автоматически после 409. |
409 MATCHING_AMOUNT_SELECTION_CLOSED | Примените состояние той же сделки из trade или перечитайте GET /payment/{id}, если тела нет. active может означать автовыбор; canceled — закрытие. Не считайте любой CLOSED отменой. |
Истёк choiceExpiresAt, но сервер ещё pending | Отключите старый выбор и проверьте состояние. До финального действия таймера возможен промежуток; срок выбора не равен сроку оплаты. |
| Сеть / timeout, исход accept неизвестен | Сначала получите состояние той же сделки. Повтор допустим с тем же id и тем же выбором: успешный accept идемпотентен. Другой выбор после принятого даст CLOSED. |
| GET после успешного accept не удался | Повторяйте GET; показывайте ошибку проверки состояния и действие «Повторить». Новую сделку не создавайте. |
400, 401, 403, 404, 5xx | Покажите понятную ошибку и восстановление/обращение в поддержку; завершите индикатор загрузки. Не скрывайте ошибку в console. |
checking, checking_manually, dispute, success, success_changed, canceled | Следуйте текущему статусу и основному контракту; не предлагайте новую оплату по старому выбору. |
Для закрытой сессии без доступного тела разрешён такой ответ:
{"statusCode":409,"message":"MATCHING_AMOUNT_SELECTION_CLOSED","trade":null}Если после 200 active браузер остаётся на вашем экране, покажите явную ссылку «Перейти к оплате» с тем же paymentPageUrl.
Не открывайте popup только после асинхронного запроса: браузер может его заблокировать. Навигация в текущей вкладке или обычная
ссылка должны оставаться доступны. Проверяйте разрешённый адрес, не собирайте URL из offerId и не подставляйте домен наугад.
Для диагностики отдельно отмечайте: источник списка (demand / amountOptions), показ, клик, отправку accept, HTTP status и код
ошибки, состояние ответа, попытку навигации и результат на принимающей странице. Используйте id, свой идентификатор попытки,
время UTC и X-REQ-ID / X-CORRELATION-ID ответа. Не сохраняйте ключи, cookies, actionToken, реквизиты, персональные данные или сырой HAR.
Сигнал redirect_started означает попытку навигации, а не посещение. Выдача реквизита и автовыбор по таймеру не доказывают клик.
Сначала проверьте серверный адаптер отдельно: ответ создания 201 pending с requisite: null, заполненными paymentPageUrl
и amountOptions должен вернуть браузеру успешный результат с переходом; 201 active должен сохранить тот же путь навигации.
Идемпотентный повтор 200 pending должен сохранить ID заявки, а не создавать новую. Затем проверьте в браузере, что каждый из
этих результатов действительно открывает переданный URL или оставляет рабочую ссылку на него.
Контрактная проверка собственного экрана выбора: замокайте последовательно pending → 200 active, pending → 409 со свежими вариантами,
409 CLOSED → GET active, 409 CLOSED → GET canceled, 409 trade:null → GET 503, потерянный ответ accept и успешный accept
с сорванной навигацией. Проверьте видимый результат каждого клика, один ключ создания, отсутствие параллельных accept и рабочую
ссылку на оплату. Устаревший снимок сам по себе не доказывает причину неработающей кнопки.
Callback-и депозита
Section titled “Callback-и депозита”- На
pendingcallback-а нет — это состояние вы получили в ответе на создание. active— после выдачи реквизита (сразу, после выбора или по истечении выбора со своей суммой игрока), сinitialAmountиamountChanged.success/success_changed— как у обычного депозита;canceled— сcancelDetail, если отменён на этапе выбора.canceledпослеsuccess— сторно зачисления: заполненыcreditReversedAtиcreditReversedCrypto(раздел «Сторно зачисления» ниже).- В callback-ах
amountOptionsвсегдаnull.
Каждый callback — отдельное событие. Тело — состояние депозита в момент события, а не в момент доставки. В теле есть eventSeq —
номер события по депозиту: 1, 2, 3 … В ответах API поля eventSeq нет.
- Повторная доставка (ваш сервер не ответил
200) приходит с тем жеeventSeqи тем же телом. - События могут прийти не по порядку: повтор раннего события после сбоя доставки может прийти позже следующего.
- Обрабатывайте callback-и идемпотентно по паре (
id,eventSeq): событие уже обработано — ответьте200и ничего не делайте. - Актуальное состояние — у события с наибольшим
eventSeq. Событие сeventSeqменьше уже обработанного не применяйте, но ответьте200. - Повторная отправка по запросу в поддержку приходит новым событием (следующий
eventSeq) с текущим состоянием.
Пример: игрок получил реквизит, и через секунду вы отменили депозит. Придут два callback-а — active, затем canceled:
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "merchantTransactionId": "deposit-777", "status": "active", "amount": 5000, "crypto": "54054054", "fee": "1081081", "requisite": { "bank": "SBER", "sbpNumber": "79990001122", "ownerName": "Иван И." }, "requisiteIssuedAt": "2026-09-28T10:02:00.000Z", "cancelDetail": null, "amountOptions": null, "eventSeq": 1}{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "merchantTransactionId": "deposit-777", "status": "canceled", "amount": 5000, "crypto": "54054054", "fee": "1081081", "requisite": { "bank": "SBER", "sbpNumber": "79990001122", "ownerName": "Иван И." }, "requisiteIssuedAt": "2026-09-28T10:02:00.000Z", "cancelDetail": null, "amountOptions": null, "eventSeq": 2}Здесь показаны не все поля: остальные — как в ответе GET /payment/{id}.
Сторно зачисления (чек признан фейком)
Section titled “Сторно зачисления (чек признан фейком)”Если оператор платформы признал чек депозита фейком, а вы подтвердили, что игрок не использовал средства, платформа отменяет депозит уже после успеха:
- с вашего баланса списывается зачисление по депозиту —
crypto − fee(запись журнала балансаMATCHING_PAY_IN_CREDIT_REVERSED); - депозит получает
status: "canceled",creditReversedAt(время списания) иcreditReversedCrypto(списанная сумма, минимальные единицы USDT) — в callback-е,GET /payment/{id},GET /payment/merchant/{id}и в спискеGET /payment; - приходит callback
canceledсо следующимeventSeq. Повторногоsuccessне бывает.
Почему canceled, а не success_changed: success_changed — успешный статус (спор закрыт, сумма пересчитана), интеграции зачисляют по нему
игроку деньги и начисляют бонусы. canceled значит «депозит не состоялся» — так же платформа отменяет успешный депозит и у обычных
сделок. Новый статус не вводили, чтобы не сломать разбор ответов. Отличить сторно от обычной отмены можно по creditReversedAt: у сторно оно
заполнено.
Что сделать: получили canceled с заполненным creditReversedAt по депозиту, который вы уже зачислили, — спишите зачисление у игрока. Если
вы ответили платформе, что игрок уже использовал средства, сторно не будет: депозит остаётся success, callback-а нет.
Пример callback-а сторно:
{ "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "merchantTransactionId": "deposit-777", "status": "canceled", "amount": 5000, "crypto": "54054054", "fee": "1081081", "requisiteIssuedAt": "2026-09-28T10:02:00.000Z", "cancelDetail": null, "creditReversedAt": "2026-09-28T12:05:00.000Z", "creditReversedCrypto": "52972973", "eventSeq": 3}До сторно по этому депозиту приходили active (eventSeq 1) и success (eventSeq 2) с creditReversedAt: null.
Спор по депозиту
Section titled “Спор по депозиту”POST /payment/pay-in/{id}/dispute (тело: uploadMetaKey чека, по желанию amount — не больше двух знаков после запятой) и POST /payment/pay-in/{id}/dispute/cancel
работают как у обычных сделок и отвечают 200 с текущим состоянием депозита в формате этой страницы.
- Депозит
active— спор отложенный, как у обычных сделок: он начнётся, если сделка отменится по таймауту. - Депозит
canceled(игрок оплатил после отмены) — спор открывается сразу, если сумма ещё не закрыта другим платежом:200, статусdispute. - Сумма уже закрыта другим платежом —
409 MATCHING_LATE_PAYMENT_CAPACITY_TAKENсcaseId. Депозит остаётсяcanceled, спор передан оператору платформы. Повтор спора вернёт тот жеcaseId. Если оператор подтвердит оплату, придёт callback с итоговым статусом. - Выплата, которую закрывал депозит, уже закрыта —
409 MATCHING_RESERVE_RELEASEDсcaseId, спор тоже у оператора. - Депозит на этапе выбора суммы (
pending, без реквизита) оспаривать нечего —404. - Чек депозита уже на проверке или в споре (
checking,checking_manually,dispute) —409 MATCHING_CHECK_UNDER_REVIEW, вtrade— текущее состояние депозита. Дождитесь решения по чеку: итог придёт callback-ом.
Эндпоинты выбора суммы
Section titled “Эндпоинты выбора суммы”Все — с X-Secure-Key, только по своим сделкам (чужая → 404).
| Эндпоинт | Что делает | Успех |
|---|---|---|
GET /payment/pay-in/{id}/amount-options | текущий раунд без пересчёта | 200 + блок; после выбора / отмены → 409 MATCHING_AMOUNT_SELECTION_CLOSED |
POST /payment/pay-in/{id}/amount-options/accept {"offerId": "…"} или {"ownAmount": true} | выбрать вариант или свою сумму | 200, сделка active с реквизитом |
POST /payment/pay-in/{id}/amount-options/keep-amount | «Оставить мою сумму» | 200: active или pending раунда 2 со свежими вариантами (или canceled, если вариантов не осталось) |
POST /payment/pay-in/{id}/cancel | отказ на этапе выбора | 200, canceled, cancelDetail = amount_offer_declined |
- Варианты зафиксированы на раунд: GET их не пересчитывает, меняются они только после
409,keep-amountили смены раунда. - Повтор успешного
accept/keep-amount→200с текущим состоянием; повторcancelпо отменённой сделке →200с текущим состоянием. Отмена обычных (не invoice) сделок отвечает, как раньше,201. acceptбезofferIdиownAmountили с обоими →400.{"ownAmount": true}приownAmountAvailable: false→409 MATCHING_AMOUNT_OPTION_UNAVAILABLE.keep-amountв сессии, где «Оставить» не предлагали, →409 MATCHING_KEEP_AMOUNT_UNAVAILABLE.- Отмена депозита, чек которого уже на проверке или в споре (
checking,checking_manually,dispute), →409 MATCHING_CHECK_UNDER_REVIEW.
Пример гонки: вариант заняли
Section titled “Пример гонки: вариант заняли”POST /payment/pay-in/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10/amount-options/accept{"offerId": "9c1e7b20d4f6"}HTTP/1.1 409 Conflict{ "statusCode": 409, "message": "MATCHING_AMOUNT_OPTION_UNAVAILABLE", "trade": { "id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10", "status": "pending", "amount": 5000, "amountOptions": { "requestedAmount": 5000, "ownAmountAvailable": false, "offers": [{ "offerId": "e41a0c93b7d2", "amount": 6000, "diff": 1000, "recommended": true }], "insistAvailable": true, "round": 1, "choiceExpiresAt": "2026-09-28T10:09:30.000Z", "sessionStatus": "open" } }}Покажите игроку свежие варианты из trade.amountOptions. Если trade.status = "canceled" — вариантов больше нет (cancelDetail = no_matching_capacity).
Ошибки
Section titled “Ошибки”Все ошибки 409 мэтчинга — одно тело: {"statusCode": 409, "message": "<код>", "trade": {…}}, где trade — текущее состояние той же
сделки или выплаты в формате этой страницы (для спора — сделки из пути запроса; null, только если сделку не удалось найти). У
MATCHING_LATE_PAYMENT_CAPACITY_TAKEN и у MATCHING_RESERVE_RELEASED в ответ на спор в теле ещё caseId. Остальные ошибки — как везде:
{"statusCode", "message"}. Схема MatchingConflictResponseDto в OpenAPI перечисляет все коды 409 мэтчинга, включая коды админки платформы (MATCHING_USE_DISPUTES_SECTION,
MATCHING_CASE_ALREADY_RESOLVED, MATCHING_CLAIM_ALREADY_OPEN, MATCHING_CALLBACK_NOT_FINAL, MATCHING_FALLBACK_RELEASE_UNAVAILABLE), — merchant
API их не возвращает.
| Код | HTTP | Где | Смысл |
|---|---|---|---|
MATCHING_DISABLED | 403 | pay-out; pay-in при выключенной классике | мэтчинг выключен на платформе |
MATCHING_PAYOUT_DISABLED | 403 | pay-out | выплаты мэтчинга у вас выключены |
MATCHING_PAYIN_DISABLED | 403 | pay-in | депозиты мэтчинга у вас выключены, классика тоже |
MATCHING_CURRENCY_NOT_SUPPORTED | 403 | pay-out; pay-in | валюта не поддерживается мэтчингом (сейчас только RUB) |
MATCHING_TIMEOUT_OUT_OF_LIMITS | 403 | pay-out | timeoutInMin меньше минимального SLA |
MATCHING_AMOUNT_OUT_OF_LIMITS | 403 | pay-out | сумма вне лимитов выплаты |
MATCHING_OPEN_VOLUME_LIMIT_REACHED | 403 | pay-out | превышен лимит открытых выплат |
MATCHING_BALANCE_LIMIT_EXCEEDED | 403 | pay-out | не хватает баланса с учётом лимита овердрафта |
MISSED_PARAMS | 400 | pay-out | в реквизите нет ни sbpNumber, ни cardNumber |
NO_REQUISITE_FOUND_FOR_THIS_AMOUNT | 403 | pay-in | ничего не подошло, классика выключена |
INTEREST_RATE_NOT_FOUND_FOR_METHOD_OR_CURRENCY | 403 | pay-in | нет ставки на invoice-метод, классика выключена |
MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID | 409 | pay-out; pay-in | тот же merchantTransactionId с другим телом |
MATCHING_REQUISITE_ALREADY_OPEN | 409 | исторический код | Ограничение одной открытой выплаты на реквизит снято; новые выплаты по этому основанию не отклоняются. |
MATCHING_PAYOUT_NOT_CANCELABLE | 409 | отмена выплаты | в выплате есть оплаченные части или части в работе |
MATCHING_AMOUNT_OPTION_UNAVAILABLE | 409 | accept | вариант заняли / своя сумма недоступна; свежий блок — в trade.amountOptions |
MATCHING_AMOUNT_SELECTION_CLOSED | 409 | amount-options, accept, keep-amount | этап выбора закончился |
MATCHING_KEEP_AMOUNT_UNAVAILABLE | 409 | keep-amount | «Оставить мою сумму» не предлагали |
MATCHING_CHECK_UNDER_REVIEW | 409 | отмена депозита; POST /payment/pay-in/{id}/dispute | чек на проверке или в споре |
MATCHING_LATE_PAYMENT_CAPACITY_TAKEN | 409 | POST /payment/{type}/{id}/dispute по отменённому депозиту | сумма уже закрыта другим платежом, спор передан оператору (caseId) |
MATCHING_RESERVE_RELEASED | 409 | POST /payment/{type}/{id}/dispute, …/dispute/cancel | выплата уже закрыта, спор передан оператору |
MATCHING_LEGACY_TRADE | 409 | действия по архивным выплатам | архивная выплата мэтчинга (создана до 26.09.2026) |
MATCHING_FALLBACK_MANAGED_BY_PLATFORM | 403 | отмена / спор / закрытие fallback | fallback-выплатами управляет платформа |
MATCHING_EXPORT_CONCURRENCY_LIMIT | 429 | XLSX-выписка в кабинете | уже идут два экспорта; повторите через Retry-After секунд |
Других 429 у мэтчинга нет: POST /payment/pay-in и эндпоинты выбора не ограничиваются по частоте.
Postman-коллекция
Section titled “Postman-коллекция”Коллекция проходит оба сценария сверху вниз:
- Выплата: создание → GET → закрытие на оплаченном → отмена пустой (на отдельной выплате).
- Депозит, свой экран: создание → варианты → выбор варианта (
accept) → GET. - Депозит, «Оставить мою сумму» → раунд 2 → выбор варианта.
- Депозит, редирект: создание →
paymentPageUrl(дальше игрок на странице) → опрос GET. - Отказ на этапе выбора (
cancel).
Перед запуском заполните baseUrl, secureKey и callbackUrl; остальные переменные коллекция берёт из ответов.
Связанные разделы
Section titled “Связанные разделы”- Общая концепция (Pay-Out)
- Общая концепция (Pay-In)
- Уведомления (webhook)
- Создание сделки (pay-out)
- Создание сделки (pay-in)
Сейчас принимаем: подсказка для депозитов A
Section titled “Сейчас принимаем: подсказка для депозитов A”GET /payment/matching/demand?method=sbp_invoice с заголовком X-Secure-Key показывает, какие суммы депозитов сейчас подходят
общему пулу мэтчинга. method — sbp_invoice (по умолчанию) или c2c_invoice; подсказка пока только для RUB. Другой метод → 400.
Без действующего ключа → 401, действуют обычные ограничения доступа merchant API, включая IP whitelist.
Пример: две выплаты принимают только точный остаток 3 000,01 ₽ каждая:
{ "currency": "RUB", "method": "sbp_invoice", "enabled": true, "generatedAt": "2026-09-29T10:00:00.000Z", "expiresAt": "2026-09-29T10:00:30.000Z", "payoutCount": 2, "capacity": 6000.02, "buckets": [ { "from": 1000, "to": 1500, "payoutCount": 0, "capacity": 0 }, { "from": 1500, "to": 3000, "payoutCount": 0, "capacity": 0 }, { "from": 3000, "to": 5000, "payoutCount": 2, "capacity": 6000.02 }, { "from": 5000, "to": 10000, "payoutCount": 0, "capacity": 0 }, { "from": 10000, "to": null, "payoutCount": 0, "capacity": 0 } ], "exactAmounts": [{ "amount": 3000.01, "payoutCount": 2 }]}Все суммы — рубли, до двух знаков после запятой. generatedAt и expiresAt — время UTC в ISO 8601. Снимок пула кэшируется на
30 секунд; частый опрос не делает его свежее. Рекомендуем запрашивать раз в 30 секунд отдельно для каждого используемого метода.
buckets— пять диапазонов[from, to): нижняя граница включена, верхняя исключена;nullозначает отсутствие верхней границы.payoutCount— число выплат окна кандидатов, принимающих хотя бы одну сумму в диапазоне, а не любую сумму из него.capacityкорзины — сумма свободных остатков этих выплат. Одна выплата может попасть в несколько корзин: ёмкости и счётчики корзин нельзя складывать. Поля верхнего уровняcapacityиpayoutCountуже посчитаны без повторов. Ёмкость — текущий свободный остаток, а не гарантия, что весь он будет закрыт чеками выбранного диапазона или в пределах ваших лимитов.exactAmounts— точные остатки, сгруппированные по сумме и отсортированные по возрастанию. Депозит на такую сумму закрывает свободный остаток; не округляйте копейки. Если у выплаты есть другие чеки в обработке, её окончательный статус дождётся их.- Учитываются только выплаты в том же окне кандидатов по сроку, что использует подбор, живая занятость денег и слотов, удержания, взятый fallback, ожидающие претензии, план чеков выплаты и ваши лимиты депозитов. Суммы разных методов не смешиваются. Подсказка не раскрывает реквизиты, идентификаторы выплат или мерчантов B.
enabled=falseи нули означают выключенный движок/приём депозитов для вашего кабинета либо отсутствие RUB в пуле.enabled=trueи нули означают, что в снимке нет подходящей ёмкости. Ошибка загрузки не заменяется пустым или просроченным снимком.
Используйте корзины для выбора потока подходящих депозитов, а exactAmounts — для предложения игроку точной суммы, если ваш
сценарий допускает выбор суммы. Затем создавайте обычный POST /payment/pay-in с тем же invoice-методом. Подсказка не резервирует
выплату: между чтением и созданием депозита ёмкость может измениться. Всегда обрабатывайте штатный ответ создания, этап выбора
суммы и отказы по правилам интеграции выше. Не меняйте сумму уже созданного депозита по этой подсказке.
Та же карточка «Сейчас принимаем» доступна в разделе мэтчинга кабинета. В админской статистике она показывает общий пул с глобальными лимитами A, независимо от выбранного исторического периода и мерчанта. Исторический раздел статистики «Спрос» отражает уже полученные запросы и не заменяет эту подсказку.