Payment Docs
Примеры кода

Node.js

Минимальный сервер на Express, который создаёт заказы и проверяет webhook-уведомления

Минимальный сервер на Express с двумя маршрутами: один создаёт заказ и перенаправляет плательщика, другой принимает webhook-уведомления и проверяет заголовок X-Signature.

npm install express
server.js
import 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 означает, что деньги поступили.

На этой странице