Solvr API для мерчанта
    • Как мерчанту подключить платежи
    • Вебхуки для мерчанта
    • API
      • Платежи
        • Создать платёж
        • Список платежей мерчанта
        • Один платёж
        • Отменить платёж
        • История статусов
      • Страница оплаты
        • Страница оплаты по id платежа
        • Страница оплаты по токену
      • Чеки
        • Список чеков и файлов по платежу
        • Приложить чек по ссылке
        • Загрузить файл подтверждения
      • Апелляции
        • Открыть спор по платежу
        • Список споров мерчанта
        • Один спор
      • Баланс
        • Баланс мерчанта
      • Schemas
        • PaymentMethod
        • PaymentType
        • PaymentStatus
        • CreatePayment
        • Payment
        • FormPage
        • StatusHistory
        • Receipt
        • Appeal
        • Balance
        • Error

    Как мерчанту подключить платежи

    Базовый адрес API: https://api-app.solvr.online.
    Ниже только то, что делает мерчант. Кабинет администратора, трейдеры и внутренние сервисы сюда не входят.

    1. Что вам выдают до первой оплаты#

    Администратор платформы создаёт мерчанта и передаёт два секрета.
    1.
    Ключ API. Он начинается с sk_. Им подписываются ваши запросы. Ключ показывают один раз. Если он утёк, старый отзывают и выпускают новый.
    2.
    Секрет вебхука. Им вы проверяете, что уведомление прислала платформа, а не посторонний. Секрет тоже показывают один раз. Его можно сменить, после смены старые подписи перестанут подходить.
    Ключ и секрет вебхука это разные строки. Ключ кладётся в заголовок Authorization. Секрет вебхука наружу в запросах не отправляется, он живёт только у вас и у платформы.
    Вам также называют код мерчанта. В ответах API он приходит как merchantId. Сами вы его в запросы не подставляете: платформа узнаёт мерчанта по ключу.

    2. Как устроен каждый запрос#

    Заголовок:
    Для загрузки файла заголовок Content-Type другой, это написано в шаге про чек.
    Ответ с ошибкой всегда выглядит так:
    {
      "code": "VALIDATION_ERROR",
      "message": "validation failed",
      "errors": {
        "amount": ["must be a positive decimal"]
      }
    }
    Поле errors есть только когда не прошли конкретные поля.
    Суммы пишите строкой: "1500", "1500.50". Не числом 1500. Дробная часть через точку.
    Валюта из трёх букв. Платформа сама сделает её заглавной, rub и RUB это одно и то же.

    3. Два вида платежа#

    type бывает только два.
    in это приём. Клиент платит вам. Вы показываете ему реквизиты или страницу оплаты. Когда деньги дошли, вам приходит вебхук со статусом оплаты.
    out это выплата. Вы отправляете деньги клиенту. Перед этим платформа держит нужную сумму USDT на балансе мерчанта. Если USDT не хватает, выплата не начнётся или баланс уйдёт в минус (зависит от настроек).

    4. Приём денег, шаг за шагом#

    Шаг 1. Генерация merchantTransactionId#

    merchantTransactionId это любая уникальная строка, по которой вам будет удобно идентифицировать платёж. Один и тот же номер можно отправить в API только как один платёж. Повтор не создаст второй.

    Шаг 2. Создание платежа#

    {
      "type": "in",
      "amount": "1500",
      "currency": "RUB",
      "method": "sbp",
      "geo": "RU",
      "merchantTransactionId": "order-1001",
      "clientId": "user-42",
      "webhookUrl": "https://shop.example/hooks/solvr",
      "returnUrl": "https://shop.example/orders/1001",
      "payer": {
        "name": "Иван Иванов"
      }
    }
    Обязательные поля: type, amount, currency, method, geo, merchantTransactionId.
    method это способ оплаты. geo это регион этого способа. Для российских переводов geo равен RU. Список кодов в конце страницы.
    webhookUrl это адрес, куда платформа будет сообщать о смене статуса. Если поле не передать, используется глобальный адрес, который администратор задал при онбординге. Переданный webhookUrl имеет приоритет над глобальным. Если нет ни того ни другого, уведомлений не будет, статус придётся забирать самим через GET /v1/payments/{id}.
    returnUrl это куда вернуть человека после страницы оплаты. Можно не передавать.
    clientId это ваш идентификатор покупателя. Можно не передавать.
    payer это любые данные о плательщике. Платформа сохранит их как есть и вернёт в ответе.
    amountUsd можно посчитать самим и передать. Если не передать, платформа посчитает сумму в долларах и курс сама. Курс для мерчанта потом виден в поле merchantRate. В вебхуке курса нет, он есть только в ответе API.
    ttlSec это сколько секунд живёт платёж. Если не передать, срок берётся из настроек.
    autoAmountLimit и autoAmountStep нужны редко. Если на точную сумму нет свободного реквизита, платформа может попробовать соседние суммы. Не передавайте эти поля, если вам не сказали иное.

    Шаг 3. Ответ с кодом 201#

    Успех это код 201 и JSON платежа. Важные поля:
    id сохраните. Дальше все запросы идут по нему.
    status в начале обычно process. Это значит, что платёж ещё не оплачен.
    requisiteDetails это то, что нужно показать клиенту.
    form.url это путь страницы оплаты, например /v1/form/токен. Полный адрес: https://api-app.solvr.online плюс этот путь.
    form.token нужен, если страницу запрашиваете отдельно.
    expiresAt это момент, после которого неоплаченный платёж станет expired.
    merchantRate это курс мерчанта на момент создания.
    Пример реквизитов для СБП:
    {
      "phone_number": "79991234567",
      "owner_name": "Петр Петров",
      "bank_name": "Т-Банк",
      "country_name": "РФ"
    }
    Пример для карты:
    {
      "card_number": "2200123412341234",
      "owner_name": "Петр Петров",
      "bank_name": "Сбербанк"
    }
    Пример для QR:
    {
      "qr_data": "https://qr.example/pay/abc",
      "qr_image": "https://cdn.example/qr/abc.png",
      "owner_name": "Петр Петров",
      "bank_name": "Т-Банк"
    }
    Набор полей зависит от способа. Показывайте клиенту то, что пришло, не ждите всегда одни и те же ключи.
    Если requisiteDetails пустой и статус process, реквизит ещё не подобран. Повторите создание с тем же merchantTransactionId или запросите GET /v1/payments/{id} чуть позже.

    Шаг 4. Страница оплаты#

    Два рабочих варианта.
    Вариант А. Своя страница. Вы сами рисуете сумму, банк, телефон или карту из requisiteDetails и таймер до expiresAt.
    Вариант Б. Страница платформы. Отправьте клиента на https://api-app.solvr.online + form.url. Эта страница открывается без ключа API. Когда время выйдет или человек закончит, его можно вернуть на ваш returnUrl.
    Проверить страницу по токену:
    Ключ не нужен. Если токен неизвестен или истёк, ответ 404.
    Посмотреть страницу по id платежа, уже с ключом:

    Шаг 5. Оплата клиентом#

    На каждое изменение статуса платежа приходит вебхук.
    Запасной путь, если вебхук не дошёл:
    Смотрите status и paidAmount.
    Что означают статусы приёма:
    СтатусЧто сделать у себя
    processЖдать. Клиент ещё платит или деньги ещё не зачислены.
    confirmation_requestedПлощадка просит файл. Загрузите чек. В ответе API этот статус может прийти строкой 9. Это то же самое.
    paidДеньги пришли ровно на сумму заказа. Заказ можно закрывать как оплаченный.
    underpaidПришло меньше, чем просили. Смотрите paidAmount. Сами решите, добирать разницу или нет.
    overpaidПришло больше. Смотрите paidAmount.
    expiredВремя вышло, оплаты нет. Заказ не оплачен.
    cancelПлатёж отменён.
    errorПровести не удалось. Смотрите причину в истории или в вебхуке.
    chargebackУже после успеха оплату оспорили. Заказ больше нельзя считать успешно оплаченным.
    Заказ у себя отмечайте оплаченным только на paid. underpaid и overpaid это не полная оплата нужной суммы.

    Шаг 6. Запрос подтверждения#

    Вебхук придёт со статусом confirmation_requested и причиной confirmation_requested. В нём будут поля confirmationKind и иногда confirmationNote.
    confirmationKind говорит, что прислать:
    receipt чек или скрин
    video видео оплаты
    other другой файл, смотрите текст в confirmationNote
    Загрузите файл:
    Поля формы:
    file сам файл, обязателен
    kind одно из receipt, video, other
    note короткая подпись, можно пусто
    Если файл уже лежит по ссылке, можно не загружать байты:
    {
      "fileUrl": "https://shop.example/receipts/1001.jpg",
      "note": "Чек клиента"
    }
    Список уже приложенных файлов:
    После проверки статус снова сменится, и придёт новый вебхук.

    5. Выплата клиенту#

    Шаг 1. Проверка баланса#

    Ответ:
    {
      "currency": "USDT",
      "balance": "1000",
      "held": "16.04",
      "available": "983.96"
    }
    available это сколько ещё можно отправить новыми выплатами. В большинстве случаев баланс может уходить в минус (зависит от настроек и условий). Если счёта нет, все три числа будут "0", это не ошибка.
    Другая валюта учёта, если вам её назвали:

    Шаг 2. Создание выплаты#

    Тело такое же, как у приёма, но type равен out. В payer или в договорённых полях передайте реквизиты получателя так, как описано ниже. Способ и регион обязательны.
    {
      "type": "out",
      "amount": "1500",
      "currency": "RUB",
      "method": "sbp",
      "geo": "RU",
      "merchantTransactionId": "payout-1001",
      "webhookUrl": "https://shop.example/hooks/solvr",
      "payer": {
        "phone_number": "79991234567",
        "bank_name": "Т-Банк",
        "owner_name": "Иван Иванов"
      }
    }
    Платформа удерживает USDT на балансе мерчанта и только потом ищет, кто отправит рубли.
    Если USDT не хватает, выплата не начнётся или баланс уйдёт в минус (зависит от настроек).
    Если получателя некому отправить, ответ 503 и код NO_LIQUIDITY или NO_REQUISITES. Платёж при этом может уже существовать. Повторите тот же запрос с тем же merchantTransactionId: платформа попробует найти исполнителя снова.

    Шаг 3. Конечный статус#

    Для выплаты конечные хорошие статусы те же: paid, underpaid, overpaid. Пока идёт отправка, статус process. Отмена возможна только пока выплата ещё не ушла в конечный статус, см. раздел про отмену.
    Страницы оплаты у выплаты обычно нет. Поле form в ответе будет отсутствовать.

    6. Повтор того же номера заказа#

    merchantTransactionId это ключ идемпотентности. Он уникален для вашей платформы.
    Если сеть оборвалась и вы не уверены, создался ли платёж, отправьте тот же JSON ещё раз.
    Второй платёж не появится.
    Ответ снова 201, внутри тот же id.
    Если первый раз реквизит не нашёлся, повтор попробует подобрать его снова.
    Если платёж уже в ошибке, повтор вернёт его как есть и сам ошибку не исправит.
    Поэтому номер заказа нельзя переиспользовать для новой оплаты. Новый заказ это новый merchantTransactionId.

    7. Отмена#

    Отменить можно платёж в статусе process или confirmation_requested.
    Тело не нужно. Успех это 200 и платёж со статусом cancel.
    Если платёж уже оплачен, истёк или в ошибке, придёт 409 и код INVALID_STATE. Такой платёж отменой не вернуть. Для спора после оплаты используйте апелляцию.

    8. Как узнать, что было с платежом#

    Один платёж:
    Все платежи мерчанта:
    Фильтр status принимает: process, paid, underpaid, overpaid, expired, cancel, chargeback, error.
    История смен статуса:
    Каждая запись содержит fromStatus, toStatus, reason и createdAt. reason это короткая машинная фраза, та же, что в вебхуке.
    Чужой id даст 403 с кодом FORBIDDEN. Несуществующий даст 404 с кодом NOT_FOUND.

    9. Спор#

    Спор нужен, когда вы не согласны с итогом. Например клиент показывает чек, а статус так и остался process, или сумма зачислена не та.
    {
      "paymentId": "6f1c2a40-1b2e-4c3d-9a8b-0e7d6c5b4a39",
      "reason": "Клиент оплатил, статус остался process",
      "evidence": {
        "receiptUrl": "https://shop.example/receipts/1001.jpg"
      }
    }
    paymentId и reason обязательны. evidence это любой JSON: ссылки, текст, номера.
    Ответ 201. Статус нового спора process.
    Дальше решение принимает администратор платформы. Вы только читаете итог:
    Статусы спора:
    СтатусСмысл
    processСпор ещё смотрят.
    successСпор закрыт как оплата.
    canceledСпор отклонён.
    Спор не заменяет вебхук платежа. После решения статус самого платежа может измениться отдельно, и тогда придёт обычный вебхук платежа.

    10. Ошибки, которые стоит обработать#

    HTTPcodeЧто делать
    401UNAUTHORIZEDКлюча нет или он неверный. Проверьте заголовок Authorization: Bearer sk_....
    403MERCHANT_REQUIREDКлюч не привязан к мерчанту. Нужен другой ключ.
    403FORBIDDENЭтот платёж или спор не ваш.
    404NOT_FOUNDНет такого id или токена страницы.
    409IDEMPOTENCY_CONFLICTЗапись с таким ключом уже есть и конфликтует.
    409INVALID_STATEДействие не подходит к текущему статусу. Например отмена уже оплаченного.
    422VALIDATION_ERRORПоправьте поля из errors и отправьте снова.
    422INSUFFICIENT_FUNDSНа выплату не хватило USDT. Пополните баланс.
    503NO_LIQUIDITYСейчас некого назначить. Повторите тот же запрос позже.
    503NO_REQUISITESНет подходящих реквизитов. Повторите позже или смените метод.
    503SERVICE_UNAVAILABLEЧасть платформы недоступна. Повторите тот же запрос.
    500INTERNALСбой. Повторите тот же merchantTransactionId, не создавайте новый номер, пока не проверите GET.
    Повторять безопасно именно создание с тем же merchantTransactionId. Новый номер заводите только когда уверены, что старый платёж вам больше не нужен.

    11. Коды способов для России#

    geo для всех этих кодов: RU. Валюта приёма: RUB.
    Телефон и СБП: sbp, phone_transfer, tbank, sberbank, alfa, vtb, gazprombank, psb, sovcombank, raiffeisen, rshb, mts_bank, ozon_bank, yandex_bank, wb_bank, otkritie, pochta, rosbank, mkb, tochka, modulbank, akbars, uralsib, domrf, spb, sinara.
    Карта: c2c, card, card_transfer.
    QR: tbank_qr.
    Платформа примет и другой код, если он заведён у вас в настройках. Если по коду нет живых реквизитов, создание вернёт NO_REQUISITES или NO_LIQUIDITY.

    12. Минимальный порядок запуска#

    1.
    Получить sk_ и секрет вебхука.
    2.
    Поднять у себя POST на webhookUrl, который проверяет подпись и отвечает 200. Подробности в vebhuki.md.
    3.
    Создать тестовый приём на маленькую сумму.
    4.
    Убедиться, что в ответе есть id, requisiteDetails и form.url.
    5.
    Дождаться вебхука payment.created, затем вебхука со статусом paid или другим конечным.
    6.
    Сверить GET /v1/payments/{id} с тем, что пришло в вебхуке.
    7.
    Только после этого включать выплаты. Перед первой выплатой проверить GET /v1/balance.
    Modified at 2026-10-23 12:16:27
    Next
    Вебхуки для мерчанта
    Built with