Voyage Pay / Merchant Gateway
Merchant Gateway
Собственный API Voyage Pay для создания ордеров, получения статуса и приёма подписанных событий.
SECTION / 01
Быстрый старт
От ключа до checkout URL за четыре шага.
01
Получите Voyage Key
Овнер создаёт merchant workspace. Ключ начинается с vp_live_ и хранится только на вашем backend.
02
Отправьте order request
Передайте свой external_id, сумму, метод и URL для событий.
03
Откройте checkout_url
Перенаправьте клиента на URL из order envelope.
04
Примите order.paid
Проверьте Voyage-Signature, зафиксируйте delivery_id и ответьте HTTP 2xx.
SECTION / 02
Доступ к API
| Base URL | https://api.voyagepay.pro | Единый production-адрес Merchant Gateway. |
| Voyage-Api-Key | vp_live_… | Обязателен для создания и проверки ордера. |
| Webhook secret | vp_wh_… | Используется для HMAC-подписи событий. |
Граница доверия
Не передавайте Voyage Key в browser, mobile application, URL, логи или аналитику.
SECTION / 03
Новый ордер
POST /api/merchant/v1/orders
curl -X POST https://api.voyagepay.pro/api/merchant/v1/orders \
-H "Content-Type: application/json" \
-H "Voyage-Api-Key: vp_live_ВАШ_КЛЮЧ" \
-d '{
"external_id": "checkout-10492",
"amount": 10000,
"currency": "RUB",
"payment_method": "sbp",
"traffic_category": "classic",
"operation": "payin",
"notification_url": "https://merchant.example/events/voyage",
"redirect_url": "https://merchant.example/checkout/complete",
"cancel_redirect_url": "https://merchant.example/checkout/cancelled",
"note": "Заказ 10492"
}'| external_id | string | Ваш уникальный ID заказа и ключ идемпотентности. |
| amount | number ≥ 1 | Сумма ордера. |
| currency | RUB | Рабочая валюта. |
| payment_method | card | sbp | Карта или СБП. |
| traffic_category | classic | bt | mk | Категория маршрутизации; classic по умолчанию. |
| operation | payin | payout | Направление; payin по умолчанию. |
| notification_url | HTTPS URL | Адрес для Voyage Events. |
Безопасные повторы
Повторите исходный запрос с тем же
external_id, если не получили ответ из-за сетевого сбоя. Voyage Pay вернёт созданный ранее ордер. Если остальные параметры отличаются, API ответит 409 Conflict.SECTION / 04
Ответ и статус
{
"request_id": "8c998946-93ef-4bcc-b119-1b874ac69c42",
"order": {
"id": "8c998946-93ef-4bcc-b119-1b874ac69c42",
"external_id": "checkout-10492",
"state": "awaiting_payment",
"amount": { "value": 10000, "currency": "RUB" },
"payment_method": "sbp",
"traffic_category": "classic",
"operation": "payin",
"expires_at": "2026-07-21T12:30:00.000Z",
"checkout_url": "https://voyagepay.pro/pay/8c998946-93ef-4bcc-b119-1b874ac69c42",
"payment_details": {
"bank": "bank-name",
"recipient": "Получатель",
"account": "+7 900 000-00-00"
}
}
}GET /api/merchant/v1/orders/:id с заголовком Voyage-Api-Key.
| awaiting_payment | Ожидает оплаты | Ордер активен. |
| paid | Оплачен | Финальный положительный статус. |
| declined | Отклонён | Ордер не завершён. |
| expired | Истёк | Время оплаты закончилось. |
| cancelled | Отменён | Ордер отменён. |
SECTION / 05
Диспуты
Откройте спор только по собственному ордеру и отслеживайте решение по API.
POST /api/merchant/v1/disputes
curl -X POST https://api.voyagepay.pro/api/merchant/v1/disputes \
-H "Content-Type: application/json" \
-H "Voyage-Api-Key: vp_live_ВАШ_КЛЮЧ" \
-d '{
"order_id": "8c998946-93ef-4bcc-b119-1b874ac69c42",
"reason": "not_received",
"description": "Клиент сообщил об оплате, но ордер не подтверждён",
"evidence": [
"https://merchant.example/evidence/receipt-10492.jpg"
]
}'| order_id | UUID | ID ордера Voyage Pay. Чужой ордер недоступен. |
| reason | enum | not_received | wrong_amount | operator_inactive | other |
| description | string | Описание ситуации: от 5 до 2000 символов. |
| evidence | HTTPS URL[] | Необязательно: до 10 защищённых ссылок на доказательства. |
{
"request_id": "54f9b753-5af0-44e9-9e6a-1c3c6a8838b8",
"dispute": {
"id": "54f9b753-5af0-44e9-9e6a-1c3c6a8838b8",
"order_id": "8c998946-93ef-4bcc-b119-1b874ac69c42",
"status": "open",
"reason": "not_received",
"description": "Клиент сообщил об оплате, но ордер не подтверждён",
"evidence": ["https://merchant.example/evidence/receipt-10492.jpg"],
"operator_response": null,
"operator_evidence": [],
"resolution_note": null,
"created_at": "2026-07-31T01:20:00.000Z",
"updated_at": "2026-07-31T01:20:00.000Z",
"resolved_at": null
}
}| GET /api/merchant/v1/disputes | Список | Последние 100 диспутов текущего мерчанта. |
| GET /api/merchant/v1/disputes/:id | Карточка | Текущий статус, ответ оператора и решение. |
| open | Открыт | Диспут зарегистрирован. |
| under_review | На рассмотрении | Оператор ответил, администрация проверяет материалы. |
| resolved_merchant | В пользу мерчанта | Решение принято в пользу мерчанта. |
| resolved_operator | В пользу оператора | Ордер подтверждён и проведён. |
| cancelled | Отменён | Диспут отменён инициатором. |
Когда открывать диспут
Диспут можно открыть по активному, истёкшему или уже автоматически отменённому неподтверждённому ордеру. Если первоначальный резерв уже вернулся оператору, Voyage Pay повторно удержит доступную сумму из рабочего и страхового депозитов, а недостающую часть зафиксирует как долг оператора. Нельзя открыть диспут только по уже подтверждённому ордеру. Для одного ордера одновременно допускается один активный диспут. Все запросы требуют заголовок
Voyage-Api-Key.SECTION / 06
Voyage Events
POST https://merchant.example/events/voyage
Voyage-Delivery-Id: 4b811e8c-4db7-49a7-a762-662f77a1f88c
Voyage-Event: order.paid
Voyage-Signature: v1=<hex_hmac>
{
"delivery_id": "4b811e8c-4db7-49a7-a762-662f77a1f88c",
"type": "order.paid",
"occurred_at": "2026-07-21T12:15:00.000Z",
"data": {
"order_id": "8c998946-93ef-4bcc-b119-1b874ac69c42",
"external_id": "checkout-10492",
"state": "paid",
"amount": { "value": 10000, "currency": "RUB" },
"payment_method": "sbp",
"traffic_category": "classic",
"fee": 250
}
}Подпись считается от точных raw bytes HTTP body. Не собирайте JSON заново перед проверкой.
import crypto from 'node:crypto';
const calculated = 'v1=' + crypto
.createHmac('sha256', process.env.VOYAGE_WEBHOOK_SECRET)
.update(rawRequestBody)
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(request.headers['voyage-signature']),
Buffer.from(calculated),
);Повторы
Voyage Pay повторяет неуспешные доставки. Дедуплицируйте по
delivery_id.SECTION / 07
Чек-лист запуска
01
Проверяйте подпись
Отклоняйте событие до любой бизнес-логики, если Voyage-Signature неверна.
02
Храните delivery_id
Повторная доставка не должна повторно менять заказ.
03
Доверяйте state
redirect_url не подтверждает оплату. Источник истины — order.paid и GET order.
04
Отвечайте 2xx
Отправляйте 2xx только после надёжной фиксации события.
