Примеры кода
Node.js
Минимальный сервер на Express, который создаёт заказы и проверяет webhook-уведомления
Минимальный сервер на Express с двумя маршрутами: один создаёт заказ и перенаправляет плательщика, другой принимает webhook-уведомления и проверяет заголовок X-Signature.
npm install expressimport express from "express";
import crypto from "node:crypto";
const API_URL = "https://api.riopay.online/v1";
const API_TOKEN = process.env.API_TOKEN; // выдаётся вашим менеджером
const app = express();
// Сохраняем сырое тело запроса: подпись считается от тех же байтов, что мы получили
app.use(express.json({ verify: (req, _res, buf) => (req.rawBody = buf) }));
// 1. Создаём заказ и перенаправляем плательщика на платёжную страницу
app.post("/create-order", async (req, res) => {
const { amount, orderId, userId } = req.body;
const response = await fetch(`${API_URL}/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Token": API_TOKEN,
},
body: JSON.stringify({
amount: String(amount),
externalId: orderId,
externalUserId: userId,
purpose: `Payment for order ${orderId}`,
successUrl: "https://your-site.com/payment-success",
failUrl: "https://your-site.com/payment-failed",
callbackUrl: "https://your-site.com/webhooks/payments",
}),
});
if (!response.ok) {
return res.status(response.status).send(await response.text());
}
const order = await response.json();
// Сохраните order.id рядом со своим orderId, чтобы потом сопоставлять webhook-уведомления
res.redirect(order.paymentLink);
});
// 2. Принимаем webhook-уведомления
app.post("/webhooks/payments", (req, res) => {
const expected = crypto
.createHmac("sha512", API_TOKEN)
.update(req.rawBody)
.digest("hex");
if (expected !== req.headers["x-signature"]) {
return res.status(403).send("Invalid signature");
}
const { status, externalId } = req.body;
// Webhook-уведомления могут приходить повторно: обработка должна быть идемпотентной
if (status === "COMPLETED") {
console.log(`Заказ ${externalId} оплачен`);
// отметьте заказ как оплаченный в своей базе данных
} else if (["REFUND", "CHARGEBACK"].includes(status)) {
console.log(`Заказ ${externalId} отменён: ${status}`);
// деньги вернулись плательщику: отметьте заказ как возвращённый
} else if (["FAILED", "CANCELED", "EXPIRED", "BLOCKED"].includes(status)) {
console.log(`Заказ ${externalId} завершён со статусом ${status}`);
// позже может стать COMPLETED, если банк подтвердит платёж
}
res.status(200).send("OK");
});
app.listen(3000, () => console.log("Сервер слушает :3000"));Что адаптировать
- Замените жёстко заданные URL на свои адреса успеха, ошибки и webhook.
- Сохраняйте
idиз ответа на создание заказа вместе со своимexternalId. - Перенесите обработку статусов в свою модель заказа; только
COMPLETEDозначает, что деньги поступили.