Skip to content

Документация для мерчантов

Этот документ описывает интеграцию мерчанта с REST API шлюза OnePay V1: приём платежей, hosted checkout, возвраты, выплаты, сохранённые карты, подписки и webhook-уведомления.

Быстрый старт

  1. Получите у OnePay параметры мерчанта:
ПараметрГде используется
tenant_idИдентификатор tenant в OnePay.
merchant_idИдентификатор мерчанта.
profile_idПрофиль мерчанта: валюта, терминалы, routing rules, webhook URL.
api_key_idЗаголовок X-Merchant-Key-Id.
api_secretЗаголовок X-Merchant-Key-Secret; выдаётся один раз при подключении или ротации.
webhook_signing_secretСекрет для проверки webhook-подписей.
  1. Настройте URL для webhook-уведомлений на профиле через менеджера или backoffice. В create-запросах webhook_url не передаётся: шлюз берёт URL из профиля и сохраняет его на операции.

  2. Создайте платёж:

bash
curl -X POST "$API_BASE_URL/v1/payments" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: order-10001-create" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "amount": 150000,
    "currency": "KZT",
    "payment_method": "card",
    "merchant_order_id": "ORDER-10001",
    "description": "Оплата заказа ORDER-10001",
    "return_url": "https://merchant.example/orders/ORDER-10001/result"
  }'
  1. Перенаправьте покупателя на data.payment_page_url.

  2. Дождитесь webhook о терминальном статусе или проверьте платёж через GET /v1/payments/{payment_id}.

Базовый URL и формат

Все запросы отправляются в JSON:

http
Content-Type: application/json

Примеры ниже используют переменную:

bash
API_BASE_URL=https://api.example.kz

Суммы в KZT передаются в тиинах. Например 150000 означает 1 500.00 KZT.

Аутентификация

Для прямой серверной интеграции используйте API-ключи в заголовках:

HeaderОписание
X-Merchant-Key-IdИдентификатор ключа мерчанта.
X-Merchant-Key-SecretСекрет ключа мерчанта.

Пример:

http
X-Merchant-Key-Id: key_live_xxx
X-Merchant-Key-Secret: secret_live_xxx

При этой схеме tenant_id и merchant_id передаются в теле POST-запросов или query-параметрах GET-запросов.

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

Для операций создания передавайте уникальный ключ:

http
Idempotency-Key: order-10001-create

Правила:

СценарийРезультат
Тот же ключ и тот же payloadВозвращается ранее созданный ресурс.
Тот же ключ и другой payload409 idempotency_mismatch.
Новый бизнес-заказИспользуйте новый ключ.

Ключ можно передать в заголовке Idempotency-Key или в JSON-поле idempotency_key. Заголовок имеет приоритет.

Стандартный формат ответа

Успешные ответы возвращаются в envelope:

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "payment_id": 12345,
    "status": "created"
  }
}

Ошибки возвращаются в общем формате:

json
{
  "success": false,
  "error_code": 400,
  "message": "amount is required",
  "message_key": "validation_error",
  "data": {
    "field": "amount"
  }
}

Типовые HTTP-статусы:

HTTPКогда возникает
400Ошибка JSON или валидации.
401Неверные или отсутствующие merchant credentials.
404Ресурс не найден или недоступен мерчанту.
409Конфликт статуса, лимиты, баланс, идемпотентность.
502Коннектор банка временно недоступен.
500Внутренняя ошибка шлюза.

Приём платежа через hosted checkout

Поток

  1. Мерчант создаёт платёж через POST /v1/payments.
  2. Шлюз возвращает payment_page_url и client_secret.
  3. Покупатель открывает payment_page_url и вводит данные карты на стороне шлюза.
  4. При необходимости покупатель проходит 3DS.
  5. Шлюз переводит платёж в терминальный статус и отправляет webhook.
  6. Покупатель возвращается на return_url.

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

POST /v1/payments

Обязательные поля:

ПолеТипОписание
tenant_idstringTenant мерчанта.
merchant_idstringМерчант.
profile_idstringПрофиль/терминальная конфигурация.
amountint64Сумма в тиинах.
currencystringВалюта, например KZT.
payment_methodstringОбычно card; также возможны методы, разрешённые на профиле.
return_urlstringКуда вернуть покупателя после оплаты.

Опциональные поля:

ПолеТипОписание
merchant_order_idstringID заказа на стороне мерчанта.
descriptionstringОписание платежа.
customerobjectДанные клиента. Нужны для сохранения карты/мандата.
save_cardbooleanСоздать mandate для последующих списаний.
mandate_idstringСписать по ранее сохранённой карте.
line_itemsarrayКорзина заказа.
additional_charges_amountint64Дополнительные сборы в тиинах.
discount_amountint64Скидка в тиинах.

Пояснения по основным полям:

ПолеКак заполнять
amountПередавайте сумму в тиинах. Например 150000 означает 1 500.00 KZT.
currencyИспользуйте валюту, разрешённую на профиле. Обычно для Казахстана это KZT.
payment_methodДля обычной карточной оплаты укажите card. Apple Pay и Google Pay подключаются отдельно как способы оплаты.
merchant_order_idУдобно передавать номер заказа из вашей системы, чтобы потом сопоставлять ответ и webhook.
return_urlURL страницы, куда покупатель вернётся после оплаты или отказа. Финальный результат всё равно лучше фиксировать по webhook.
customerДанные клиента. Нужны, если вы хотите сохранить карту или связать платёж с конкретным клиентом.
save_cardЕсли true, после успешной оплаты OnePay может создать mandate_id для последующих списаний.
mandate_idИспользуется только для списания по ранее сохранённой карте. Для первого платежа его не передают.
line_itemsДетализация корзины. Полезна для отображения состава заказа и сверки на стороне мерчанта.

Пример запроса с комментариями:

jsonc
{
  "tenant_id": "tenant-1",
  "merchant_id": "merchant-1",
  "profile_id": "profile-1",
  "amount": 150000, // сумма в тиинах: 150000 = 1 500.00 KZT
  "currency": "KZT",
  "payment_method": "card", // обычная оплата банковской картой
  "merchant_order_id": "ORDER-10001", // ID заказа в вашей системе
  "description": "Оплата заказа ORDER-10001",
  "return_url": "https://merchant.example/orders/ORDER-10001/result",
  "customer": {
    "customer_id": "customer-100", // ваш идентификатор клиента
    "name": "Ivan Ivanov",
    "email": "ivan@example.kz",
    "phone": "+77010000000"
  },
  "save_card": false // true, если нужно сохранить карту после успешной оплаты
}

Пример ответа:

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "payment_id": 12345,
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "status": "created",
    "amount": 150000,
    "currency": "KZT",
    "payment_method": "card",
    "merchant_order_id": "ORDER-10001",
    "client_secret": "sec_xxx",
    "payment_page_url": "https://pay.example.kz/checkout/sec_xxx",
    "redirect_url": "",
    "created_at": "2026-08-17T10:00:00Z",
    "updated_at": "2026-08-17T10:00:00Z"
  }
}

Что использовать из ответа:

ПолеНазначение
payment_idID платежа в OnePay. Используется для проверки статуса, отмены и сверки с webhook.
statusТекущий статус платежа. После создания обычно created.
client_secretТехнический ключ checkout-сессии. Не является merchant API-secret.
payment_page_urlГотовая ссылка на hosted checkout. Перенаправьте на неё покупателя.
redirect_urlМожет быть заполнен при сценариях, где требуется внешнее действие, например 3DS.

Проверить платёж

GET /v1/payments/{payment_id}?tenant_id=...&merchant_id=...

bash
curl "$API_BASE_URL/v1/payments/12345?tenant_id=tenant-1&merchant_id=merchant-1" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET"

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

Статусы платежа

СтатусЗначение
createdПлатёж создан, покупатель ещё не завершил оплату.
processingШлюз обрабатывает платёж.
waiting_for_authТребуется внешняя аутентификация, например 3DS.
pendingОжидается финальный ответ банка.
succeededОплата успешна.
failedОплата отклонена или завершилась ошибкой.
refundedПлатёж полностью возвращён.

Терминальные статусы для оплаты: succeeded, failed, refunded.

Server-to-server исполнение платежа

Если у мерчанта есть разрешение и PCI-контур, карту можно передать напрямую в POST /v1/payments/{payment_id}/execute.

bash
curl -X POST "$API_BASE_URL/v1/payments/12345/execute" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "client_secret": "sec_xxx",
    "card": {
      "pan": "4111111111111111",
      "month": "12",
      "year": "2030",
      "holder": "IVAN IVANOV",
      "cvv": "123"
    },
    "browser_info": {
      "ip_address": "203.0.113.10",
      "user_agent": "Mozilla/5.0",
      "accept_header": "text/html,application/xhtml+xml",
      "accept_language": "ru-KZ,ru;q=0.9",
      "language": "ru",
      "screen_width": 1440,
      "screen_height": 900,
      "color_depth": 24,
      "time_zone_offset": -300,
      "java_enabled": false,
      "js_enabled": true
    }
  }'

Пример ответа:

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "payment_id": 12345,
    "payment_status": "waiting_for_auth",
    "attempt_id": 77,
    "attempt_status": "waiting_for_auth",
    "redirect_url": "https://api.example.kz/v1/payments/12345/3ds/start?attempt_id=77&client_secret=sec_xxx",
    "rrn": "",
    "external_reference": "",
    "error_code": "",
    "error_message": ""
  }
}

Если redirect_url заполнен, перенаправьте покупателя по этому URL.

3DS-страницы и возврат банка обрабатываются gateway endpoints: /v1/payments/{payment_id}/3ds/start и /v1/payments/{payment_id}/3ds/return. Обычно мерчант не вызывает их напрямую: покупатель переходит по redirect_url, а gateway завершает сценарий и затем возвращает покупателя на return_url.

Отмена платежа до финала

POST /v1/payments/{payment_id}/cancel

Отмена доступна для платежей в статусах created или waiting_for_auth. Это локальная отмена в шлюзе без вызова банка.

bash
curl -X POST "$API_BASE_URL/v1/payments/12345/cancel" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: order-10001-cancel" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "reason": "buyer abandoned checkout"
  }'

Возврат платежа

POST /v1/refunds

Refund создаётся только для успешного платежа. Если amount не передан, шлюз пытается вернуть весь доступный остаток. Для частичного возврата передайте amount в тиинах.

bash
curl -X POST "$API_BASE_URL/v1/refunds" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: refund-10001-1" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "payment_id": 12345,
    "amount": 50000,
    "reason": "partial return"
  }'

Пример ответа:

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "refund_id": 9001,
    "payment_id": 12345,
    "payment_attempt_id": 77,
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "status": "succeeded",
    "amount": 50000,
    "currency": "KZT",
    "reason": "partial return",
    "connector_name": "halyk",
    "connector_refund_id": "bank-ref-123",
    "rrn": "123456789012",
    "arn": "",
    "external_reference": "9001",
    "error_code": "",
    "error_message": "",
    "created_at": "2026-08-17T10:10:00Z",
    "updated_at": "2026-08-17T10:10:03Z"
  }
}

Статусы refund: created, processing, pending, succeeded, failed. Терминальные: succeeded, failed.

Выплаты клиентам

Customer payout — выплата конечному получателю с баланса мерчанта. Это не то же самое, что settlement мерчанту.

Создать выплату

POST /v1/payouts

bash
curl -X POST "$API_BASE_URL/v1/payouts" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: payout-20001" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "amount": 250000,
    "currency": "KZT",
    "destination_type": "card",
    "destination_ref": "card_token_abc",
    "recipient_name": "Ivan",
    "recipient_surname": "Ivanov",
    "description": "Customer refund to card"
  }'

Поля:

ПолеТипОписание
amountint64Сумма выплаты в тиинах.
currencystringВалюта выплаты.
destination_typestringcard или iban.
destination_refstringТокен/референс карты или IBAN. Для card нельзя передавать raw PAN.
recipient_namestringИмя получателя, если требуется коннектором.
recipient_surnamestringФамилия получателя, если требуется коннектором.
payout_cinfostringДополнительная информация для payout-коннектора.
descriptionstringОписание выплаты.

Если доступного баланса недостаточно, API вернёт 409.

Получить выплату

GET /v1/payouts/{payout_id}?tenant_id=...&merchant_id=...

Статусы payout: created, processing, pending, succeeded, failed.

Batch-выплаты

POST /v1/payout-batches

bash
curl -X POST "$API_BASE_URL/v1/payout-batches" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: batch-2026-08-17-1" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "items": [
      {
        "item_id": "item-1",
        "item_idempotency_key": "batch-2026-08-17-1-item-1",
        "amount": 100000,
        "currency": "KZT",
        "destination_type": "card",
        "destination_ref": "card_token_1"
      },
      {
        "item_id": "item-2",
        "item_idempotency_key": "batch-2026-08-17-1-item-2",
        "amount": 150000,
        "currency": "KZT",
        "destination_type": "iban",
        "destination_ref": "KZ000000000000000000"
      }
    ]
  }'

Статусы batch: created, processing, succeeded, failed, partially_succeeded.

Получить batch можно через GET /v1/payout-batches/{batch_id}?tenant_id=...&merchant_id=....

Сохранённые карты и рекурренты

Сохранить карту для повторных списаний

Создайте обычный платёж с save_card: true и заполненным customer. Покупатель проходит hosted checkout. После успешной оплаты в ответах и webhook может появиться mandate_id.

json
{
  "tenant_id": "tenant-1",
  "merchant_id": "merchant-1",
  "profile_id": "profile-1",
  "amount": 10000,
  "currency": "KZT",
  "payment_method": "card",
  "return_url": "https://merchant.example/cards/result",
  "save_card": true,
  "customer": {
    "customer_id": "customer-100",
    "name": "Ivan Ivanov",
    "email": "ivan@example.kz",
    "phone": "+77010000000"
  }
}

Пояснения:

ПолеНазначение
save_cardПросит OnePay сохранить карту после успешной оплаты.
customer.customer_idИдентификатор клиента в системе мерчанта. По нему удобно получать сохранённые карты.
customer.emailEmail клиента, если он есть в вашей системе.
customer.phoneТелефон клиента в международном формате.
mandate_idПоявляется после успешного сохранения карты и используется для будущих списаний.

Список сохранённых карт

GET /v1/mandates?tenant_id=...&merchant_id=...&customer_id=...

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

Отмена сохранённой карты

POST /v1/mandates/{mandate_id}/cancel?tenant_id=...&merchant_id=...

Платёж по сохранённой карте

Создайте платёж с payment_method: "mandate" и mandate_id.

json
{
  "tenant_id": "tenant-1",
  "merchant_id": "merchant-1",
  "profile_id": "profile-1",
  "amount": 99000,
  "currency": "KZT",
  "payment_method": "mandate",
  "mandate_id": "mandate_xxx",
  "merchant_order_id": "ORDER-RECUR-1",
  "return_url": "https://merchant.example/orders/ORDER-RECUR-1/result"
}

Пояснения:

ПолеНазначение
payment_methodДля сохранённой карты укажите mandate.
mandate_idID сохранённой карты, полученный после первого успешного платежа.
merchant_order_idНовый ID заказа или списания на стороне мерчанта.
return_urlURL возврата, если платёж потребует дополнительного действия покупателя.

Подписки

Подписка создаёт регулярные списания по mandate.

POST /v1/subscriptions

bash
curl -X POST "$API_BASE_URL/v1/subscriptions" \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Key-Id: $API_KEY_ID" \
  -H "X-Merchant-Key-Secret: $API_SECRET" \
  -H "Idempotency-Key: sub-customer-100-basic" \
  -d '{
    "tenant_id": "tenant-1",
    "merchant_id": "merchant-1",
    "profile_id": "profile-1",
    "mandate_id": "mandate_xxx",
    "amount": 299000,
    "currency": "KZT",
    "schedule": "monthly",
    "customer_id": "customer-100",
    "customer_email": "ivan@example.kz",
    "customer_name": "Ivan Ivanov"
  }'

Основные endpoints:

МетодПутьНазначение
GET/v1/subscriptionsСписок подписок.
GET/v1/subscriptions/{subscription_id}Получить подписку.
POST/v1/subscriptions/{subscription_id}/pauseПауза.
POST/v1/subscriptions/{subscription_id}/resumeВозобновить.
POST/v1/subscriptions/{subscription_id}/cancelОтменить.
POST/v1/subscriptions/{subscription_id}/cancel-optionsОтменить с параметрами, например cancel_at_period_end.
POST/v1/subscriptions/{subscription_id}/change-planСменить тарифный план.
POST/v1/subscriptions/{subscription_id}/chargeРучное списание.
GET/v1/subscriptions/{subscription_id}/chargesСписок списаний.
POST/v1/subscriptions/{subscription_id}/charges/{charge_id}/refundВозврат списания.

Доступные способы оплаты

GET /v1/payment-methods?tenant_id=...&merchant_id=...&profile_id=...

Ответ показывает методы, разрешённые текущими routing rules и настройками коннекторов:

Вызывайте этот endpoint перед показом способов оплаты, если на разных профилях или терминалах доступны разные методы. Например один профиль может принимать только card, а другой — card, applepay и googlepay.

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "payment_methods": [
      { "type": "card", "enabled": true }
    ]
  }
}

Checkout endpoints для фронтенда

Hosted checkout использует browser-facing endpoints без merchant API-secret:

МетодПутьНазначение
GET/v1/checkout/{client_secret}HTML-страница checkout или JSON-сессия при Accept: application/json / ?format=json.
POST/v1/checkout/{client_secret}/cancelОтмена checkout-сессии по client_secret, например при уходе покупателя назад в магазин.

client_secret относится только к конкретному payment intent. Не передавайте merchant credentials в браузер.

Если вы используете готовую payment_page_url, напрямую вызывать эти endpoints обычно не нужно. JSON-режим GET /v1/checkout/{client_secret}?format=json полезен для собственной payment-page интеграции, где frontend мерчанта показывает состояние checkout-сессии, но не получает merchant API-secret.

Apple Pay и Google Pay

Apple Pay и Google Pay подключаются как способы оплаты внутри обычного pay-in flow. Сначала проверьте, что метод доступен через /v1/payment-methods, затем создайте платёж с payment_method: "applepay" или payment_method: "googlepay".

МетодПутьНазначение
POST/v1/apple-pay/validate-merchantВалидация Apple Pay merchant session; требует client_secret платежа с payment_method: "applepay".

Apple Pay и Google Pay должны быть разрешены на профиле/маршрутизации. Если метод не настроен, endpoint может вернуть 404.

Google Pay

Google Pay ™ — это удобный и безопасный способ оплаты покупок с помощью карт, сохранённых в аккаунте Google. Покупатель может использовать Google Pay на сайтах, в приложениях и в других местах, где доступен этот способ оплаты.

Прежде чем интегрироваться с OnePay, ознакомьтесь с документацией Google:

Преимущества Google Pay ™

  • Удобство: покупатель выбирает сохранённую карту и не вводит платёжные данные вручную при каждой покупке.
  • Скорость: оплата проходит быстрее, так как нужные данные уже доступны в Google Pay.
  • Безопасность: Google Pay использует токенизацию, шифрование и дополнительные механизмы аутентификации для защиты платёжных данных.
  • Широкая поддержка: Google Pay можно использовать на сайтах и в приложениях, где подключён этот способ оплаты.

Как работает Google Pay ™

NFC-технология

Google Pay поддерживает бесконтактную оплату с помощью NFC на совместимых устройствах. Покупатель может подтвердить оплату на устройстве и оплатить покупку без передачи реквизитов карты продавцу.

Онлайн-покупки

Google Pay также используется для онлайн-платежей. При оформлении заказа на сайте покупатель выбирает Google Pay как способ оплаты и завершает покупку без ручного ввода карточных данных.

Совместимость

Google Pay доступен на поддерживаемых устройствах и в поддерживаемых браузерах. Перед запуском оплаты на сайте необходимо следовать требованиям Google к web-интеграции, тестированию и фирменному оформлению кнопки оплаты.

Что требуется для подключения

Для подключения Google Pay на платёжной странице необходимо уведомить менеджера OnePay. Менеджер проконсультирует по требованиям и дальнейшим шагам. Включение Google Pay выполняется на стороне эквайринга и платёжной страницы. С помощью Google Pay доступен приём платежей через платёжную страницу.

SCA и PSD2

Google Pay поддерживает токенизированный сценарий оплаты, который обычно является предпочтительным. В большинстве случаев эмитенты принимают такой сценарий как соответствующий требованиям строгой аутентификации клиента.

Все мерчанты должны соблюдать правила допустимого использования Google Pay API и условия использования Google Pay API. Google Pay является товарным знаком Google LLC.

Apple Pay

Apple Pay — это система онлайн-платежей Apple для оплаты на сайтах и в приложениях. Для web-интеграции используется Apple Pay on the Web.

Перед интеграцией ознакомьтесь с документацией Apple:

Для Apple Pay домен, где показывается кнопка оплаты, должен быть зарегистрирован и верифицирован. Merchant validation выполняется через POST /v1/apple-pay/validate-merchant.

Полученный Apple Pay payment token необходимо передавать в поле apple_pay_token.

Webhook-уведомления

Webhook URL задаётся на профиле мерчанта. При создании платежа, payout или подписки шлюз сохраняет текущий URL на операции, поэтому последующая смена URL не меняет адрес доставки для уже созданных операций.

Stable формат

Заголовки:

HeaderОписание
X-Webhook-IdID события.
X-Webhook-TimestampUnix timestamp в секундах.
X-Webhook-AttemptНомер попытки доставки.
X-SignatureHex HMAC-SHA256 подпись.

Подпись считается так:

text
hex(HMAC_SHA256(webhook_signing_secret, "<event_id>.<timestamp>.<raw_body>"))

Пример проверки на Node.js:

js
import crypto from "node:crypto";

export function verifyOnePayWebhook({ secret, eventId, timestamp, rawBody, signature }) {
  const payload = `${eventId}.${timestamp}.${rawBody}`;
  const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Пример тела payment webhook:

json
{
  "event_id": "evt_123",
  "event_type": "payment.succeeded",
  "payment_id": 12345,
  "tenant_id": "tenant-1",
  "merchant_id": "merchant-1",
  "profile_id": "profile-1",
  "status": "succeeded",
  "amount": 150000,
  "currency": "KZT",
  "merchant_order_id": "ORDER-10001",
  "rrn": "123456789012",
  "external_reference": "bank-operation-123",
  "created_at": "2026-08-17T10:00:00Z",
  "updated_at": "2026-08-17T10:02:00Z"
}

Пояснения по полям webhook:

ПолеНазначение
event_idУникальный ID события. Используйте его для идемпотентной обработки webhook.
event_typeТип события: например payment.succeeded или payment.failed.
payment_idID платежа в OnePay. По нему можно запросить актуальное состояние платежа.
statusСтатус платежа на момент отправки события.
merchant_order_idID заказа из вашего create-запроса. По нему удобно обновлять заказ в вашей системе.
rrnБанковский reference number, если он доступен после обработки платежа.
external_referenceИдентификатор операции на стороне внешнего коннектора или банка, если доступен.
created_atКогда платёж был создан.
updated_atКогда платёж был обновлён до текущего состояния.

Типовые события:

СобытиеКогда отправляется
payment.succeededПлатёж успешно завершён.
payment.failedПлатёж завершён ошибкой.
payment.refundedПлатёж полностью возвращён.
refund.createdВозврат создан.
refund.succeededВозврат успешен.
refund.failedВозврат неуспешен.
payout.createdВыплата создана.
payout.succeededВыплата успешна.
payout.failedВыплата неуспешна.
payout_batch.succeededВсе элементы batch-выплаты успешны.
payout_batch.partially_succeededУ batch есть успешные и неуспешные элементы.
subscription.*События жизненного цикла подписок.

Мерчант должен отвечать на webhook HTTP 2xx. При ошибке доставки шлюз повторяет отправку по retry-политике. Повтор webhook не означает повторную платёжную операцию; обработчик мерчанта должен быть идемпотентным по X-Webhook-Id.

Рекомендуемая обработка webhook на стороне мерчанта:

  1. Сохраните X-Webhook-Id и проверьте, не обрабатывали ли это событие раньше.
  2. Проверьте подпись X-Signature по raw body запроса.
  3. Найдите заказ по merchant_order_id или payment_id.
  4. Обновите заказ только если webhook пришёл с финальным статусом.
  5. Верните HTTP 2xx, когда событие успешно сохранено или уже было обработано.

Тестовая интеграция

Рекомендуемый checklist:

СценарийЧто проверить
Успешный pay-inСоздание платежа, переход на checkout, payment.succeeded, webhook.
Отказ pay-inТерминальный failed, корректный error_code.
3DSwaiting_for_auth, redirect URL, возврат из 3DS, финальный webhook.
Повтор createТот же Idempotency-Key не создаёт второй платёж.
RefundЧастичный и полный возврат, refund.succeeded, payment.refunded.
PayoutУспех, отказ, недостаточный баланс.
Webhook retryПовторная доставка не дублирует заказ на стороне мерчанта.

Для Halyk sandbox в локальных окружениях убедитесь, что контейнер gateway имеет системные CA-сертификаты; иначе HTTPS-вызовы к sandbox могут падать на проверке сертификата.

Отличия от некоторых PSP-документаций

В некоторых платёжных системах callback URL и параметры подписи передаются в каждом create-запросе. В OnePay V1:

  • merchant API использует заголовки X-Merchant-Key-Id и X-Merchant-Key-Secret, а не HMAC-подпись каждого запроса;
  • webhook URL управляется на профиле, а не в теле операции;
  • суммы в KZT передаются в тиинах;
  • Idempotency-Key обязателен для безопасных повторов create/refund/payout операций;
  • hosted checkout является рекомендуемым способом приёма карточных данных.