POST /sbp/quotesОкончательная цена до выпуска QR. amountUsdt показывает, сколько получит клиент (на карту cardId или на баланс компании) за вычетом всех комиссий и вашей наценки. rate — сколько рублей стоит 1 полученный USDT. Покажите это клиенту и создайте заказ по quoteId, пока котировка не истекла (expiresAt, около двух минут). Клиент должен быть верифицирован.
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
customerId | string | да | Ваш идентификатор клиента |
amountRub | string | да | Сколько рублей клиент отправит по QR: десятичная строка, до 2 знаков |
cardId | string | см. ниже | Карта компании, которую пополнить купленным |
partnerFeeBps | integer | нет | Ваша наценка в базисных пунктах от суммы заказа (100 = 1%), от 0 до 9999, по умолчанию 0. См. Ваша наценка |
purpose | string | нет | topup (по умолчанию) — покупка USDT. card_issue — оплата выпуска карты, см. Выпуск карты за рубли |
cardId обязателен, если покупки вашей компании зачисляются только на карту. Без него в режиме «на карту или на баланс» USDT лягут на баланс компании.
curl -X POST 'https://b2b.lumowallet.io/sbp/quotes' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"customerId": "cust-8841",
"amountRub": "10000",
"cardId": "3f1d2a4b-8c7e-4f61-9a0b-5d2e1c3b4a59"
}'{
"quoteId": "b7a3c1e2-9d4f-4a8b-8e6c-1f2d3a4b5c6d",
"customerId": "cust-8841",
"target": "card",
"cardId": "3f1d2a4b-8c7e-4f61-9a0b-5d2e1c3b4a59",
"amountRub": "10000.00",
"amountUsdt": "117.42",
"partnerFeeUsdt": "0.00000000",
"rate": "85.164367",
"expiresAt": "2026-10-24T10:17:02.000Z"
}При зачислении на карту amountUsdt — сумма пополнения карты с точностью до цента, комиссия пополнения уже вычтена. Если после округления остаётся меньше цента, этот остаток ложится на баланс компании. Сумма меньше минимального пополнения карты — ответ 400 SBP_AMOUNT_OUT_OF_RANGE ещё до выпуска QR.
Чтобы зарабатывать на покупках, передайте в котировке partnerFeeBps: свою наценку в базисных пунктах от суммы заказа (100 = 1%). Размер выбираете сами и можете менять от котировки к котировке. Нужна фиксированная сумма в рублях — пересчитайте её в bps от amountRub.
Наценка удерживается из купленного так же, как наша комиссия: amountUsdt и rate уже её учитывают, а сама она приходит отдельным полем partnerFeeUsdt. После оплаты мы зачисляем на баланс компании всю покупку, пополняем карту на amountUsdt, а partnerFeeUsdt остаётся у вас на балансе.
curl -X POST 'https://b2b.lumowallet.io/sbp/quotes' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"customerId": "cust-8841",
"amountRub": "10000",
"cardId": "3f1d2a4b-8c7e-4f61-9a0b-5d2e1c3b4a59",
"partnerFeeBps": 200
}'{
"quoteId": "c8b4d2f3-0e5a-4b9c-9f7d-2a3e4b5c6d7e",
"customerId": "cust-8841",
"target": "card",
"cardId": "3f1d2a4b-8c7e-4f61-9a0b-5d2e1c3b4a59",
"amountRub": "10000.00",
"amountUsdt": "111.61",
"partnerFeeUsdt": "2.37107291",
"rate": "89.597706",
"expiresAt": "2026-10-24T10:17:02.000Z"
}Здесь клиент заплатит 10 000 ₽, на карту ляжет 111,61 USD, а 2,37 USDT останутся на балансе компании. Если наценка не оставляет клиенту ничего или сумма пополнения выходит меньше минимальной, котировка ответит 400 SBP_AMOUNT_OUT_OF_RANGE.
POST /sbp/ordersВыпускает QR СБП по котировке. Сумма и курс заказа берутся ровно из неё. Нужен заголовок Idempotency-Key.
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
quoteId | string | да | quoteId из POST /sbp/quotes |
externalId | string | нет | Ваш идентификатор заказа |
curl -X POST 'https://b2b.lumowallet.io/sbp/orders' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Idempotency-Key: 2c9e7f1a-5b3d-4e8a-9c6f-0a1b2c3d4e5f' \
-H 'Content-Type: application/json' \
-d '{
"quoteId": "b7a3c1e2-9d4f-4a8b-8e6c-1f2d3a4b5c6d",
"externalId": "buy-5531"
}'{
"id": "e4d2c1b0-7a6f-4e5d-8c3b-2a1f0e9d8c7b",
"externalId": "buy-5531",
"customerId": "cust-8841",
"quoteId": "b7a3c1e2-9d4f-4a8b-8e6c-1f2d3a4b5c6d",
"status": "awaiting_payment",
"target": "card",
"cardId": "3f1d2a4b-8c7e-4f61-9a0b-5d2e1c3b4a59",
"amountRub": "10000.00",
"amountUsdt": "117.42",
"partnerFeeUsdt": "0.00000000",
"rate": "85.164367",
"cardRechargeId": null,
"cardRechargeStatus": null,
"qrUrl": "https://qr.nspk.ru/…",
"qrPngBase64": "iVBORw0KGgoAAAANSUhEUgAA…",
"payerPhoneMasked": "+7 999 ***-**-67",
"failureReason": null,
"expiresAt": "2026-10-24T11:15:10.000Z",
"completedAt": null,
"createdAt": "2026-10-24T10:15:10.402Z"
}Как показать QR клиенту:
- на телефоне отдайте
qrUrlоперационной системе: она предложит открыть приложение банка; - на компьютере покажите картинку из
qrPngBase64, клиент отсканирует её телефоном.
payerPhoneMasked — номер, с которого банк ждёт оплату. Покажите его клиенту рядом с QR. QR живёт до expiresAt, около часа. У клиента может быть только один активный заказ.
| Код | Что это значит |
|---|---|
201 | Заказ создан, QR в ответе |
200 | Повтор с тем же Idempotency-Key: тот же заказ. Картинки QR в ответе нет, берите её из GET /sbp/orders/{id} |
202 | status: creating, банк ещё не подтвердил выпуск QR |
202 creating: не создавайте новый заказ. QR мог выпуститься, мы уточним это сами. Перечитайте заказ через несколько секунд (GET /sbp/orders/{id}): он станет awaiting_payment или failed. Повтор с тем же ключом безопасен. С новым ключом по той же котировке получите 409 SBP_QUOTE_USED.
Тот же ключ с другим quoteId — 409 IDEMPOTENCY_KEY_REUSED.
status | Что происходит |
|---|---|
creating | Выпускаем QR у банка |
awaiting_payment | QR выпущен, ждём оплату |
completed | Оплачено, USDT зачислены |
expired | QR истёк без оплаты |
cancelled | Заказ отменён |
failed | Не удалось выпустить QR или провести оплату, причина в failureReason |
Каждая смена статуса приходит вебхуком sbp.order.status_changed. Если оплата всё-таки придёт после expired или failed, заказ станет completed и USDT будут зачислены.
При target: card после оплаты мы сначала зачисляем USDT на баланс компании, а затем пополняем карту ровно на amountUsdt. Ваша наценка partnerFeeUsdt на карту не идёт и остаётся на балансе. Ход пополнения виден в cardRechargeStatus, а cardRechargeId связывает заказ с событиями card.recharge.* (см. Webhooks по картам).
cardRechargeStatus | Что это значит |
|---|---|
null | Пополнение ещё не запущено. При сбое связи мы повторим его сами |
pending, succeeded, in_progress_manual, refunded, failed | Статус пополнения на момент запуска, как в Пополнении карты. Дальше следите за card.recharge.* |
Если пополнение отклонено (cardRechargeStatus: failed), купленные USDT остаются на балансе компании. Пополнить карту можно обычным POST /cards/recharge.
GET /sbp/orders/{id}
GET /sbp/orders?customerId=cust-8841&status=awaiting_payment,completed&limit=50&offset=0Пока заказ в статусе awaiting_payment, ответ GET /sbp/orders/{id} содержит qrUrl, qrPngBase64 и payerPhoneMasked. Список возвращает { "items": [...], "total": 12 }, новые заказы идут первыми, картинки QR в нём нет.
POST /sbp/orders/{id}/cancelОтменяет неоплаченный заказ. Отменяйте брошенные заказы сразу: каждый неоплаченный QR приближает блокировку клиента в банке. Если заказ уже оплачен или завершён, ответ — 409 SBP_ORDER_NOT_CANCELABLE.
errorCode | HTTP | Что произошло |
|---|---|---|
SBP_BUY_NOT_ENABLED | 403 | Покупка не подключена вашей компании |
SBP_CUSTOMER_NOT_FOUND | 404 | Клиент не зарегистрирован, сначала POST /sbp/customers |
SBP_CUSTOMER_INVALID | 400 | Банку нужен российский мобильный номер плательщика: обновите phone через POST /sbp/customers |
SBP_CUSTOMER_NOT_VERIFIED | 412 | Клиент не верифицирован, статус в details |
SBP_CARD_REQUIRED | 400 | Покупки компании идут только на карту, нужен cardId |
SBP_AMOUNT_OUT_OF_RANGE | 400 | Сумма вне границ GET /sbp/rate, меньше минимального пополнения карты или наценка не оставляет клиенту ничего |
QUOTE_NOT_FOUND / QUOTE_EXPIRED | 404 / 400 | Котировки нет или она истекла, запросите новую |
SBP_QUOTE_USED | 409 | По котировке уже создан заказ |
IDEMPOTENCY_KEY_REQUIRED | 400 | Нет заголовка Idempotency-Key |
IDEMPOTENCY_KEY_REUSED | 409 | Ключ уже использован для другой котировки |
SBP_ACTIVE_ORDER_EXISTS | 409 | У клиента уже есть неоплаченный заказ |
SBP_LIMIT_EXCEEDED | 429 | Превышен лимит банка на клиента, причина в details.providerCode |
SBP_ORDER_NOT_FOUND | 404 | Заказа нет |
SBP_ORDER_NOT_CANCELABLE | 409 | Заказ уже не отменить |
SBP_PROVIDER_REJECTED | 422 | Банк отклонил запрос, код в details.providerCode |
SBP_PROVIDER_UNAVAILABLE / SBP_BUY_UNAVAILABLE | 503 | Сервис временно недоступен, повторите позже |
SBP_KYC_UNAVAILABLE | 503 | Верификация временно недоступна |
У ошибок от банка в details есть providerCode, а иногда ещё и providerDetails:
{
"errorCode": "SBP_LIMIT_EXCEEDED",
"message": "…",
"details": {
"providerCode": "sbp_buy_fiat_daily_limit",
"providerDetails": {
"code": "ProviderDailyQrlimitReached",
"limit": "100000",
"remaining": "0",
"resets_at": "2026-10-25T09:00:00Z"
}
}
}providerDetails.codeприходит, когда банк однозначно отказал этому клиенту. Покажите по нему понятный клиенту текст.limit,remainingиresets_atприходят у лимитов в рублях: сколько клиенту доступно и когда лимит обновится.
Значения providerCode, которые стоит обработать отдельно:
providerCode | Что сказать клиенту |
|---|---|
sbp_buy_daily_qr_limit | На сегодня лимит покупок исчерпан, попробуйте завтра |
sbp_buy_daily_paid_qr_limit | Лимит оплаченных покупок на сегодня, попробуйте через час |
sbp_buy_pending_qr_limit | Слишком много неоплаченных QR. Ожидание не поможет, нужна поддержка |
sbp_buy_fiat_daily_limit / sbp_buy_fiat_monthly_limit | Превышен лимит суммы за сутки или за месяц, см. resets_at |
sbp_buy_amount_unavailable | Банк не может принять эту сумму, другая может пройти |
sbp_buy_blocked | Покупка для клиента недоступна, нужна поддержка |
sbp_buy_bank_not_found / sbp_buy_bank_unsupported | Банк клиента не поддерживается |