API Reference

REST API

REST поверх HTTPS, JSON в обе стороны. База — https://api.balancedpay.pro. Bearer-ключ из ЛК, опционально с HMAC-подписью; Idempotency-Key на create-операциях.

Платежи

Магазин определяется по API-ключу — один ключ соответствует одному магазину. Для нескольких магазинов выпустите отдельные ключи в личном кабинете.

Создание платежа

POST/api/v1/public/payments
Создаёт платёжную сессию. В ответе — объект платежа и payment_url, на который нужно перенаправить покупателя.
Тело запроса
amountСумма в минорных единицах. Для RUB — копейки. Обязательно.
currencyКод валюты ISO-4217. По умолчанию RUB.
methodСпособ оплаты: sbp, cards, tpay, sberpay, recurrent или any. По умолчанию any — покупатель выбирает на нашей странице.
order_idВаш идентификатор заказа. Если не передан, сгенерируется ord_<timestamp>.
customer_idВаш идентификатор клиента. Сохраняется в metadata и используется для группировки операций.
return_urlURL, на который вернётся покупатель после оплаты. Требуется схема https://.
descriptionНазначение платежа. Отображается в чеке покупателя.
webhook_urlАдрес webhook-получателя только для этого платежа. Переопределяет настройки магазина.
metadataПроизвольные пары ключ-значение. До 20 пар, значения — строки до 500 символов.
Запрос
curl -X POST https://api.balancedpay.pro/api/v1/public/payments \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 7f2a-pay-1042' \
  -d '{
    "amount":      150000,
    "currency":    "RUB",
    "method":      "sbp",
    "order_id":    "ORDER-1042",
    "customer_id": "user-7821",
    "return_url":  "https://example.com/order/1042/done"
  }'
Ответ
{
  "payment": {
    "id":          "5a331a39-32bf-4940-afb1-855e2fc6757f",
    "merchant_id": "22222222-2222-2222-2222-222222222222",
    "shop_id": "33333333-3333-3333-3333-333333333333",
    "order_id":    "ORDER-1042",
    "amount":      150000,
    "currency":    "RUB",
    "method":      "sbp",
    "status":      "pending",
    "expires_at":  "2026-05-05T13:42:00Z",
    "created_at":  "2026-05-05T12:42:00Z"
  },
  "payment_url": "https://pay.balancedpay.pro/pay/5a331a39-…",
  "mode":        "live",
  "bank":        "Сбер",            // имя банка-эквайера, если он ответил sync
  "bank_ref":    "262512345678"     // ID операции в банке (только для h2h)
}

Поля idempotent: true, failure_code и failure_message возвращаются при повторе запроса по Idempotency-Key или при синхронном отказе банка. В тестовом режиме добавляются служебные поля симулятора — описаны на странице Тестовая среда.

Проверка статуса платежа

GET/api/v1/public/payments/{id}
Возвращает полный объект платежа с актуальным status, finalized_at и списком возвратов refunds[]. Используется для синхронной проверки статуса и после получения webhook-уведомления.
Запрос
curl -X GET https://api.balancedpay.pro/api/v1/public/payments/5a331a39-32bf-4940-afb1-855e2fc6757f \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json'
Ответ
{
  "id":              "5a331a39-32bf-4940-afb1-855e2fc6757f",
  "merchant_id":     "22222222-2222-2222-2222-222222222222",
  "shop_id":     "33333333-3333-3333-3333-333333333333",
  "shop_name":   "Web checkout",
  "order_id":        "ORDER-1042",
  "status":          "succeeded",
  "amount":          150000,
  "amount_refunded": 0,
  "currency":        "RUB",
  "method":          "sbp",
  "rrn":             "262512345678",
  "bank_name":       "Сбер",
  "return_url":      "https://example.com/order/1042/done",
  "created_at":      "2026-05-05T12:42:00Z",
  "captured_at":     "2026-05-05T12:43:11Z",
  "finalized_at":    "2026-05-05T12:43:11Z",
  "expires_at":      "2026-05-05T13:42:00Z",
  "processing_until":"2026-05-05T13:02:00Z",
  "metadata":        { "customer_id": "user-7821" },

  "customer_card_brand":  "visa",
  "customer_card_mask":   "411111******1111",
  "customer_card_holder": "IVAN IVANOV",
  "customer_phone_mask":  "+7•••••••12-34",
  "customer_payer_bank":  "Сбер",

  "refunds": [
    {
      "id":           "9b2c1f8a-1111-2222-3333-444444444444",
      "payment_id":   "5a331a39-32bf-4940-afb1-855e2fc6757f",
      "amount":       50000,
      "reason":       "частичный по запросу",
      "status":       "succeeded",
      "created_at":   "2026-05-05T15:10:00Z",
      "completed_at": "2026-05-05T15:10:08Z"
    }
  ]
}

Отмена платежа

POST/api/v1/public/payments/{id}/cancel
Работает только в статусе pending. На остальных статусах возвращается 409 not_pending. После успешной отмены отправляется webhook payment.cancelled.
Запрос
curl -X POST https://api.balancedpay.pro/api/v1/public/payments/5a331a39-32bf-4940-afb1-855e2fc6757f/cancel \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json'
Ответ
{
  "status": "cancelled"
}

Метод any (мультиформа)

Когда мерчант создаёт платёж с method: "any" (или вообще без поля method), в ответе payment_url ведёт на нашу страницу https://pay.balancedpay.pro/pay/<id>. Покупатель сам выбирает способ оплаты: карта, СБП, T-Pay, SberPay или recurrent.

Жизненный цикл

  1. Создаётся платёж со статусом pending, methods=any. Banking-сессии ещё нет, processing_until пустой.
  2. Покупатель открывает payment_url, видит кнопки методов.
  3. Жмёт «Оплатить через СБП» (например). Метод фиксируется: статус остаётся pending, поле method меняется с any на sbp. Webhook на этом шаге не отправляется.
  4. Открывается банк-сессия: статус становится processing, processing_until заполняется на длительность TTL метода (для СБП — 20 мин). Webhook'ом этот переход не сопровождается.
  5. Банк присылает финальный статус: succeeded или failed. Прилетает webhook payment.succeeded или payment.failed.
Если за время processing_until банк не вернёт статус, платёж переходит в expired, и мерчант получает webhook payment.expired. Это другая семантика, чем failed: банк не отказал, просто не закрыл сессию.

До выбора метода платёж нельзя ни отменить (cancel разрешён только в pending — и здесь он сработает), ни вернуть (succeeded ещё не наступил). Мерчант может смотреть статус через GET /payments/{id} или ждать webhook.

Поле metadata

metadata — произвольные пары ключ-значение, которые вы передаёте при создании платежа. Мы их не интерпретируем, только храним и отдаём обратно. Удобно прокидывать ID корзины, источник трафика, A/B-метку и т.п.

Лимиты

  • Все значения — строки. Числа и булевы передавайте как "42" / "true", мы не трогаем.
  • Рекомендуем не больше 20 пар на платёж и 500 символов в значении. Жёсткого лимита нет, но сверху всё это лежит в JSONB-колонке БД.
  • Кодировка — UTF-8.

Где их потом увидеть

  • В ответе POST /payments — поле payment.metadata.
  • В ответе GET /payments/{id} — то же поле.
  • В data-объекте webhook'а.

Зарезервированные ключи

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

ключкогда появляется
modeвсегда. Значение test или live по используемому ключу.
webhook_urlесли вы передали webhook_url в теле POST /payments.
customer_idесли вы передали customer_id в теле POST /payments.
bank_reflive-режим: ID операции на стороне банка-эквайера.
failure_codeна failed/expired: наш унифицированный код отказа (см. раздел «Коды отказов»).
bank_failure_codeна failed: сырой код от банка. Только для аудита, программно опираться лучше на failure_code.
simulator_outcometest-режим: запланированный исход симулятора (succeeded, failed, requires_action, pending).
simulator_failure_codetest-режим: код, который симулятор подсунет как банковский ответ.

Не используйте эти имена для своих ключей: при пересечении мы перезапишем ваше значение.

Возврат покупателя в магазин

Если в POST /payments вы указали return_url, после достижения финального статуса мы перенаправим покупателя по этому адресу с двумя query-параметрами:

https://example.com/order/1042/done?payment_id=5a331a39-…&status=succeeded
параметрзначение
payment_idUUID платежа.
statussucceeded | failed | cancelled | refunded | expired.

Существующие query-параметры в вашем return_url сохраняются. Если в URL уже был ?ref=email, он останется, плюс добавятся наши два.

Не доверяйте параметрам как факту оплаты. Покупатель может подменить URL руками. Перед тем как «поздравить с покупкой», подтвердите статус сервер-сайдом — через webhook payment.succeeded или явный GET /payments/{id}.

Когда происходит редирект

  • succeeded: через ~2 секунды после показа экрана «Платёж выполнен».
  • failed / cancelled / refunded / expired: сразу.
  • Если return_url не указан, мы оставляем покупателя на нашей странице с состоянием.

Возвраты

Возврат — самостоятельный объект, привязанный к платежу. На одном платеже допускается несколько частичных возвратов; когда их сумма достигает amount исходного платежа, его статус становится refunded. На каждый успешный возврат отправляется webhook payment.refunded.

Объект Refund

ПолеТипОписание
idUUIDИдентификатор возврата.
payment_idUUIDПлатёж, по которому сделан возврат.
merchant_idUUIDМерчант. Определяется по API-ключу.
amountint64Сумма возврата в минорных единицах.
reasonstringПричина возврата. Попадает в чек и audit-лог.
statusenumpending → succeeded или failed.
created_attimeКогда возврат был создан.
completed_attimeМомент перехода в финальный статус.
{
  "id":           "9b2c1f8a-1111-2222-3333-444444444444",
  "payment_id":   "5a331a39-32bf-4940-afb1-855e2fc6757f",
  "merchant_id":  "22222222-2222-2222-2222-222222222222",
  "amount":       50000,
  "reason":       "частичный возврат по запросу клиента",
  "status":       "succeeded",
  "created_at":   "2026-05-05T15:10:00Z",
  "completed_at": "2026-05-05T15:10:02Z"
}

Возврат средств

POST/api/v1/public/payments/{id}/refund
Полный или частичный возврат на платёж в статусе succeeded. Суммарный возврат не может превышать amount исходного платежа. На каждый успешный возврат отправляется webhook payment.refunded.
Тело запроса
amountСумма возврата в минорных единицах. Если поле не передано или равно 0 — возвращается весь остаток: amount − amount_refunded.
reasonПричина возврата. Отображается покупателю в чеке и фиксируется в audit-логе.
Запрос
curl -X POST https://api.balancedpay.pro/api/v1/public/payments/5a331a39-32bf-4940-afb1-855e2fc6757f/refund \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json' \
  -d '{
  "amount": 50000,
  "reason": "частичный возврат по запросу клиента"
}'
Ответ
{
  "id":           "9b2c1f8a-1111-2222-3333-444444444444",
  "payment_id":   "5a331a39-32bf-4940-afb1-855e2fc6757f",
  "merchant_id":  "22222222-2222-2222-2222-222222222222",
  "amount":       50000,
  "reason":       "частичный возврат по запросу клиента",
  "status":       "succeeded",
  "created_at":   "2026-05-05T15:10:00Z"
}

Отдельного эндпоинта получения возврата по id в публичном API нет. Актуальный список возвратов всегда возвращается в массиве refunds[] ответа GET /api/v1/public/payments/{id}. Сводный отчёт по всем возвратам магазина доступен в личном кабинете.

Выплаты

Выплата по СБП на счёт физлица

POST/api/v1/public/payouts
Создаёт выплату на номер телефона и member_id банка. Магазин определяется по API-ключу. Финальный статус (succeeded или failed) приходит webhook-ом payout.*.
Тело запроса
amountСумма выплаты в минорных единицах (копейки). Обязательно.
methodСпособ выплаты. Сейчас доступен только payouts_sbp; используется по умолчанию.
phoneТелефон получателя в формате +7XXXXXXXXXX. Обязательно.
bank_idmember_id банка-получателя в реестре НСПК (12 цифр). Например, 100000000111 — Сбербанк. Обязательно.
full_nameФИО получателя в одну строку. Используется банком для дополнительной сверки.
Запрос
curl -X POST https://api.balancedpay.pro/api/v1/public/payouts \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 7f2a-payout-001' \
  -d '{
    "amount":    250000,
    "method":    "payouts_sbp",
    "phone":     "+79001234567",
    "bank_id":   "100000000111",
    "full_name": "Иванов Иван Иванович"
  }'
Ответ
{
  "payout": {
    "id":          "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "merchant_id": "22222222-2222-2222-2222-222222222222",
    "shop_id": "33333333-3333-3333-3333-333333333333",
    "amount":      250000,
    "currency":    "RUB",
    "method":      "payouts_sbp",
    "status":      "pending",
    "recipient": {
      "phone":     "+79001234567",
      "bank_id":   "100000000111",
      "full_name": "Иванов Иван Иванович"
    },
    "created_at":  "2026-05-05T16:01:00Z"
  },
  "mode":     "live",
  "bank":     "Сбер",            // имя банка-эквайера, если он ответил sync
  "bank_ref": "PAY2026050601234"
}

Поля failure_reason и idempotent возвращаются при отказе или повторе запроса по Idempotency-Key. В тестовом режиме добавляются служебные поля симулятора — описаны на странице Тестовая среда.

Статус выплаты

GET/api/v1/public/payouts/{id}
Возвращает текущее состояние выплаты. Статус проходит pending → succeeded или failed. На финальный статус отправляется webhook payout.succeeded или payout.failed.
Запрос
curl -X GET https://api.balancedpay.pro/api/v1/public/payouts/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json'
Ответ
{
  "id":            "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "merchant_id":   "22222222-2222-2222-2222-222222222222",
  "shop_id":   "33333333-3333-3333-3333-333333333333",
  "amount":        250000,
  "currency":      "RUB",
  "method":        "payouts_sbp",
  "status":        "succeeded",
  "fail_reason":   "",
  "recipient": {
    "phone":     "+79001234567",
    "bank_id":   "100000000111",
    "full_name": "Иванов Иван Иванович"
  },
  "created_at":    "2026-05-05T16:01:00Z",
  "completed_at":  "2026-05-05T16:01:09Z"
}

Банки СБП

Поле bank_id в POST /payouts — это member_id участника НСПК (12 цифр). Полный реестр ведёт НСПК; мы зеркалим его через интеграцию с Банком 131 и отдаём одним списком.

Справочник банков СБП

GET/api/v1/public/banks/sbp
Полный реестр участников СБП с member_id и названиями на русском и английском. Синхронизируется с реестром НСПК через bank131. Подходит для рендера выпадающего списка в форме выплаты.
Запрос
curl -X GET https://api.balancedpay.pro/api/v1/public/banks/sbp \
  -H 'Authorization: Bearer sk_test_x9k…' \
  -H 'Content-Type: application/json'
Ответ
{
  "items": [
    { "member_id": "100000000004", "name": "Тинькофф Банк", "name_en": "T-Bank" },
    { "member_id": "100000000005", "name": "ВТБ",            "name_en": "VTB" },
    { "member_id": "100000000007", "name": "Альфа-Банк",     "name_en": "Alfa-Bank" },
    { "member_id": "100000000111", "name": "Сбербанк",       "name_en": "Sberbank" },
    // … и так весь реестр НСПК (~200 участников), отсортирован по name
  ]
}

Если нужного банка нет в списке

Справочник — список для UI, не allow-list. Принимается любой member_id из официального реестра НСПК; ограничения нет, валидация проходит на стороне банка-получателя.

Валидация на стороне balancedpay

  • bank_id не из 12 цифр — 400 bad_request возвращается до отправки в банк.
  • member_id формально корректен, но в НСПК отсутствует — банк-эквайер вернёт отказ, операция перейдёт в failed с failure_code do_not_honor или bank_unknown, либо запрос отдаст 502 router_error.