Документация API SBP Demo

API приёма платежей

Публичная документация для клиентов (интеграторов): как устроен обмен, кто кому что отправляет, статусы заказа и вебхуки. Эта страница и её LLM-версия доступны без авторизации.

1 Общая картина: участники и потоки #

В обмене участвуют четверо. Вы (клиент/интегратор) создаёте заказы и принимаете вебхуки. orders-api — сервер API этого стенда. СБП — даёт QR/ссылку на оплату и сообщает об оплате. Плательщик — ваш пользователь, который сканирует QR в приложении банка.

flowchart LR
    Payer["👤 Плательщик
(приложение банка)"] Client["🧑‍💻 Ваш сервер
(интегратор)"] Order["⚙️ orders-api
этот стенд"] Provider["🏦 СБП"] WH["📬 Ваш webhook_url"] Client -- "POST /api/v1/orders
Bearer-токен" --> Order Order -- "создание платёжной сессии
подпись RSA" --> Provider Provider -- "сессия + QR + deeplink" --> Order Order -- "201: order_id, QR,
sbp_deeplink" --> Client Payer -- "сканирует QR,
подтверждает оплату" --> Provider Provider -- "вебхук
payment_finished" --> Order Order -- "200 OK (всегда)" --> Provider Order -- "order.status_changed
X-Webhook-Signature (RSA)" --> WH WH -- "2xx" --> Order style Order fill:var(--accent-soft),stroke:var(--link) style Provider fill:var(--warn-bg),stroke:var(--warn-text) style Client fill:var(--ok-bg),stroke:var(--ok-text) style Payer fill:var(--th-bg),stroke:var(--muted) style WH fill:var(--ok-bg),stroke:var(--ok-text)

Кто что вызывает

вы → orders-api
POST/GET /api/v1/orders… — создание, списки, действия
Авторизация: Authorization: Bearer <uuid>.<secret>
Ответ: JSON синхронно (201/200/202/ошибка)
orders-api → СБП
создание платёжной сессии, confirm, cancel, refund
Авторизация: подпись Base64(RSA-SHA256(тело))
Ответ: JSON с сессией/QR или ошибкой СБП
СБП → orders-api
вебхуки payment_finished и др.
Авторизация: подпись RSA (на demo — проверка выкл.)
Ответ: всегда 200 (чтобы СБП не ретраила), ошибки — в лог
orders-api → вы
POST <webhook_url>order.status_changed при каждой реальной смене статуса
Авторизация: X-Webhook-Signature = Base64(RSA-SHA256(тело))
Ответ: вы отвечаете 2xx; до 3 попыток с бэкоффом

Что приходит вам

Сразу после POST /api/v1/orders
синхронный ответ 201
order_id, статус, rub_amount, rate_used, sbp_deeplink, qr_image_base64
Заказ создан и готов к оплате
вебхук order.status_changed
заказ + блок payment (QR, deeplink)
Плательщик оплатил
вебхук order.status_changed
статус succeeded (или промежуточные awaiting_*)
Отмена / ошибка / возврат
вебхук order.status_changed
canceled / failed / refunded / partially_refunded
Возврат на модерации
синхронный ответ 202
{"status":"pending_review"} — решение придёт вебхуком

Вебхуки приходят только если вашему клиенту задан webhook_url (задаёт оператор при заведении клиента).

2 Жизненный цикл заказа #

Полная последовательность от создания до успешной оплаты:

sequenceDiagram
    autonumber
    participant C as Ваш сервер
    participant O as orders-api
    participant P as СБП
    participant U as Плательщик

    C->>O: POST /api/v1/orders (amount USD, return_url, …)
    Note over O: пауза? лимиты? курс из кеша?
rub_amount = USD × курс × (1+markup) O->>P: создание платёжной сессии (подпись RSA) P-->>O: сессия + sbp_deeplink + QR O-->>C: 201 { order_id, status, rub_amount, QR } O->>C: вебхук order.status_changed (processing) U->>P: сканирует QR, подтверждает в банке P->>O: вебхук (payment_finished, accepted) O-->>P: 200 OK O->>C: вебхук order.status_changed (succeeded) Note over C: заказ оплачен — деньги зачислены opt Отмена до оплаты C->>O: POST /api/v1/orders/{id}/cancel O->>P: отмена сессии O-->>C: 200 { status: canceled } O->>C: вебхук order.status_changed (canceled) end opt Возврат после оплаты C->>O: POST /api/v1/orders/{id}/refund { amount RUB | полный } O->>P: возврат платежа O-->>C: 200 OK (или 202 pending_review — модерация) O->>C: вебхук (refunded / partially_refunded) end

Что сервис проверяет при создании (по порядку)

  1. Токен — клиент существует, активен, секрет совпал (401).
  2. Пауза — оператор мог приостановить создание заказов (503).
  3. Лимиты клиента — min/max суммы, дневные/недельные обороты и число заказов (400).
  4. Курс — свежий USD/RUB в кеше; нет — 503, заказ не создаётся.
  5. Идемпотентность — повтор с тем же ключом вернёт существующий заказ.
  6. СБП — система недоступна: заказ сохраняется со статусом failed, ответ 502, вебхук клиенту отправляется.

3 Диаграмма статусов #

stateDiagram-v2
    [*] --> pending : POST /orders
    pending --> created : сессия СБП создана
    pending --> failed : СБП недоступна (502)
    created --> processing : СБП начала обработку
    created --> canceled : cancel / срок сессии
    processing --> action_required : ждёт действия плательщика
    processing --> awaiting_confirm
    processing --> awaiting_capture
    processing --> succeeded : оплачен (accepted)
    processing --> canceled
    processing --> failed
    action_required --> succeeded
    action_required --> canceled
    awaiting_confirm --> succeeded : confirm
    awaiting_capture --> succeeded
    succeeded --> partially_refunded : частичный возврат
    succeeded --> refunded : полный возврат
    partially_refunded --> refunded : возврат остатка

    succeeded : succeeded ✅ финал*
    canceled : canceled ⛔ финал
    failed : failed ⛔ финал
    refunded : refunded ↩ финал

* succeeded финален до возврата: refunded/partially_refunded — тоже финальные. Финальные статусы «липкие»: запоздавший вебхук с промежуточным статусом их не затирает.

Статусы и их источники

СтатусЗначениеКто выставляет
pendingзапись создана, запрос в СБП ещё не ушёл/идётсервис при создании
createdплатёжная сессия создана, QR выданответ СБП / вебхук СБП
processingСБП обрабатывает платёж (in_progress)вебхук СБП / sync
action_requiredнужно действие плательщика (next_action)вебхук СБП
awaiting_confirmоперация ждёт confirmвебхук СБП
awaiting_captureоперация ждёт captureвебхук СБП
succeededоплачен (сессия accepted)вебхук СБП / confirm / sync
canceledотменён до оплатыcancel / вебхук СБП
failedошибка (в т.ч. недоступность СБП)сервис / вебхук СБП
refundedвозвращена вся суммапо сумме принятых возвратов СБП
partially_refundedвозвращена часть суммыпо сумме принятых возвратов СБП
Важно: сессия СБП после оплаты навсегда остаётся accepted — возвраты видны только в списке возвратов платежа внутри сессии. Сервис сам пересчитывает их сумму и выставляет refunded/partially_refunded — вам об этом приходит вебхук.

4 Основы #

  • База: https://fdsklfgejhmgrfdgbdfy.site, всё — JSON (кодировка UTF-8).
  • Заказы создаются в USD в центах (10000 = $100.00), оплата идёт в рублях через СБП.
  • Сумма в рублях считается сервисом: курс USD/RUB из кеша × (1 + наценка клиента). Итог приходит в ответе (rub_amount, копейки) и фиксируется в заказе.
  • Ссылка на оплату (СБП deeplink) и QR — в ответе создания заказа и в вебхуках.
  • Длина тела запроса — до 1 МиБ.
  • Время в ответах — UTC (ISO 8601). Даты в фильтрах — по МСК.

5 Авторизация #

Bearer-токен в заголовке. Формат: <client_uuid>.<secret> — выдаётся при заведении вашего клиента, у каждого клиента свой ключ. Перевыпуск токена делает старый недействительным.

Для удобства все примеры ниже используют переменные окружения — задайте их один раз:

bashнастройка
export TOKEN="<client_uuid>.<secret>"
export API="https://fdsklfgejhmgrfdgbdfy.site"

# проверка: список ваших заказов
curl -s "$API/api/v1/orders?limit=1" -H "Authorization: Bearer $TOKEN" | jq

Ошибки: 401 — токен отсутствует/неверен/клиент на паузе. Изоляция: вы видите и двигаете только свои заказы — чужой id даст 403.

6 Создание заказа #

POST /api/v1/orders

ПолеТипОписание
amountint, обязательноеСумма в центах USD, > 0
currencystringТолько usd (можно не указывать)
return_urlstring, обязательноеКуда вернуть плательщика
customer_refstringВаш идентификатор плательщика (иначе — idempotency_key или случайный UUID)
payment_descriptionstringНазначение платежа
sender_ipv4stringIPv4 плательщика (валидируется)
idempotency_keystringКлюч идемпотентности (см. ниже)

1Создайте заказ и сохраните его id

bashсоздание заказа на $1.00
ORDER_ID=$(curl -s -X POST "$API/api/v1/orders" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100,
    "currency": "usd",
    "return_url": "https://shop.example.com/return",
    "customer_ref": "user-42",
    "payment_description": "Заказ №1042",
    "idempotency_key": "order-1042"
  }' | jq -r .order_id)
echo "$ORDER_ID"

Ответ 201:

jsonответ
{
  "order_id": "7c9e…",
  "status": "processing",
  "amount": 100,
  "currency": "usd",
  "rate_used": 87.46,               // курс, зафиксированный в заказе
  "rub_amount": 8746,               // сумма к оплате: копейки RUB
  "return_url": "https://shop.example.com/return",
  "sbp_deeplink": "https://qr.nspk.ru/…",  // ссылка на оплату
  "qr_image_base64": "…",                  // картинка QR
  "created_at": "…", "updated_at": "…"
}

2Отдайте плательщику ссылку или QR

Поле sbp_deeplink — ссылка на оплату (СБП), qr_image_base64 — картинка QR. На demo-стенде «оплату» можно сымитировать действием confirm (см. раздел 9).

3Проверьте результат

bashстатус заказа
curl -s "$API/api/v1/orders/$ORDER_ID" \
  -H "Authorization: Bearer $TOKEN" | jq '{status, rub_amount}'

Коды ошибок: 400 — валидация/лимиты; 503 — нет свежего курса или создание заказов приостановлено оператором; 502 — СБП недоступна (заказ при этом сохраняется со статусом failed).

7 Список заказов #

GET /api/v1/orders — только ваши заказы.

Query-параметры: id (префикс UUID), status, amount_min/amount_max (USD), from/to (YYYY-MM-DD, по МСК), limit (≤500).

bashуспешные заказы за период
curl -s "$API/api/v1/orders?status=succeeded&from=2026-08-01&to=2026-08-15&limit=20" \
  -H "Authorization: Bearer $TOKEN" | jq 'length'
bashпоиск по префиксу ID
curl -s "$API/api/v1/orders?id=7c9e" \
  -H "Authorization: Bearer $TOKEN" | jq '.[].order_id'

8 Карточка заказа #

GET /api/v1/orders/{id} — публичная карточка заказа: статус, суммы, зафиксированный курс, QR/ссылка на оплату. Чужой заказ → 403. Данные платёжного провайдера (его идентификаторы, статусы, логи) клиенту не отдаются.

bashкарточка заказа
curl -s "$API/api/v1/orders/$ORDER_ID" \
  -H "Authorization: Bearer $TOKEN" | jq

9 Действия по заказу #

POST /api/v1/orders/{id}/{action} — только свои заказы. Каждое действие, меняющее статус, порождает вебхук order.status_changed.

ActionТелоОписаниеОтвет
confirmПодтвердить операцию (после готовности СБП)200, статус из СБП
cancelОтменить неоплаченный заказ200, canceled
refund{"amount": <RUB копейки>, "reason": "…"} или {}Возврат: полный или частичный (не больше списанного), с необязательной причиной. При модерации возвратов — заявка в пул200 или 202 pending_review
demo-confirmDemo: «оплатить» заказ в тестовом режиме (может отвечать 503 — просто повторите)200, succeeded
demo-cancelDemo: отменить заказ в тестовом режиме200, canceled
bashподтверждение оплаты (demo)
curl -s -X POST "$API/api/v1/orders/$ORDER_ID/confirm" \
  -H "Authorization: Bearer $TOKEN"
bashотмена неоплаченного заказа
curl -s -X POST "$API/api/v1/orders/$ORDER_ID/cancel" \
  -H "Authorization: Bearer $TOKEN"
bashполный возврат
curl -s -X POST "$API/api/v1/orders/$ORDER_ID/refund" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "покупатель отказался от заказа"}'
bashчастичный возврат на 50,00 ₽
curl -s -X POST "$API/api/v1/orders/$ORDER_ID/refund" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount": 5000, "reason": "частичная компенсация"}'

10 Вебхуки клиенту #

Если вашему клиенту задан webhook_url, при каждой реальной смене статуса заказа сервис присылает POST. Триггеры: создание заказа, вебхуки из СБП, ваши действия confirm/cancel/refund/demo-*, синхронизация статуса.

flowchart LR
    subgraph Триггеры
        A1[создание заказа]
        A2[вебхук из СБП]
        A3[confirm / cancel / refund]
        A4[sync статуса]
    end
    B{"статус реально
изменился?"} C["POST webhook_url
order.status_changed"] D["ответ 2xx → done"] E["таймаут/5xx/429 →
до 3 попыток с бэкоффом"] F["прочие 4xx →
без ретрая"] A1 --> B A2 --> B A3 --> B A4 --> B B -- "да" --> C B -- "нет (дубль)" --> X[ничего не отправляется] C --> D C --> E C --> F style C fill:var(--accent-soft),stroke:var(--link) style X fill:var(--th-bg),stroke:var(--muted)

Тело вебхука:

jsonorder.status_changed
{
  "type": "order.status_changed",
  "order": {
    "order_id": "…", "status": "processing",
    "amount": 100, "currency": "usd",
    "rub_amount": 8746, "rate_used": 87.46,
    "return_url": "…",
    "created_at": "…", "updated_at": "…"
  },
  "payment": {                       // пока оплата актуальна
    "sbp_deeplink": "https://qr.nspk.ru/…",
    "qr_image_base64": "…",
    "redirect_url": ""
  },
  "timestamp": "…"
}

Заголовки: X-Webhook-Event: order.status_changed и X-Webhook-Signature — подпись Base64(RSA-SHA256(body)). Проверяйте её публичным ключом, который вам выдали. Отвечайте 2xx; при таймауте/5xx/429 доставка повторится до 3 раз с нарастающей паузой, затем продолжится фоново каждые 5 минут (не более 20 доставок на заказ в сутки); прочие 4xx считаются окончательным отказом и не ретраятся.

Проверка подписи вручную (тело — байт-в-байт как получено):

bashverify signature (openssl)
# body.bin — тело вебхука, sig.b64 — значение X-Webhook-Signature
base64 -d sig.b64 > sig.bin
openssl dgst -sha256 -verify public.pem -signature sig.bin body.bin \
  && echo "подпись верна"
Дублей не бывает: вебхук уходит только при реальном изменении статуса. Каждая попытка доставки фиксируется сервисом — историю можно запросить у оператора.

11 Возвраты #

Два сценария в зависимости от настройки вашего клиента (refund_auto):

flowchart TD
    A["POST /orders/{id}/refund
{ amount: RUB копейки } или {} — полный"] --> B{"refund_auto
у клиента?"} B -- "true" --> C["сразу отправляется в СБП"] C --> D["200 OK + вебхук
refunded / partially_refunded"] B -- "false" --> E["заявка в пул на модерацию"] E --> F["202 { status: pending_review }"] F --> G{"решение оператора"} G -- "одобрено" --> H["уходит в СБП → вебхук refunded / partially_refunded"] G -- "отклонено" --> I["заявка rejected, статус заказа не меняется"] style D fill:var(--ok-bg),stroke:var(--ok-text) style H fill:var(--ok-bg),stroke:var(--ok-text) style F fill:var(--warn-bg),stroke:var(--warn-text) style I fill:var(--th-bg),stroke:var(--muted)
  • Сумма возврата — рубли (копейки); пустое тело = полный возврат списанного.
  • Частичных возвратов может быть несколько, суммарно не больше rub_amount заказа.
  • Поле reason (необязательно, до 500 символов) — причина, видна оператору в пуле заявок.
  • Поле currency в запросе игнорируется — возврат всегда в рублях.

12 Деньги и курс #

  • Все суммы — целые, в минорных единицах: USD центы (10000 = $100.00), RUB копейки (8476000 = 84 760,00 ₽).
  • rub_amount = round(amount/100 × курс × (1 + markup%) × 100) — считает сервис.
  • Курс фиксируется в заказе на момент создания (rate_used) и больше не меняется.
  • Курс USD/RUB собирается каждые несколько секунд; если свежего курса нет — создание заказа отвечает 503.
  • Возвраты — в рублях (копейки); без поля amount — полный возврат списанного.

13 Формат ошибок #

jsonтело ошибки
{ "error": "amount must be positive" }
HTTPКогда
400Невалидный запрос, лимиты клиента, чужой idempotency_key
401Нет/неверный токен, клиент на паузе
403Заказ принадлежит другому клиенту
404Заказ не найден
503Нет свежего курса; создание заказов приостановлено
502СБП недоступна

14 Идемпотентность #

Передайте idempotency_key — повторный запрос с тем же ключом вернёт тот же заказ без повторного списания. Ключ уникален глобально и привязан к вашему клиенту: совпадение с чужим ключом даст 400 idempotency_key is already used, а не чужой заказ.

bashповтор с тем же ключом — тот же заказ
for i in 1 2; do
  curl -s -X POST "$API/api/v1/orders" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"amount":100,"return_url":"https://example.com/ok","idempotency_key":"dup-1"}' \
    | jq -r .order_id
done
# оба раза напечатает один и тот же order_id

Документация: версия для LLM (llms.txt) · обновляется вместе с API.