Интеграция
Генерация ключей
Выполните эти две команды в терминале:
terminal
openssl genrsa -out finik_private.pem 2048
openssl rsa -in finik_private.pem -pubout > finik_public.pem| Файл | Назначение |
|---|---|
finik_private.pem | Подписывайте каждый запрос этим ключом. Храните в секрете — любой, кто его получит, сможет отправлять запросы от вашего имени. |
finik_public.pem | Отправьте в Finik по защищённому каналу (email Finik), чтобы сервис мог проверять ваши запросы. |
Подпись запросов
Каждый запрос требует заголовок signature — RSA-SHA256, Base64-кодированный, сгенерированный с вашим приватным ключом. Finik предоставляет готовые пакеты для Node.js и Python.
Установите @mancho.devs/authorizer и node-fetch, затем:
create-payment.ts
import { Signer, RequestData } from '@mancho.devs/authorizer';
import fetch from 'node-fetch'; // or axios
// Choose the environment you call:
const baseUrl = 'https://api.acquiring.averspay.kg'; // prod
// const baseUrl = 'https://beta.api.acquiring.averspay.kg'; // beta
// IMPORTANT: Host header must match the URL host exactly.
const host = new URL(baseUrl).host;
// Your credentials
const apiKey = process.env.FINIK_API_KEY!; // from Finik
const privateKey = process.env.FINIK_PRIVATE_PEM!; // contents of finik_private.pem
const timestamp = Date.now().toString(); // UNIX ms
// Create Payment body per spec
const body = {
Amount: 100,
CardType: 'FINIK_QR',
PaymentId: '00000000-0000-0000-0000-000000000000', // use a real UUID
RedirectUrl: 'https://example.com/success',
Data: {
accountId: 'your-account-id',
name_en: 'your-qr-name',
},
};
// Build the canonical input for signing
const requestData: RequestData = {
httpMethod: 'POST',
path: '/v1/payment', // absolute path only (no query)
headers: {
Host: host, // must match baseUrl host
'x-api-key': apiKey, // included in signature
'x-api-timestamp': timestamp, // UNIX ms; same value used in signature
// You may send other headers, but only `host` and `x-api-*` are included in the signature.
},
queryStringParameters: undefined, // or { ... } if you have query; lib sorts & encodes
body, // plain JS object; lib will canonicalize/JSON-stringify
};
// Produce Base64 RSA-SHA256 signature
const signature = await new Signer(requestData).sign(privateKey);
// Send the actual HTTP request
const url = `${baseUrl}${requestData.path}`;
const res = await fetch(url, {
method: requestData.httpMethod,
headers: {
'content-type': 'application/json',
'x-api-key': apiKey,
'x-api-timestamp': timestamp,
signature, // <- attach signature header
},
body: JSON.stringify(body),
// If your API currently returns 302 (HTML) on S2S calls, prevent auto-follow:
redirect: 'manual', // remove once API returns 201 JSON by default
});
if (res.status === 302) {
// Read Location when you opt into redirects
console.log('Redirect to:', res.headers.get('location'));
} else {
console.error(res.status, await res.text());
}Только Host и заголовки, начинающиеся с x-api-, включаются в подпись. Одно и то же значение временно́й метки должно использоваться и в подписи, и в заголовке запроса.
Создание платежа
POST подписанный запрос на:
API endpoints
POST https://api.acquiring.averspay.kg/v1/payment — Прод
POST https://beta.api.acquiring.averspay.kg/v1/payment — БетаЗаголовки
| Header | Описание |
|---|---|
signature | Подпись, которую вы сгенерировали. |
x-api-key | Ваш ключ API от Finik. |
x-api-timestamp | Текущее время в миллисекундах UNIX — то же значение, что использовалось при подписи. |
Тело запроса
| Field | Тип | Обязательно | Описание |
|---|---|---|---|
Amount | Number | Опционально | Фиксированная сумма платежа. Если не указана, клиент может оплатить любую сумму. |
CardType | String | Обязательно | Всегда FINIK_QR. |
PaymentId | String | Обязательно | Уникальный ID платежа — предотвращает дублирование платежей. |
RedirectUrl | String | Обязательно | Куда Finik отправляет клиента после успешной оплаты. |
Data | Object | Обязательно | Детали платежа — см. ниже. |
Data object
| Field | Тип | Обязательно | Описание |
|---|---|---|---|
accountId | String | Обязательно | ID вашего счёта Finik — куда зачисляются средства. |
name_en | String | Обязательно | Имя QR-кода, отображаемое клиенту. |
webhookUrl | String | Обязательно | Ваш серверный endpoint для уведомлений о статусе платежа. |
description | String | Опционально | Описание на платёжной странице. |
startDate | Number | Опционально | Начало срока действия QR (UNIX мс). |
endDate | Number | Опционально | Конец срока действия QR (UNIX мс). |
additionalData | Array | Опционально | Если поле value не задано, клиент заполняет форму перед показом платежных методов. Максимальное количество полей — 20. |
additionalData items
| Field | Тип | Обязательно | Описание |
|---|---|---|---|
fieldId | String | Обязательно | Ключ этого поля в payload webhook. |
name | String | Обязательно | Метка поля на платёжной странице. |
isHidden | Boolean | Опционально | Скрыть поле на платёжной странице. |
value | String | Опционально | Предзаполненное значение. |
Пример тела запроса
POST /v1/payment — body
{
"Amount": 100,
"CardType": "FINIK_QR",
"PaymentId": "a3f1c2e4-7b9d-4e2a-8c1f-3d0e9b2a5f6c",
"RedirectUrl": "https://example.com/success",
"Data": {
"accountId": "your-account-id",
"name_en": "your-qr-name",
"webhookUrl": "https://merchant.example.com/webhooks/finik",
"description": "your-qr-description",
"startDate": 1737369000000,
"endDate": 1737455400000
}
}Обработка ответа
Успешный Create Payment возвращает 302 редирект с URL платёжной страницы в заголовке Location:
response
HTTP/1.1 302 Found
Location: https://qr.finik/<payment-path>- Вызовите POST /v1/payment с бэкенда с отключёнными редиректами.
- Прочитайте
Location— это URL вашей платёжной страницы. - Отправьте этот URL клиенту — перенаправьте браузер на него или верните в ваше SPA / мобильное приложение для открытия в webview.
- Клиент завершает оплату и перенаправляется на ваш
RedirectUrl. - Finik отправляет webhook с итоговым статусом — считайте его источником истины.
Отключите авто-переход и читайте заголовок Location
fetch.js
const res = await fetch("https://api.acquiring.averspay.kg/v1/payment", {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": apiKey, "x-api-timestamp": ts, signature },
body: JSON.stringify(body),
redirect: "manual", // don't auto-follow
});
if (res.status === 302) {
const paymentUrl = res.headers.get("location"); // send this to the browser
}Бэкенд может перенаправить браузер (302 Location: <paymentUrl>) или вернуть JSON в ваше SPA и установить window.location = paymentUrl.