Платіжна сторінка за 15 хвилин
End-to-end інтеграція з платіжною сторінкою RozetkaPay — від запиту на оплату до обробки callback зі статусом платежу
Приклад End-to-end інтеграції з платіжною сторінкою RozetkaPay — від запиту на оплату до обробки callback зі статусом платежу.
Кроки здійснення оплати
- Отримати URL платіжної сторінки — створити платіж через RozetkaPay
- Редірект покупця на платіжну сторінку — покупець оплачує на стороні RozetkaPay
- Обробка callback — отримати callback, верифікувати підпис, оновити статус замовлення
- Відображення статусу оплати покупцю
sequenceDiagram
participant C as Клієнт
participant S as Ваш сервер
participant RP as RozetkaPay
C->>S: POST /checkout
S->>RP: POST /api/payments/v1/new
RP-->>S: { action.value: hosted URL }
S-->>C: 302 Редірект → hosted URL
C->>RP: Оплата на сторінці RozetkaPay
RP-->>C: Редірект → result_url
RP->>S: POST /rozetkapay/callback (async)
S->>S: Перевірка підпису, оновлення замовлення
S-->>RP: 200 OK
C->>S: GET /order/:id/result
S-->>C: Статус платежу
Крок 1. Створення платежу
// server.jsimport express from "express";const app = express();app.use(express.json());
const RP_LOGIN = process.env.RP_LOGIN;const RP_PASSWORD = process.env.RP_PASSWORD;const BASE_URL = "https://api.rozetkapay.com";const PUBLIC_URL = process.env.PUBLIC_URL; // напр. https://your-site.com
app.post("/checkout", async (req, res) => { const externalId = `order_${Date.now()}`; const auth = Buffer.from(`${RP_LOGIN}:${RP_PASSWORD}`).toString("base64");
const r = await fetch(`${BASE_URL}/api/payments/v1/new`, { method: "POST", headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json", }, body: JSON.stringify({ external_id: externalId, mode: "hosted", amount: req.body.amount, currency: "UAH", confirm: true, callback_url: `${PUBLIC_URL}/rozetkapay/callback`, result_url: `${PUBLIC_URL}/order/${externalId}/result`, customer: { email: req.body.email }, }), });
const data = await r.json(); if (!data.is_success) return res.status(400).json(data);
// збережіть external_id ↔ payment_id на своєму боці (БД) await orders.create({ external_id: externalId, payment_id: data.id, status: "init" });
res.redirect(302, data.action.value);});Крок 2. Обробка Callback
RozetkaPay POST-ить JSON на callback_url після завершення оплати. Підпис у заголовку X-ROZETKAPAY-SIGNATURE розраховується за формулою:
base64url(sha1(password + base64url(body) + password))Повна специфікація — у Callbacks / Webhooks.
import crypto from "node:crypto";
function b64url(buf) { return Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_");}
function verifySignature(password, rawBody, signature) { const body64 = b64url(rawBody); const hash = crypto .createHash("sha1") .update(password + body64 + password) .digest(); return b64url(hash) === signature;}
// ВАЖЛИВО: потрібен raw body, а не розпарсений JSONapp.post("/rozetkapay/callback", express.raw({ type: "application/json" }), async (req, res) => { const signature = req.header("X-ROZETKAPAY-SIGNATURE"); const raw = req.body.toString("utf8");
if (!verifySignature(RP_PASSWORD, raw, signature)) { return res.status(401).send("invalid signature"); }
const payload = JSON.parse(raw); const { external_id, status, payment_id } = payload;
// дедуплікація: якщо status вже "success" — пропускаємо const order = await orders.findByExternalId(external_id); if (order?.status === status) return res.status(200).send("ok");
await orders.update(external_id, { status, payment_id });
// відповідаємо 200 швидко, важку роботу робіть асинхронно res.status(200).send("ok");});Крок 3. Result page
app.get("/order/:id/result", async (req, res) => { const order = await orders.findByExternalId(req.params.id);
// callback може ще не прийти — покажіть pending і робіть polling на клієнті if (!order || order.status === "init" || order.status === "pending") { return res.send("<h1>Платіж обробляється…</h1>"); } if (order.status === "success") { return res.send("<h1>Дякуємо! Оплата пройшла</h1>"); } res.send("<h1>Оплата не вдалась</h1>");});Тестування локально
callback_url має бути публічно доступний. На localhost підніміть тунель:
# ngrokngrok http 3000# → https://abc123.ngrok-free.app
export PUBLIC_URL=https://abc123.ngrok-free.appПотім оплатіть тестовою карткою, наприклад 4444555566661111 (3DS success).
Чеклист перед запуском
- Ключі API у змінних середовища, не в коді
-
callback_urlвикористовує HTTPS - Підпис callback верифікується до парсингу JSON
-
external_idунікальний і зберігається у базі даних - Дедуплікація callback: один
payment_id→ одна бізнес-дія - Повторні спроби на
5xx— з тим самимexternal_id -
BASE_URLпереключено наhttps://api.rozetkapay.com/ - Отримано реальні ключі API
Далі
- Платіжні методи — direct, токени, гаманець
- Двостадійна оплата — блокування + confirm
- Обробка помилок — структура error response