Payments API

Приём платежей через QR и перевод на карту

Вы создаёте платёж одним запросом, отправляете покупателя на нашу форму оплаты и получаете вебхук, когда деньги пришли.

QRПокупатель платит по QR или ссылке. Оплата подтверждается автоматически.
Карта УзбекистанаПокупатель переводит на карту и прикладывает чек. Оператор подтверждает вручную.
ВалютаRUB. Суммы — строкой или числом, до 2 знаков после точки.

Подключение за 5 шагов

  1. Получите API-ключ и webhook secretИх выдаёт менеджер. Ключ выглядит как pk_live_…, secret — как whsec_…. Сообщите менеджеру адрес, на который присылать вебхуки.
  2. Проверьте доступные методыGET /api/v1/methods/ покажет, какие способы оплаты включены для вашего ключа и в каких пределах суммы.
  3. Создайте платёжPOST /api/v1/payments/ с суммой, методом и вашим номером заказа. Сохраните id из ответа рядом с заказом.
  4. Отправьте покупателя на формуПеренаправьте его по payment_url. На форме он увидит QR или реквизиты карты и загрузит чек.
  5. Примите вебхукКогда статус станет success, на ваш адрес придёт payment.success. Проверьте подпись и выдайте товар.

Авторизация

Базовый адрес API:

https://m-pay-provider.com/api/v1

Передавайте ключ в каждом запросе в заголовке Authorization. Запросы принимаются только по HTTPS.

HTTP
Authorization: Bearer pk_live_9f3c…

Если ключ не передан, неверный или отключён, API отвечает 401. Ключ даёт полный доступ к платежам вашего аккаунта: храните его на сервере и не вставляйте в код сайта или мобильного приложения.

Доступные методы

GET/api/v1/methods/

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

Ответ 200
[
  {
    "method": "qr",
    "qr_method_id": "12",
    "name": "СБП QR",
    "description": "Оплата по QR в приложении банка",
    "min_amount": "1000.00",
    "max_amount": "200000.00"
  },
  {
    "method": "card",
    "qr_method_id": null,
    "name": "Перевод на карту Узбекистана",
    "description": null,
    "min_amount": "1000.00",
    "max_amount": "100000.00"
  }
]

max_amount: null означает, что верхнего предела нет. Пустой список — сейчас ни один метод недоступен.

Создать платёж

POST/api/v1/payments/
ПолеТипОписание
methodобязательноstringqr или card
amountобязательноstring / numberСумма в RUB, от 1, до 2 знаков после точки: 5000, "1500.50"
order_idstringВаш номер заказа, до 128 символов. Придёт обратно в ответах и вебхуках. Уникальность мы не проверяем
customer_idstringID покупателя в вашей системе, до 128 символов. По нему работают лимиты на покупателя; для некоторых аккаунтов поле обязательно
descriptionstringДо 500 символов. Показывается покупателю на форме оплаты
qr_method_idstringТолько для qr: ID метода из списка методов. Не передан — подберём подходящий по сумме
success_urlURLКуда вернуть покупателя после оплаты
fail_urlURLКуда вернуть покупателя, если оплата не прошла
curl -X POST https://m-pay-provider.com/api/v1/payments/ \
  -H "Authorization: Bearer pk_live_9f3c…" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "card",
    "amount": 5000,
    "order_id": "A-1042",
    "customer_id": "user-17",
    "success_url": "https://shop.example/thanks",
    "fail_url": "https://shop.example/fail"
  }'

Ответ 201

JSON
{
  "id": "83c00c41-ca6c-4d34-bb53-77de721d0b3d",
  "status": "pending",
  "method": "card",
  "amount": "5000.00",
  "paid_amount": null,
  "currency": "RUB",
  "order_id": "A-1042",
  "customer_id": "user-17",
  "description": null,
  "payment_url": "https://m-pay-provider.com/pay/83c00c41-ca6c-4d34-bb53-77de721d0b3d/",
  "comment": null,
  "created_at": "2026-10-18T13:40:02+03:00",
  "expires_at": "2026-10-18T14:10:02+03:00",
  "paid_at": null,
  "card": {
    "number": "8600 1234 5678 9012",
    "holder_name": "ALIYEV ALISHER",
    "phone": "+998 90 111 22 33",
    "bank": "Kapitalbank",
    "instructions": "Откройте приложение банка → Переводы за границу…"
  },
  "receipts_count": 0
}
ПолеОписание
idID платежа. Используйте его во всех остальных запросах и для сверки с вебхуками
statusТекущий статус, см. Статусы
payment_urlНаша форма оплаты для покупателя
paid_amountЗачисленная сумма. Заполняется при success
expires_atДо какого момента ждём оплату
commentПричина при отказе, отклонении или отмене
cardРеквизиты для перевода, только у method: card. У QR — null
receipts_countСколько чеков загружено по платежу

Если запрос оборвался по таймауту, не создавайте платёж повторно вслепую. Сначала найдите его по номеру заказа: GET /api/v1/payments/?order_id=A-1042.

Форма оплаты

По payment_url открывается готовая страница, адаптированная под телефон. Ничего встраивать не нужно — достаточно перенаправить покупателя.

МетодЧто видит покупатель
qrКнопку «Перейти к оплате» на страницу банка или QR-код. Через несколько секунд после создания платежа, пока поставщик готовит ссылку, — индикатор ожидания. По желанию можно приложить чек.
cardНомер карты, получателя, банк, телефон и сумму с кнопками «Копировать», инструкцию по переводу, таймер и обязательную загрузку чека.

Страница сама обновляет статус. После успешной оплаты покупатель через 3 секунды возвращается на success_url. При отказе на странице появляется кнопка возврата на fail_url.

Возврат на success_url — это только удобство для покупателя, а не подтверждение оплаты. Выдавайте товар по вебхуку payment.success или после проверки статуса через API.

Оплата на карту

Покупатель переводит деньги на карту узбекского банка и обязательно прикладывает чек — скриншот или PDF. Оператор сверяет поступление и подтверждает платёж, обычно за несколько минут.

  1. Платёж созданКарта выдана, статус pending. На перевод и загрузку чека даётся время до expires_at.
  2. Чек загруженСтатус review, приходит вебхук payment.review.
  3. Оператор проверилsuccess, если деньги пришли, или rejected, если перевода нет. Причина отклонения — в comment.

Если чек не загрузили вовремя, платёж переходит в expired. Покупатель, который всё же успел перевести, может загрузить чек и после этого — платёж снова уйдёт на проверку.

Хотите показывать реквизиты в своём интерфейсе — берите их из поля card, а чек отправляйте через загрузку чека.

Статусы

СтатусЧто значитФинальный
createdПлатёж создан, поставщик QR готовит реквизиты. Обычно длится несколько секунднет
pendingЖдём оплату от покупателянет
reviewТолько карта: чек загружен, оператор проверяет поступлениенет
successОплачено. Можно выдавать товарда
failedОплата не прошла или поставщик не смог создать платёж. Причина в commentда*
rejectedТолько карта: оператор не нашёл переводда
expiredВремя на оплату истеклонет*
cancelledПлатёж отменёнда*

* Банк может подтвердить оплату с опозданием. Поэтому QR-платёж из expired, failed или cancelled иногда переходит в success, а платёж по карте из expired — в review. Не удаляйте такие заказы сразу: держите их хотя бы сутки и обрабатывайте вебхук payment.success в любой момент.

Получить платёж

GET/api/v1/payments/{id}/

Возвращает платёж в том же формате, что и при создании. Удобно для проверки статуса, если вебхук не пришёл. Опрашивайте не чаще раза в 5–10 секунд. Чужой или несуществующий платёж — 404.

Список платежей

GET/api/v1/payments/?status=pending&limit=50
ПараметрОписание
statusФильтр по статусу
order_idНайти платежи по вашему номеру заказа
customer_idПлатежи одного покупателя
limitСколько вернуть, по умолчанию 50, максимум 200
offsetСколько пропустить — для постраничного вывода

Платежи отсортированы от новых к старым. Ответ — массив объектов платежа.

Загрузить чек

POST/api/v1/payments/{id}/receipt/

Нужно, только если чек принимаете вы, а не наша форма. Формат запроса — multipart/form-data, поле file. Подходят JPG, PNG, WebP и PDF до 10 МБ, не больше 5 чеков на платёж.

curl -X POST https://m-pay-provider.com/api/v1/payments/83c00c41-…/receipt/ \
  -H "Authorization: Bearer pk_live_9f3c…" \
  -F "file=@check.jpg"

В ответ 201 приходит обновлённый платёж. Для card статус станет review. Для qr статус не меняется: чек передаётся поставщику, чтобы ускорить проверку, а итог придёт вебхуком.

Отменить платёж

POST/api/v1/payments/{id}/cancel/

Отменяет платёж по карте, пока чек не загружен — статус pending. Покупатель больше не сможет его оплатить. QR-платёж отменить нельзя: дождитесь итогового статуса от поставщика. В остальных случаях — 409 cannot_cancel.

Вебхуки

При каждой смене статуса мы отправляем POST с JSON на ваш адрес. Событие называется payment. + новый статус.

СобытиеКогда приходит
payment.successОплата подтверждена — выдавайте товар
payment.reviewПокупатель загрузил чек по карте
payment.rejectedОператор отклонил чек
payment.failedОплата не прошла
payment.expiredИстекло время на оплату
payment.cancelledПлатёж отменён
payment.pendingПоставщик QR выдал реквизиты (приходит не всегда)

Тело запроса

JSON
{
  "id": "4821",
  "event": "payment.success",
  "created_at": "2026-10-18T13:52:10+03:00",
  "data": {
    "id": "83c00c41-ca6c-4d34-bb53-77de721d0b3d",
    "status": "success",
    "method": "card",
    "amount": "5000.00",
    "paid_amount": "5000.00",
    "currency": "RUB",
    "order_id": "A-1042",
    "customer_id": "user-17",
    "paid_at": "2026-10-18T13:52:10+03:00"
  }
}

В data — объект платежа целиком, как в ответе API, но без реквизитов карты.

Заголовки

ЗаголовокЗначение
X-Webhook-IdID доставки, совпадает с id в теле. Одинаковый при повторах
X-EventИмя события
X-TimestampВремя отправки, unix-секунды
X-Signaturesha256= + HMAC-SHA256, см. ниже
User-Agentpayments-webhook/1

Ответ и повторы

Ответьте любым кодом 2xx в течение 10 секунд — тогда вебхук считается доставленным. Тяжёлую обработку делайте после ответа. Если ответ другой или его нет, повторим до 8 раз с нарастающей паузой: 2, 4, 8, 16, 32, 64 и 128 минут.

Один и тот же вебхук может прийти дважды. Запоминайте X-Webhook-Id и не обрабатывайте повтор. Вебхуки могут прийти не по порядку — ориентируйтесь на data.status, а при сомнениях запросите платёж через API.

Проверка подписи

Ключ подписи — ваш webhook secret whsec_…. Подписывается строка X-Timestamp + "." + сырое тело запроса. Берите тело ровно в том виде, как оно пришло: после разбора и повторной сборки JSON байты изменятся, и подпись не сойдётся. Сравнивайте подписи безопасной функцией и отклоняйте запросы старше 5 минут.

import hashlib, hmac, json, time

SECRET = "whsec_…"

def handle_webhook(raw_body, headers):
    ts = headers.get("X-Timestamp", "")
    signature = headers.get("X-Signature", "")
    expected = "sha256=" + hmac.new(
        SECRET.encode(), ts.encode() + b"." + raw_body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return 401
    if abs(time.time() - int(ts)) > 300:
        return 401

    event = json.loads(raw_body)
    if already_processed(event["id"]):
        return 200
    if event["event"] == "payment.success":
        mark_order_paid(event["data"]["order_id"], event["data"]["paid_amount"])
    remember(event["id"])
    return 200

При перевыпуске webhook secret старый перестаёт действовать сразу — обновите его у себя в тот же момент.

Лимиты

Ограничения защищают от спама и неоплаченных заявок. Для вашего аккаунта их настраивает менеджер, отдельно для QR и для карт:

  • минимальная и максимальная сумма платежа;
  • сколько платежей можно создать в час и в сутки;
  • сколько неоплаченных платежей может быть одновременно;
  • те же ограничения на одного покупателя — по customer_id.

Если лимит превышен, API отвечает 429 с кодом, по которому видно, какой именно. Отдельно действует ограничение частоты запросов: 120 в минуту на ключ.

Передавайте customer_id всегда, даже если для вас он не обязателен. Тогда лимит сработает на одного недобросовестного покупателя, а не на весь ваш магазин.

Ошибки

Ошибка всегда приходит в одном формате. У ошибок валидации дополнительно есть fields с описанием по каждому полю.

429
{
  "error": "customer_pending_limit",
  "message": "У покупателя слишком много неоплаченных платежей"
}
HTTPerrorЧто делать
400validation_errorПоле заполнено неверно — подробности в fields
400invalid_amountСумма должна быть больше нуля
400amount_too_small, amount_too_large, amount_out_of_rangeСумма вне лимита — сверьтесь с методами
400customer_id_requiredДля вашего аккаунта нужно передавать customer_id
400qr_method_unavailableЭтот метод QR выключен — запросите список методов заново
400 / 502provider_errorПоставщик QR не принял платёж, причина в message. При 502 повторите позже или выберите другой метод
400bad_fileНужен JPG, PNG, WebP или PDF
401not_authenticated, authentication_failedКлюч не передан, неверный или отключён
403method_disabled, client_disabledМетод или аккаунт отключён — обратитесь к менеджеру
404not_foundПлатёж не найден или принадлежит другому аккаунту
409no_cardСейчас нет карты для этой суммы — предложите QR или повторите позже
409payment_closed, payment_not_ready, cannot_cancelДействие недоступно в текущем статусе платежа
413file_too_largeФайл больше 10 МБ
429hour_limit, day_limit, pending_limitПревышен лимит аккаунта или ключа
429customer_hour_limit, customer_day_limit, customer_pending_limitПревышен лимит на покупателя
429too_many_receiptsПо платежу уже загружено максимум чеков
429throttledСлишком частые запросы — подождите и повторите

Перед запуском

  • API-ключ хранится только на сервере.
  • Товар выдаётся по вебхуку payment.success с проверенной подписью, а не по возврату на success_url.
  • Повторный вебхук с тем же X-Webhook-Id не выдаёт товар второй раз.
  • Ваш адрес для вебхуков отвечает 2xx быстрее чем за 10 секунд.
  • В каждом платеже передаются order_id и customer_id.
  • Заказы в статусах expired и failed не удаляются сразу — оплата может подтвердиться позже.
  • Ошибки 409 no_card и 429 показывают покупателю понятное сообщение.

Попробовать запросы со своим ключом можно в Swagger. Вопросы по подключению — вашему менеджеру.