Выплаты клиентам (PayOut)
Вывод средств: мерчант создаёт заявку, перевод получателю выполняет трейдер или API-провайдер
Выплаты клиентам (PayOut)
Выплата (PayOut) — это P2P-операция вывода средств, при которой мерчант создаёт заявку на перевод, а исполнитель — трейдер или API-провайдер — выполняет фактический перевод получателю со своего банковского счёта.
Процесс выплаты (PayOut Flow)
Деньги получателю приходят со счёта исполнителя (трейдера или API-провайдера), а не напрямую с баланса мерчанта. Баланс мерчанта компенсирует исполнителя за выполненный перевод внутри платформы Syncra.
Эндпоинт создания PayOut
POST /api/v1/p2p/merchant/deals/payoutПараметры запроса (JSON Body)
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
amount | int64 | Да | Сумма выплаты в минорных единицах (копейки). 500000 = 5000.00 RUB. |
currency | string | Да | ISO 4217 код fiat-валюты выплаты ("RUB", "KZT", "TRY", ...). Возвращается в ответе эхом. |
wallet_id | string(UUID) | Да | USDT-кошелёк мерчанта, с которого списываются средства: холд суммы создаётся в USDT (по SELL-курсу на момент создания), а не на fiat-кошельке. Это wallet_id USDT-строки из GET /balance. |
target_requisite | string | Да | Реквизиты получателя (номер карты, телефона, IBAN). |
target_name | string | Нет | Имя получателя для верификации банком/трейдером. |
target_bank | string | Условно* | Банк получателя: слаг ("ziraat", "garanti") или название. Используется для intra-bank каскадного матчинга (актуально для TRY/Havale). Пусто = без банковской подсказки (карты/СБП). |
requisite_details | map<string,string> | Нет | Именованные реквизиты получателя (см. ниже). Допустимые ключи: iban, account_number, document_number, bank_name, phone_number. Неизвестные ключи → 400 со списком допустимых. |
payment_method | string | Да | Ссылка на метод выплаты из вашего каталога GET /payment-methods: слаг ("sbp_rub", "card_rub", "mobcom_rub", "express-havale", ...; одинаков на всех контурах stage/prod) или UUID (поле id) — оба значения резолвятся в один и тот же метод. Отсутствие, неизвестное значение или метод чужой валюты → 400 INVALID_ARGUMENT (со списком допустимых слагов). |
idempotency_key | string | Да | Уникальный ID операции (идемпотентность). |
* target_bank обязателен для банковских методов (BANK_TRANSFER /
Havale), если банк не выводится из самого запроса: платформа определяет банк
по TCMB-коду из IBAN (позиции 5–9) через справочник GET /merchant/banks.
Если ни target_bank, ни код из IBAN не определены — запрос отклоняется
400 INVALID_ARGUMENT до создания сделки и холда (см.
Коды ошибок).
payment_method в PayOut принимает слаг (sbp_rub, card_rub,
mobcom_rub) или UUID метода, а НЕ uppercase enum BANK_CARD /
SBP. Enum-значения RequisiteType (BANK_CARD, E_WALLET, ...) относятся
к типу реквизита трейдера: в ответе GET /payment-methods (поле
requisite_types) это справочное описание канала, в запросах оно не
передаётся. Тот же унифицированный контракт у PayIn payment_method_id
(UUID или слаг) — обе ноги принимают оба формата.
Валюта метода должна совпадать с валютой сделки. Резолвер проверяет,
что найденный метод обслуживает валюту currency запроса: выплата
card_rub с currency: "TRY" отклоняется с 400 INVALID_ARGUMENT
(«payment method currency does not match the deal currency»). Берите метод
той же валюты из каталога GET /payment-methods (поле currency_code).
Пример запроса
{
"amount": 500000,
"currency": "RUB",
"wallet_id": "8f870a31-b88d-4cb0-a882-628d08cbda88",
"target_requisite": "2202201234567890",
"target_name": "Иван Иванович И.",
"payment_method": "card_rub",
"idempotency_key": "payout_order_99812"
}Пример ответа (200 OK)
Ответ — объект MerchantDealInfo в поле deal верхнего уровня (обёртки
data нет). Идентификатор сделки — deal_id (НЕ id). Для PAYOUT
payment_url пуст (он есть только у PayIn); вместо него заполнено
target_requisite. currency — эхо ISO-кода fiat-валюты выплаты из запроса.
Статус в ответе зависит от того, куда каскад маршрутизировал выплату (матчинг выполняется синхронно, в момент создания):
PROCESSING— маршрут на API-провайдера: выплата создана у провайдера в том же запросе, перевод уже в работе;MATCHED— маршрут на трейдера, свободный исполнитель найден (пример ниже);UNASSIGNED— свободного исполнителя нет на момент создания; retry-воркер продолжает матчинг (см. Статусы PayOut сделки).
Терминальный статус (COMPLETED / FAILED / EXPIRED) приходит
вебхуком — держатели статуса PROCESSING и MATCHED в ответе создания
равнозначны с точки зрения дальнейшего жизненного цикла.
{
"deal": {
"deal_id": "e81d77a0-0d3a-4422-b91c-7cb052a2119c",
"status": "MATCHED",
"amount": "500000",
"currency": "RUB",
"created_at": "2026-07-12T17:21:00Z",
"client_id": "",
"deal_type": "PAYOUT",
"updated_at": "2026-07-12T17:21:00Z",
"payment_url": "",
"target_requisite": "2202201234567890",
"received_amount": "0",
"amount_match_status": ""
}
}В V2 target_requisite в ответе — эхо значения из запроса, без
маскировки: это собственные реквизиты мерчанта, а не данные трейдера.
Маска вида 220220******7890 осталась только в legacy-эндпоинтах V1
(GET /v1/payments/outgoing/{id} и callback-конверте V1).
Для выплат, маршрутизированных на API-провайдера, тот же ответ создания
приходит со "status": "PROCESSING" — остальные поля идентичны.
Созданная выплата — полноценная сделка: deal_type=PAYOUT-записи видны в
GET /deals (объединённый листинг с PayIn) и доступны по
GET /deals/{deal_id} по deal_id из ответа создания
(см. Deals).
Статусы PayOut сделки
Канонический жизненный цикл — domain.PayOutStatus (7 значений). PayOut
использует отдельный домен статусов от PayIn. Полная таблица с V1-кодами
— в Справочнике статусов.
| Статус V2 | Финальный? | Описание |
|---|---|---|
INITIALIZED | Нет | Выплата создана, баланс захолдирован, ожидает мэтчинга. |
MATCHED | Нет | Трейдер/команда назначена. Статус в ответе создания при маршруте на трейдера (есть свободный исполнитель). |
UNASSIGNED | Нет | Исполнителя нет на момент создания. Retry-воркер продолжает мэтчинг; по истечении SLA — EXPIRED. |
PROCESSING | Нет | Перевод в работе: у API-провайдера — статус в ответе создания (выплата создана у провайдера синхронно); у трейдера — после подтверждения назначения. |
COMPLETED | Да (Успех) | Средства отправлены, холд списан. |
FAILED | Да (Отказ) | Отклонена (неверные реквизиты, отмена). Холд возвращён мерчанту. |
EXPIRED | Да (Таймаут) | Истекла до завершения перевода (в т.ч. по SLA из UNASSIGNED). |
Особые статусы
UNASSIGNED— на момент создания авто-матчинг каскада не нашёл свободного трейдера (все заняты / лимиты исчерпаны / нет реквизитов под эту валюту и сумму). Сделка возвращается с этим статусом в ответеPOST /deals/payout, после чего retry-воркер автоматически повторяет мэтчинг: при успехе статус переходит вMATCHED→ далее обычный жизненный цикл; если SLA на назначение истекает — выплата переходит в терминальныйEXPIRED, холд возвращается мерчанту. Не терминальный статус. Ручное назначение (admin override) остаётся доступным для оператора.- PayOut не использует PayIn-статусы (
PAYMENT_NOTIFIED,PAYMENT_VERIFIED,APPEALED,SOFT_DISPUTED).
Webhook-события PayOut используют те же имена, что PayIn: deal.completed
(успех) и deal.failed (отказ, status: FAILED). НЕ payout.*.
Подробнее — в
Callbacks.
Выплаты TRY / IBAN (Havale)
Для Турции выплаты идут как банковский перевод по IBAN (RequisiteType
BANK_TRANSFER): базовый реквизит — target_requisite (IBAN получателя),
дополнительно заполняются target_bank и requisite_details.
Новые поля запроса
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
target_bank | string | Условно | Банк получателя: слаг ("ziraat", "garanti", ...) или название. Передаётся провайдеру как банк выплаты и используется для intra-bank каскадного матчинга. Обязателен, когда банк не выводится из IBAN (см. «Определение банка» ниже); для карт/СБП не передаётся. |
requisite_details | map<string,string> | Нет | Именованные реквизиты сверх target_requisite/target_name. Допустимые ключи: iban, account_number, document_number, bank_name, phone_number. Неизвестные ключи → 400 InvalidArgument со списком допустимых. Отсутствие/пустая карта = «дополнительных реквизитов нет». |
Определение банка (bank derivation)
Для банковского метода (BANK_TRANSFER / Havale) платформа должна знать банк
получателя ещё до создания сделки. Банк считается определённым, если:
- передан валидный
target_bank(слаг изGET /merchant/banks), или - в реквизитах есть турецкий IBAN, чей TCMB-код (позиции 5–9:
TRxx BBBBB- счёт) резолвится в справочник банков платформы — банк выводится
автоматически,
target_bankпередавать не нужно.
- счёт) резолвится в справочник банков платформы — банк выводится
автоматически,
Если ни то, ни другое не выполняется — запрос отклоняется сразу, до создания сделки и взятия эскроу-холда:
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "{\"code\":3,\"message\":\"target_bank: bank not derivable from IBAN — pass target_bank (see GET /merchant/banks)\",\"details\":[]}",
"instance": "/api/v1/p2p-engine"
}Внутри платформы остаётся страховочный managed-отказ no_bank_derived
(статус FAILED + вебхук deal.failed + возврат холда) на случай, если
справочник банков изменится между валидацией запроса и матчингом. Для
интегратора это эквивалент «200 → мгновенно FAILED» не является
контрактом: единственный ожидаемый ответ на неопределяемый банк — 400
выше.
Значения requisite_details — это собственные реквизиты мерчанта
(данные его получателя), поэтому в ответах они возвращаются эхом, без
маскировки (в отличие от реквизитов трейдеров в PayIn). В логах
платформа сохраняет только набор ключей, без значений.
Пример запроса (TRY / Havale)
{
"amount": 2500000,
"currency": "TRY",
"wallet_id": "8f870a31-b88d-4cb0-a882-628d08cbda88",
"target_requisite": "TR33 0006 1005 1978 6457 8413 26",
"target_name": "Ahmet Yilmaz",
"target_bank": "ziraat",
"requisite_details": {
"iban": "TR33 0006 1005 1978 6457 8413 26",
"document_number": "12345678901"
},
"payment_method": "express-havale",
"idempotency_key": "payout_try_order_00042"
}amount: 2500000= 25 000.00 TRY (минорные единицы — куруш, 2 знака).payment_method— слаг (или UUID) Havale-метода, заведённого для вашего тенанта (например,express-havale); значение резолвится против активных методов тенанта — неизвестная ссылка или метод другой валюты отклоняется с400(список методов и слагов вашего тенанта —GET /api/v1/p2p/merchant/payment-methods, см. Banks).requisite_details.document_number— пример дополнительного реквизита (TC kimlik / номер документа), нужен не всегда: передавайте, когда метод или банк получателя его требует.
Пример ответа (200 OK)
{
"deal": {
"deal_id": "c25f9b31-7e40-4a11-8f0a-6d92b7c4e155",
"status": "MATCHED",
"amount": "2500000",
"currency": "TRY",
"created_at": "2026-08-18T11:02:00Z",
"client_id": "",
"deal_type": "PAYOUT",
"updated_at": "2026-08-18T11:02:00Z",
"payment_url": "",
"target_requisite": "TR33 0006 1005 1978 6457 8413 26",
"received_amount": "0",
"amount_match_status": ""
}
}target_requisite — эхо из запроса без маскировки (см. NOTE выше).
Лимиты на разовые выплаты
Лимиты сумм не захардкожены — они динамически определяются конфигурацией тенанта:
| Источник лимита | Управление | Описание |
|---|---|---|
Каскадные правила (cascade_rules.max_amount) | Админ-панель | Максимальная сумма для конкретного каскада маршрутизации. |
Реквизиты трейдеров (requisites.max_amount) | Трейдер / Админ | Лимит на одну транзакцию для конкретного реквизита. |
Конфиг тенанта (system_config) | Суперадмин | Глобальные лимиты автоодобрения. |
Значения лимитов можно менять администратором в реальном времени без
перезапуска сервисов. При необходимости отправить сумму выше лимита одной
транзакции — разделите её на несколько запросов с уникальными
idempotency_key.
События вебхуков
| Событие | Статус выплаты | Описание |
|---|---|---|
deal.created | INITIALIZED / MATCHED / UNASSIGNED / PROCESSING | Выплата создана (статус — на момент ответа создания после синхронного авто-матчинга; PROCESSING — маршрут на API-провайдера). |
deal.completed | COMPLETED | Выплата завершена |
deal.failed | FAILED | Выплата отклонена |
deal.expired | EXPIRED | Выплата истекла до завершения перевода (в т.ч. по SLA из UNASSIGNED); холд возвращён |
Валидация IBAN
Если метод выплаты принимает IBAN (тип реквизита BANK_TRANSFER), получатель проходит проверку контрольной суммы ISO 13616: реквизит с опечаткой отклоняется сразу (400, маскированный IBAN в тексте ошибки), пробелы и регистр нормализуются до проверки. Карточные и телефонные методы проверки не требуют.