Что такое MonoPay/плата от mono и как выглядит интеграция

Короче: вы создаете счет на своем сервере через API monobank, вы получаете invoiceId и pageUrl в ответ, вы отправляете пользователя заплатить и фиксируете результат в соответствии с событием, которое monobank отправляет на ваш webHookUrl. Сам monobank прямо рекомендует рассматривать вебхук как основной механизм получения статусов, а метод проверки статуса использовать только как "план Б" на случай рассинхронизации.

Основные точки подключения:

  1. Подключение эквайринга в офисе компании и получение токена (X-Token).

  2. Создать счет (/api/merchant/invoice/create) с суммой, описанием и redirectUrl и webHookUrl.

  3. Оплата на pageUrl (карта/Apple Pay/Google Pay/платеж в приложении — зависит от сценария).

  4. Вебхук от monobank об изменении статуса счета.

  5. Проверка подписи вебхук (ECDSA, заголовок X-Sign).

  6. Подтверждение в вашей системе: вы меняете статус заказа/подписки/аккаунта.

  7. Резервная проверка статуса (/api/merchant/invoice/status), если необходимо.

Получение регистрации и подключения: что делать перед написанием кода

Чтобы подключить эквайринг, вам необходимо быть зарегистрированным как ИП и иметь открытый единственный счет ИП в моно.

Далее происходит авторизация в веб-кабинете и заполнение информации о бизнесе (вид деятельности, ссылка на сайт/социальные сети), после чего вы выбираете и подключаете платежные инструменты.

Это важно: если эквайринг не подключен, у вас просто не будет токена для API (он «становится доступным после подключения эквайринга»).

Получение документации содержит четкие этапы получения токена:

  • войдите в веб-офис;
  • перейдите на вкладку нужного способа оплаты и нажмите «Настроить»;

  • Теперь вы можете создать токен.

Затем вы передаете этот токен в заголовке X-Token для запросов API.

Тестовая среда

монобанк предоставляет возможность работать в тестовой среде: для этого нужен тестовый токен, а данные "карты" могут быть любыми действительными (с действительным номером по алгоритму Luna) - финансовая авторизация будет не состоится, но интеграцию вы прогоните.

Архитектура интеграции: что хранить в базе данных

Чтобы контролировать интеграцию (а не разбираться потом, почему "деньги пришли, но заказ не оплачен"), советую сохранять как минимум: платежи / счета

  • id (внутренний)

  • order_id (или другой идентификатор компании)

  • invoice_id (из monobank)

  • сумма (в минимальных единицах, например копейках)

  • ccy (ISO 4217, по умолчанию 980)

  • статус (создано/обработка/успех/истекло/сбой и т. д.)

  • created_at, updated_at

  • modified_date (с monobank — критично для правильного порядка веб-перехватчиков)

  • raw_last_payload (необязательно, но полезно для отладки)

Почему важна modifiedDate?
monobank предупреждает, что веб-перехватчики не гарантируются по отдельности, и теоретически status=success может предшествовать status=processing. Следовательно, «правильная истина» — это событие с большей modifiedDate.

Также подсчитывается количество повторных попыток: серверная часть сбора данных делает до 3 попыток веб-перехватчика POST, пока не получит 200 ОК. Это означает: ваш обработчик веб-перехватчика должен быть идемпотентом (получение того же состояния не должно нарушать работу системы).

Счет/создание: ключевые параметры и пример запроса

Конечная точка

Официальный метод создания учетной записи:

POST https://api.monobank.ua/api/merchant/invoice/create

Заголовки

  • X-Token: <токен из кабинета>

  • необязательно: X-Cms / X-Cms-Version — если вы делаете модуль для CMS (полезно, если интеграция упакована в виде плагина).

Тело запроса: минимальные требования

Обычно используется минимальный минимум:

  • сумма — сумма в минимальных единицах (для гривны — копейки)

  • ccy по умолчанию равен 980

  • merchantPaymInfo — информация о «человеческом» платеже: ссылка, назначение, комментарий, корзина basketOrder (особенно актуально для электронной коммерции)

  • redirectUrl — куда вернуть клиента после завершения платежа (успех или ошибка)

  • webHookUrl — куда monobank будет отправлять статус при изменении (кроме expired)

  • срок действия — время жизни аккаунта в секундах (по умолчанию 24 часа)

  • PaymentTypeдебет или удержание (если требуется удержание с дальнейшей финализацией)

В ответ вы получаете:

  • invoiceId

  • pageUrl (ссылка на оплату)

Пример для PHP 8.x (cURL)

$token = getenv('MONO_X_TOKEN');
$payload = [
   'amount' => 19900,        // 199.00 UAH -> в копейках     'ccy'    => 980,     'merchantPaymInfo' => [         'reference'   => 'ORDER-100045',     // твой уникальный референс
        'destination' => 'Оплата заказа #100045',
        'comment'     => 'Спасибо за покупку',         'basketOrder' => [             [                 'name'  => 'Підписка PRO, 1 місяць',                 'qty'   => 1,                 'sum'   => 19900,                 'total' => 19900,                 'unit'  => 'шт.',                 'code'  => 'SUB-PRO-1M',             ],         ],     ],     'redirectUrl' => 'https://example.com/pay/return',     'webHookUrl'  => 'https://example.com/pay/mono/webhook',     'validity'    => 3600,     'paymentType' => 'debit', ]; $ch = curl_init('https://api.monobank.ua/api/merchant/invoice/create'); curl_setopt_array($ch, [     CURLOPT_POST           => true,     CURLOPT_RETURNTRANSFER => true,     CURLOPT_HTTPHEADER     => [         'Content-Type: application/json',         'X-Token: ' . $token,     ],     CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE), ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) {     throw new RuntimeException("Monobank create invoice failed: HTTP $httpCode; body=$response"); } $data = json_decode($response, true); $invoiceId = $data['invoiceId']; $pageUrl   = $data['pageUrl']; // 1) сохраняешь invoiceId в БД вместе с order_id
// 2) редиректиш клиента на $pageUrl header('Location: ' . $pageUrl, true, 302); exit;

Семантика полей и сама конечная точка соответствуют документации: сумма в минимальных единицах, redirectUrl, webHookUrl, а ответ содержит invoiceId и pageUrl.

Возврат пользователя: redirectUrl

После оплаты пользователь будет перенаправлен на ваш redirectUrl (GET). Страницы часто делаются здесь:

  • “Платеж прошел успешно, заказ обрабатывается”

  • "Ошибка платежа, попробуйте еще раз"

Но это принципиально важно: не обязательно подтверждать оплату только на основании факта редиректа.
Редирект — это клиентский UX, который можно прервать, заменить, запустить или пользователь может просто закрыть вкладка.

На стороне сервера необходимо обработать:

<ул>вебхук со статусом (с проверкой подписи) илирезервное копирование: запрос статуса счета.

Вебхук: прием событий и проверка подписи (ECDSA, X-Sign)

Как появляются вебхуки

monobank отправляет POST на webHookUrl при изменении статуса (кроме expired), тело запроса идентично ответу метода "Статус аккаунта", а в заголовках присутствует X-Sign — подпись тела вебхука по стандарту ECDSA.

Также важны две особенности:

  • до 3 попыток доставки до 200 ОК;
  • события могут происходить не в хронологическом порядке, сосредоточьтесь на modifiedDate.

Получение открытого ключа

В примере из документации указано, что публичный ключ для проверки необходимо получить через конечную точку https://api.monobank.ua/api/merchant/pubkey.

Практический пример проверки PHP

Ниже приведен пример, адаптированный к реальному обработчику веб-перехватчика. Логика:

  1. прочитать необработанное тело;

  2. возьмите X-Sign;

  3. подтвердить через openssl_verify;

  4. если ок - парсим JSON и обновляем статус в базе с учетом modifiedDate.

 function getMonoPubKeyPem(string $token): string
{
    $ch = curl_init('https://api.monobank.ua/api/merchant/pubkey');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,         CURLOPT_HTTPHEADER     => ['X-Token: ' . $token],     ]);     $resp = curl_exec($ch);     $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);     curl_close($ch);     if ($code !== 200) {         throw new RuntimeException("Cannot fetch monobank pubkey: HTTP $code; body=$resp");     }     // В документации примеры работают с base64-представлением PEM.     // Здесь допустим, что API возвращает base64-строку, которую нужно decode.     return base64_decode(trim($resp)); } $token = getenv('MONO_X_TOKEN'); $rawBody = file_get_contents('php://input'); $xSign   = $_SERVER['HTTP_X_SIGN'] ?? ''; if ($xSign === '') {     http_response_code(400);     echo 'Missing X-Sign';     exit; } $signature = base64_decode($xSign); $pubKeyPem = getMonoPubKeyPem($token); $pubKey = openssl_get_publickey($pubKeyPem); if ($pubKey === false) {     http_response_code(500);     echo 'Invalid pubkey';     exit; } // OPENSSL_ALGO_SHA256 – как в примерах документации $ok = openssl_verify($rawBody, $signature, $pubKey, OPENSSL_ALGO_SHA256); if ($ok !== 1) {     http_response_code(401);     echo 'Invalid signature';     exit; } $payload = json_decode($rawBody, true); if (!is_array($payload) || empty($payload['invoiceId'])) {     http_response_code(400);     echo 'Bad payload';     exit; } $invoiceId    = $payload['invoiceId']; $status       = $payload['status'] ?? 'unknown'; $modifiedDate = $payload['modifiedDate'] ?? null; // Далее - твоя бизнес-логика: // 1) найти запись в БД по invoiceId // 2) сравнить modifiedDate (не обновлять более старым payload) // 3) если status=success -> отметить заказ как оплаченный // 4) вернуть 200 ОК, чтобы monobank не ретра webhook http_response_code(200); echo 'OK';

Важные детали (они из документации):

  1. X-Sign — подпись ECDSA тела веб-перехватчика;
  2. modifiedDate — основной критерий того, какой статус является релевантным;
  3. Тело вебхука соответствует структуре «статус счета», которая содержит invoiceId, статус, суммы, причины ошибок и т. д.

Поля статуса и ответа: что на самом деле использовать в бизнес-логике

Метод статуса счета возвращает полезный набор полей, в том числе:

  • invoiceId

  • статус (например, создано)

  • failureReason и errCode (когда платеж не прошел)

  • сумма, ccy, finalAmount

  • Дата создания, Дата модификации

  • ссылка, назначение

  • payInfo (маскированная карта, rrn, tranId, и т. д. — полезно для животных)

  • cancelList, другие объекты

В примере из документации эти поля непосредственно видны в образце ответа.

Практическая рекомендация:
для большинства сценариев электронной коммерции вы используете как минимум:

  • статус

  • invoiceId

  • modifiedDate

  • finalAmount (если для вас возможны частичные списания/комиссии/удержания)

И failureReason / errCode — чтобы показать человеку адекватное сообщение («Недостаточно средств», «Неверный CVV» и т. д.) и не отклонить платеж в «волшебство, которое не работает».

Проверка статуса платежа (счет/статус) как «страховка»

Конечная точка:

  • GET /api/merchant/invoice/status?invoiceId={invoiceId с заголовком X-Token

monobank прямо говорит: не используйте этот сервис как основной механизм после транзакции; лучше, чем вебхуки. Но для случаев «вебхук не пришел» или «был простой на стороне продавца» это подходящий инструмент для восстановления согласованности.

Пример PHP

$token = getenv('MONO_X_TOKEN');
$invoiceId = $_GET['invoiceId'];
$url = 'https://api.monobank.ua/api/merchant/invoice/status?invoiceId=' . urlencode($invoiceId);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,     CURLOPT_HTTPHEADER     => ['X-Token: ' . $token], ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) {     throw new RuntimeException("Monobank status failed: HTTP $httpCode; body=$response"); } $data = json_decode($response, true); // Далее: сверяешь data['status'], data['modifiedDate'], data['finalAmount'] и обновляешь БД

Готовые модули и интеграции: когда лучше не писать код с нуля

Если ваш сайт уже работает на популярной CMS/конструкторе, иногда лучшим решением будет не изобретать велосипед, а предоставить готовую интеграцию.

Официальная страница готовых интеграций

монобанк имеет централизованную страницу, где готовые интеграции и платежные модули собраны для различных систем ("по нажатию на логотип партнера, можно перейти к инструкции по настройке").

Это «минимальный пользовательский» вариант: вставил токен в настройках — и он работает.

Пример: WooCommerce/WordPress

Существует официальный плагин для WordPress/WooCommerce (Monobank WP Payment Plugin), который позиционируется как официальный модуль для подключения к Интернету эквайринг.

Пример: OpenCart

В экосистеме OpenCart также есть модули для MonoPay (включая общедоступные каталоги модулей и форумы).

Когда модуль — лучший выбор

Выберите модуль, если:

  • вам необходимо «быстрый старт оплаты» без пользовательской логики;

  • вы не планируете сложные сценарии (удержание, токенизация, разделение платежей, выставление счетов);

  • вам нужна поддержка обновлений «внутри CMS».

Когда требуется интеграция API

Напишите интеграцию через API, если:

  • у вас собственный бэкенд (Laravel/CI/Symfony/Node/Go и т. д.) и нетипичная логика платежей;

  • требуются сложные сценарии (например, удержание/финализация, токенизация, несколько продуктов с собственными правилами);

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

 

Практический контрольный список «сделано и забыто»: как обеспечить интеграцию качества производства

  1. Токены хранятся только в секретах/ENV (не в коде, не в репозитории).

  2. reference сделайте его уникальным на вашей стороне (например, ORDER-). Это очень помогает при сравнении.

  3. Конечная точка веб-перехватчика:

    • должен ответить быстро 200 ОК;

    • должен быть идемпотентным;

    • необходимо подтвердить X-Sign.

  4. Логика обновления статуса:

    • не доверяйте порядку поступления событий;

    • всегда сравнивайте modifiedDate и не перезаписывайте «новый» статус со «старым».

  5. Выполните задание по сверке (корона/очередь): раз в N минут проверяйте "висящие" счета через invoice/status (только для тех, у кого еще не было вебхук). Это соответствует назначению метода status как резервного.

  6. Перед запуском в рабочей версии:

    • запустить тестовую среду с тестовым токеном;

    • убедитесь, что ваши URL-адреса доступны из Интернета и не заблокированы WAF/Cloudflare;

    • проверьте, что «успех» действительно преобразует заказ в «оплаченный» только на сервере (вебхук/статус), а не «путем перенаправления».

 

Интеграция MonoPay (плата моно) хорошо вписывается в современную архитектуру «счет → страница оплаты → вебхук → подтверждение в базе данных», но качество решения определяется тремя вещами:

  • правильное подключение и управление токенами в офисе компании;

  • правильная обработка веб-перехватчиков (подпись + modifiedDate + идемпотентность);

  • имейте резервный механизм выверки через счет/статус на случай редкой десинхронизации.