Оплата на вашем сайте
Вы показываете товары покупателю. RivoDonate принимает оплату через включённые кассы, учитывает комиссии и зачисляет доход магазину. Версия API: v1.
Быстрый старт
- Откройте «Магазин → Управление → API интеграция» и создайте ключи.
- Сохраните их на сервере своего сайта. Укажите HTTPS адрес webhook и страницы возврата.
- Отправьте POST запрос с уникальным external_order_id.
- Перенаправьте покупателя на checkout_url. Там он выберет способ оплаты.
- Подтвердите заказ по подписанному событию order.paid или запросом статуса.
Ключ нельзя помещать в JavaScript браузера, мобильное приложение или публичный репозиторий. У каждого магазина свой ключ; заменить его можно только после удаления прежнего.
Адрес API
https://rivodonate.fun/api/v1/ordersЕсли сервер не поддерживает красивые адреса, тот же обработчик доступен по /index.php?route=merchant_orders. Запросы только с сервера; CORS для браузерной работы с ключом не включён.
Создание внешнего заказа
POST /api/v1/orders · Authorization: Bearer YOUR_API_KEY · Content-Type: application/json
curl -X POST 'https://rivodonate.fun/api/v1/orders' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
--data '{
"external_order_id": "order-1001",
"title": "Привилегия VIP",
"amount": "499.00",
"currency": "RUB",
"player_nick": "Steve",
"customer_email": "player@example.com"
}'| Поле | Формат и назначение |
|---|---|
external_order_id | Обязательно. Уникальный ID заказа в вашем магазине, 1-100 символов: A-Z, a-z, 0-9, _, -, ., :. |
title | Для внешнего товара: название до 150 символов. |
amount | Для внешнего товара: строка в RUB, до двух знаков после точки. Минимум задаёт платформа, максимум 1 000 000. |
currency | Необязательно. Только RUB. USDT в кассе не меняет валюту заказа API. |
player_nick | Обязательно. Ник игрока, 3-16 символов: латиница, цифры, _. |
customer_email | Обязательно. Email покупателя для чека. |
Сумму определяет ваш сервер по своему каталогу. Не доверяйте сумме, полученной от браузера покупателя. Комиссия рассчитывается RivoDonate: base_amount - цена товара, amount - сумма с долей комиссии покупателя. При отдельных комиссиях до выбора способа оплаты эта сумма предварительная. После выбора она пересчитывается по ставке способа и фиксируется при создании счёта в кассе. В подтверждении оплаты приходит окончательная сумма. Не передавайте комиссию повторно.
Ответ: 201 Created
{
"data": {
"id": "0123456789abcdef0123456789abcdef0123456789abcdef",
"external_order_id": "order-1001",
"status": "pending",
"title": "Привилегия VIP",
"amount": "548.90",
"base_amount": "499.00",
"currency": "RUB",
"payment_method": "",
"checkout_url": "https://rivodonate.fun/index.php?route=api_checkout&order=0123456789abcdef0123456789abcdef0123456789abcdef",
"expires_at": "2026-10-16T12:00:00+00:00",
"paid_at": null
}
}Сумма и дата в примере условные. Используйте значения из фактического ответа.
Повтор запроса без двойного заказа
Повторите запрос с тем же external_order_id и теми же полями: API вернёт прежний заказ с HTTP 200. Изменённые данные с этим ID дают 409. После сетевого таймаута повторяйте прежний запрос; не генерируйте новый ID, пока не выяснен статус старого. ID уникален в пределах магазина и сохраняется после смены ключей.
Пример PHP
Переменные окружения: RIVO_API_BASE - адрес платформы, RIVO_API_KEY - ваш ключ. Данные заказа берите из своей серверной базы.
$payload = [
'external_order_id' => 'order-1001',
'title' => 'Привилегия VIP',
'amount' => '499.00',
'currency' => 'RUB',
'player_nick' => 'Steve',
'customer_email' => 'player@example.com'
];
$curl = curl_init(getenv('RIVO_API_BASE') . '/api/v1/orders');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('RIVO_API_KEY'),
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR)
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($body === false || !in_array($status, [200, 201], true)) {
throw new RuntimeException('Не удалось создать заказ');
}
$order = json_decode($body, true, 512, JSON_THROW_ON_ERROR)['data'];
header('Location: ' . $order['checkout_url'], true, 303);
exit;Товар из каталога RivoDonate
Передайте product_id вместо title и amount. Цена, скидки, комиссия и команды выдачи берутся из RivoDonate; подменить их через API нельзя. Товар должен принадлежать магазину ключа и быть включён.
{
"external_order_id": "order-1002",
"product_id": 42,
"server_id": 7,
"player_nick": "Steve",
"customer_email": "player@example.com"
}server_id- целый ID привязанного сервера. При отсутствии выбирается первый привязанный сервер по ID.promo_code- необязательный действующий промокод.resource_amount- числовое количество для товара с переменным количеством. Для обычного товара не передаётся.- ID товара и сервера можно посмотреть в панели магазина при редактировании.
Для внешнего товара выдачу выполняет ваш сервер по webhook. Команды, цена и название фиксируются при создании заказа; изменения каталога не меняют существующую оплату.
Статусы и страницы возврата
curl 'https://rivodonate.fun/api/v1/orders?id=ORDER_ID' \
-H 'Authorization: Bearer YOUR_API_KEY'GET возвращает тот же объект data. Доступны только заказы магазина текущего ключа. Запрос читает локально подтверждённый статус; не инициирует новый платёж.
| Статус | Что делать |
|---|---|
| pending | Оплата ещё не подтверждена. Не выдавать товар. |
| paid | Оплата подтверждена. Выдать внешний товар один раз. |
| failed, cancelled, expired | Оплата не завершена. Не выдавать товар. |
| underpaid | Касса сообщила о недоплате. Не выдавать товар. |
Неоплаченная ссылка действует 24 часа до начала оплаты в кассе. После создания счёта срок и результат определяет касса. Заказ может перейти в paid после позднего подтверждения даже после неуспешного события - paid имеет приоритет.
Ссылки успешной и неудачной оплаты сохраняются из настроек магазина при создании заказа. Возврат из кассы направляет на них только после подтверждённого соответствующего статуса. Пока статус pending, покупатель видит ожидание и кнопку обновления. Наличие покупателя на вашей странице успеха не доказывает оплату.
Подписанные уведомления
RivoDonate отправляет JSON POST на сохранённый Webhook URL. Внешние товары требуют настроенного webhook. HTTPS, публичный домен и доступный публичный IPv4 обязательны. Переадресации не выполняются.
{
"event_id": "evt_123",
"event": "order.paid",
"data": {
"id": "ORDER_ID",
"external_order_id": "order-1001",
"status": "paid",
"title": "Привилегия VIP",
"amount": "548.90",
"base_amount": "499.00",
"currency": "RUB",
"payment_method": "banka_sbp",
"expires_at": "2026-10-16T12:00:00+00:00",
"paid_at": "2026-10-15T12:05:00+00:00"
}
}Заголовки: X-Rivo-Event-Id, X-Rivo-Timestamp (Unix UTC) и X-Rivo-Signature. Подпись - шестнадцатеричный HMAC-SHA256 от timestamp + "." + исходное тело запроса с вашим Webhook-секретом. Проверяйте подпись до разбора и обработки события; допускайте расхождение часов до 5 минут.
Приём с устойчивым сохранением (PHP + SQLite)
Один раз создайте таблицу входящих событий. Возвращайте 2xx только после сохранения события. Отдельный обработчик читает очередь и выдаёт заказ в транзакции, проверяя его ID, сумму, валюту и статус paid. Пример ниже сохраняет событие; логику выдачи вашего товара добавляет ваш сайт.
CREATE TABLE rivo_inbox (
event_id TEXT PRIMARY KEY,
payload TEXT NOT NULL,
processed_at TEXT
);$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_RIVO_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_RIVO_SIGNATURE'] ?? '';
$secret = getenv('RIVO_WEBHOOK_SECRET');
if (!$secret || !ctype_digit($timestamp) || abs(time() - (int)$timestamp) > 300) {
http_response_code(401);
exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true, 16, JSON_THROW_ON_ERROR);
$pdo = new PDO(getenv('DATABASE_DSN'));
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$insert = $pdo->prepare('INSERT INTO rivo_inbox(event_id, payload) VALUES (?, ?) ON CONFLICT(event_id) DO NOTHING');
$insert->execute([$event['event_id'], $raw]);
$pdo->commit();
http_response_code(204);Повторы используют одинаковые event_id и тело, но новый timestamp и подпись. Уникальность выдачи обеспечивайте по ID заказа, а не только по событию. Не переводите уже оплаченный заказ обратно в failed при запоздавшем событии.
Любой HTTP 2xx означает приём. При ошибке выполняется до 10 попыток с увеличением интервала от 1 минуты до 6 часов. Итог доставки виден в настройках интеграции; после исчерпания попыток доступна кнопка повтора. При удалении ключей старые заказы используют прежний webhook-секрет - сохраните его до окончания обработки старых заказов.
Ошибки и ограничения
{"error":"validation_error","message":"Некорректный customer_email."}| HTTP | Причина |
|---|---|
| 401 | Ключ не передан, удалён или неверен. |
| 403 | API выключен, магазин недоступен, включён тестовый режим или требуется верификация. |
| 404 | Заказ не найден в магазине ключа. |
| 409 | external_order_id использован с другими данными. |
| 413 / 415 | Тело больше 16 КиБ / неверный Content-Type. |
| 422 | Некорректные или неизвестные поля. |
| 429 | Лимит: 60 запросов в минуту на магазин, 120 на IP. Учитывайте Retry-After. |
| 500 | Внутренняя ошибка. Повторите запрос с прежним external_order_id. |
Настройка сервера
Предпросмотр формы в панели не создаёт заказ и не списывает деньги. Реальные API оплаты доступны при выключенном тестовом режиме магазина и включённых кассах. Кнопка оплаты создаёт только один счёт; повторное нажатие не выбирает другую кассу и не создаёт дубликат. При неопределённом ответе кассы сначала проверьте статус.
Очередь webhook обрабатывается штатным app/notification_worker.php. На сервере должен работать его cron раз в минуту. Если он уже настроен, второй обработчик добавлять не нужно. Для отдельного cron:
* * * * * /usr/bin/php /path/to/site/app/merchant_webhook_worker.phpТребуются PHP 8.1+, cURL, OpenSSL, mbstring, PDO SQLite и доверенные CA сертификаты. При резервном копировании сохраняйте базу вместе с storage/merchant-api.key: без этого файла нельзя расшифровать секреты старых webhook. API ключ хранится только как хеш; показать его повторно невозможно.
RivoDonate