Быстрый старт
Цель: провести реальный тестовый платёж end-to-end. К концу у вас будет сессия, созданная с вашего сервера, открытый checkout в браузере покупателя и подписанный webhook, доставленный на ваш localhost.
Вам нужна тестовая пара API-ключей (pk_test_… + sk_test_…) и
секрет для подписи webhook (whsec_…). Сгенерируйте их в
панели мерчанта в разделах
Developers → API keys и Developers → Webhooks.
1. Установите браузерный SDK
npm
npm install @lartech/infraio-checkout-js2. Создайте сессию checkout (сервер)
Сессии создаются с вашего сервера, подписываются HMAC-SHA256 вашим
secret-ключом. Никогда не помещайте sk_ в браузер.
Соберите каноническую строку подписи
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYДля запроса POST /b2b/v1/checkout-sessions/quick с unix-timestamp
1715990400 и телом {...}, входная строка подписи:
POST
/b2b/v1/checkout-sessions/quick
1715990400
{"items":[...],"currency":"USD","success_url":"..."}Подпишите её с помощью HMAC-SHA256(secret_key, signingString),
вывод hex.
Отправьте запрос
import { fetch } from "undici";
import { createHmac } from "node:crypto";
const PUBLIC_KEY = process.env.INFRAIO_PUBLIC_KEY!; // pk_test_…
const SECRET_KEY = process.env.INFRAIO_SECRET_KEY!; // sk_test_…
const BASE_URL = "https://api-dev.infraio.xyz";
const path = "/b2b/v1/checkout-sessions/quick";
const body = JSON.stringify({
items: [
{
sku: "tee-large-blue",
label: "Indigo Tee — L",
unit_price: "49.00",
quantity: 1,
currency: "USD",
image_url: "https://your-shop.test/img/tee.png",
},
],
currency: "USD",
customer_email: "[email protected]",
success_url: "https://your-shop.test/return?status=success",
cancel_url: "https://your-shop.test/return?status=cancel",
external_ref: "ord_1234", // ваш id заказа; хранится на заказе, в webhook'ах не отзеркаливается — ищите через order_id
expires_in: 1800, // секунды; по умолчанию 30 мин
idempotency_key: crypto.randomUUID(), // безопасно для повтора
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const signingInput = ["POST", path, timestamp, body].join("\n");
const signature = createHmac("sha256", SECRET_KEY)
.update(signingInput)
.digest("hex");
const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Client-ID": PUBLIC_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
},
body,
});
// Каждый ответ API обёрнут в конверт — реальный payload
// лежит под `data`. Считайте `code`/`message` на верхнем уровне
// маркером результата (`201` / `"created"` для успеха).
const { code, message, data } = await res.json() as {
code: number;
message: string;
data: { session_key: string; checkout_url: string; order_id: string; expires_at: string };
};
const { session_key, checkout_url, order_id, expires_at } = data;
// Отправьте session_key + checkout_url в браузер. order_id и expires_at
// для вашего собственного учёта.Расхождение часов важно. Gateway отклоняет запросы с X-Timestamp,
отстоящим от серверного времени более чем на 5 минут.
Синхронизируйте серверы через NTP; не доверяйте wall clock
долгоживущего cron.
3. Откройте checkout (браузер)
Popup
import { loadInfraIo } from "@lartech/infraio-checkout-js";
const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!);
sdk.checkout({
sessionId: session_key,
checkoutUrl: checkout_url,
mode: "popup", // по умолчанию
onReady: () => console.log("checkout interactive"),
onSuccess: ({ sessionId }) => {
// Только UX — см. предупреждение ниже. Webhook авторитетен.
location.assign("/thanks");
},
onCancel: () => console.log("buyer closed the popup"),
onError: (err) => console.error(err.code, err.message),
});4. Обработайте webhook (сервер)
Webhook — это единственный авторитетный сигнал того, что деньги
переместились. Браузерные callback’и (onSuccess) могут срабатывать
без реального платежа при некоторых тайминговых условиях на testnet’е
— никогда не обрабатывайте заказ по событию со стороны браузера.
import { createHmac, timingSafeEqual } from "node:crypto";
const WEBHOOK_SECRET = process.env.INFRAIO_WEBHOOK_SECRET!; // whsec_…
export async function POST(req: Request) {
const raw = await req.text(); // СЫРЫЕ байты, не распарсенные
const signature = req.headers.get("x-signature") ?? "";
const timestamp = req.headers.get("x-timestamp") ?? "";
// Подпись над `${timestamp}.${raw}` — НЕ над телом отдельно.
const expected = "sha256=" + createHmac("sha256", WEBHOOK_SECRET)
.update(`${timestamp}.${raw}`)
.digest("hex");
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return new Response("bad signature", { status: 400 });
}
// HTTP-тело ЕСТЬ объект данных конкретного события (без Stripe-стиля
// внешнего конверта). Тип события + delivery ID + timestamp живут в заголовках.
const eventType = req.headers.get("x-event") ?? ""; // напр. "payment.settled"
const deliveryId = req.headers.get("idempotency-key") // зеркалит X-Delivery
?? req.headers.get("x-delivery") ?? "";
const payload = JSON.parse(raw);
switch (eventType) {
case "payment.settled": {
// Payload несёт order_id, payment_intent_id, tx_hash,
// amount_received, confirmations, settlement-токен + сеть и т. д.
// Он НЕ включает ваш `external_ref` из заказа — ищите его
// на сервере через order_id, если нужен mapping назад.
const { order_id, tx_hash, amount_received, confirmations } = payload;
// Идемпотентно отметьте заказ оплаченным. Используйте deliveryId (X-Delivery)
// как ключ дедупликации — он стабилен между всеми повторами одной доставки.
// Мы повторяем до 6 раз (1м / 5м / 15м / 1ч / 6ч) на non-2xx.
break;
}
case "checkout.expired": { /* TTL сессии истёк — освободите инвентарь */ break; }
case "payment.refund.requested": { /* всплыть в очереди админа */ break; }
default:
// Forward-compat: мы можем добавлять события. Принимайте и no-op.
}
return new Response("ok", { status: 200 });
}См. Webhooks → Проверка подписи для крайних случаев (нормализация тела, ротация секрета, защита от replay).
5. Запустите тестовый платёж
Тестовые сессии рассчитываются против реальных testnet’ов (Sepolia, Base Sepolia, BSC Testnet и т. д.) — мок-сети нет. Возьмите тестовые средства из соответствующего крана, затем отправьте на депозитный адрес, который показывает страница checkout. Webhook сработает, когда подтверждения наберутся (1 confirmation на большинстве testnet’ов).
Что дальше
- Концепции → Сессии — модель данных трёх уровней (CheckoutSession + Order + PaymentIntent).
- Концепции → Сети и активы — какие сети и токены поддерживаются live vs test.
- Webhooks → Обзор — каждый тип события и что его запускает.
- Безопасность → API-ключи — scope’ы, ротация, rate-limit’ы.