Выплаты клиентам (PayOut)
Вывод средств через P2P-трейдеров: мерчант создаёт заявку, трейдер переводит деньги получателю со своего банка
Выплаты клиентам (PayOut)
Выплата (PayOut) — это P2P-операция вывода средств, при которой мерчант создаёт заявку на перевод, а трейдер выполняет фактический перевод получателю со своего банковского счёта.
Процесс выплаты (PayOut Flow)
Деньги получателю приходят со счёта трейдера, а не напрямую с баланса мерчанта. Баланс мерчанта компенсирует трейдера за выполненный перевод внутри платформы 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 операции (идемпотентность). |
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-валюты выплаты из запроса.
Статус в ответе зависит от синхронного авто-матчинга каскада: если есть
свободный трейдер — MATCHED (пример ниже); если свободного трейдера
нет — UNASSIGNED (далее retry-воркер, см.
Статусы PayOut сделки).
{
"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).
Созданная выплата — полноценная сделка: 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 | Нет | Трейдер осуществляет перевод. |
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 каскадного матчинга. Пусто = без подсказки (карты/СБП). |
requisite_details | map<string,string> | Нет | Именованные реквизиты сверх target_requisite/target_name. Допустимые ключи: iban, account_number, document_number, bank_name, phone_number. Неизвестные ключи → 400 InvalidArgument со списком допустимых. Отсутствие/пустая карта = «дополнительных реквизитов нет». |
Значения 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 | Выплата создана (статус — на момент ответа создания после синхронного авто-матчинга). |
deal.completed | COMPLETED | Выплата завершена |
deal.failed | FAILED | Выплата отклонена |
deal.expired | EXPIRED | Выплата истекла до завершения перевода (в т.ч. по SLA из UNASSIGNED); холд возвращён |