Интеграция

Генерация ключей

Выполните эти две команды в терминале:

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ТипОбязательноОписание
AmountNumberОпциональноФиксированная сумма платежа. Если не указана, клиент может оплатить любую сумму.
CardTypeStringОбязательноВсегда FINIK_QR.
PaymentIdStringОбязательноУникальный ID платежа — предотвращает дублирование платежей.
RedirectUrlStringОбязательноКуда Finik отправляет клиента после успешной оплаты.
DataObjectОбязательноДетали платежа — см. ниже.

Data object

FieldТипОбязательноОписание
accountIdStringОбязательноID вашего счёта Finik — куда зачисляются средства.
name_enStringОбязательноИмя QR-кода, отображаемое клиенту.
webhookUrlStringОбязательноВаш серверный endpoint для уведомлений о статусе платежа.
descriptionStringОпциональноОписание на платёжной странице.
LangStringОпциональноЯзык платёжной страницы: `en`, `ru` или `ky`. Если не указан, используется `ru`.
startDateNumberОпциональноНачало срока действия QR (UNIX мс).
endDateNumberОпциональноКонец срока действия QR (UNIX мс).
additionalDataArrayОпциональноЕсли поле value не задано, клиент заполняет форму перед показом платежных методов. Максимальное количество полей — 20.

Многоязычные страницы

Платёжная страница поддерживает несколько языков. Передайте Lang в объекте Data со значением en, ru или ky, и платёжная страница откроется на этом языке.

additionalData items

FieldТипОбязательноОписание
fieldIdStringОбязательноКлюч этого поля в payload webhook.
nameStringОбязательноМетка поля на платёжной странице.
isHiddenBooleanОпциональноСкрыть поле на платёжной странице.
valueStringОпциональноПредзаполненное значение.

Пример тела запроса

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,
    "Lang": "ru"
  }
}

Обработка ответа

Успешный Create Payment возвращает 302 редирект с URL платёжной страницы в заголовке Location:

response
HTTP/1.1 302 Found
Location: https://qr.finik/<payment-path>
  1. Вызовите POST /v1/payment с бэкенда с отключёнными редиректами.
  2. Прочитайте Location — это URL вашей платёжной страницы.
  3. Отправьте этот URL клиенту — перенаправьте браузер на него или верните в ваше SPA / мобильное приложение для открытия в webview.
  4. Клиент завершает оплату и перенаправляется на ваш RedirectUrl.
  5. 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.

Ошибки

Каждая ошибка возвращается в виде JSON следующего вида:

error response
{
  "StatusCode": 400,
  "ErrorMessage": "..."
}

Ошибки валидации запроса

HTTP статусСообщение об ошибкеПричина
400 Bad RequestA payload must be provided.Тело запроса пустое.
400 Bad RequestAn unrecognizable payload is provided.Тело запроса не является валидным JSON.
400 Bad RequestInvalid payment ID is required.`PaymentId` отсутствует или не является строкой.
400 Bad RequestPayment ID must be less than or equal to 36 characters long.`PaymentId` превышает 36 символов.
400 Bad RequestAn invalid amount is provided. Amount must be greater than 0.`Amount` равен 0 или отрицателен.
400 Bad RequestInvalid IP address is provided.`CustomerIp` (или автоматически определённый IP источника) не является валидным IP-адресом.
400 Bad RequestCard type is required.`CardType` отсутствует.
400 Bad RequestAn unrecognizable card type is provided.`CardType` в настоящее время не поддерживается (поддерживается только `FINIK_QR`).
400 Bad RequestRedirectUrl is required.`CardType` равен `FINIK_QR`, а `RedirectUrl` отсутствует.
400 Bad RequestCurrency must be provided.`Currency` отсутствует, и значение по умолчанию не удалось определить.
400 Bad RequestAn invalid currency is provided.`Currency` не равен `KGS` или `RUB`.
400 Bad Request"FINIK_QR" payment method currently does not support {Currency} currency.Указанная `Currency` не поддерживается для Finik QR (поддерживается только `KGS`).

Ошибки платёжных данных

Применимы к полю Data тела запроса.

HTTP статусСообщение об ошибкеПричина
400 Bad RequestPayment data must be provided.`Data` отсутствует.
400 Bad RequestAn invalid payment data is provided.`Data` не удалось разобрать в объект.
400 Bad RequestAccount ID is required in the payment data.`Data.accountId` отсутствует.
400 Bad RequestItem name is required in the payment data.`Data.name_en` отсутствует.
400 Bad RequestStart Date invalid in the payment data.`Data.startDate` указан, но не является валидным таймстампом.
400 Bad RequestEnd Date invalid in the payment data.`Data.endDate` указан, но не является валидным таймстампом.
400 Bad RequestImages in payment request is invalid: {images}`Data.images` указан, но не является массивом.
400 Bad Requestbase64Image in payment request includes invalid base64Один из элементов Data.images не является валидным base64 data URI изображения (должен соответствовать data:image/<type>;base64,<data>).
400 Bad RequestUnable to create media filesЭквайер отклонил или завершил с ошибкой загрузку изображения.
400 Bad RequestadditionalData invalid in the payment data.`Data.additionalData` указан, но не является массивом.
400 Bad RequestadditionalData exceeds maximum allowed size of 20.Более 20 элементов в `Data.additionalData`.
400 Bad Request"fieldId" not provided in the payment data.В одном из элементов `additionalData` отсутствует `fieldId`.
400 Bad Request"name" not provided in the payment data.В одном из элементов `additionalData` отсутствует `name`.

Ошибки эквайера и сервера

HTTP статусСообщение об ошибкеПричина
502 Bad Gateway(передаётся от эквайера, либо "Unable to process the payment request.")
400 Bad Request(передаётся от эквайера, либо "Unable to process the payment request.")HTTP-вызов к эквайеру завершился ошибкой, и сам эквайер вернул HTTP статус 400. Сообщение передаётся так же, как описано выше.

Также при обработке запроса могут возникнуть следующие ошибки валидации:

ОшибкаСообщениеПричина
ValidationExceptionA maximum of 10 media files can be uploaded.Слишком много `mediaFiles`.
ValidationExceptionThe {media file id} is required.Отсутствует `id` элемента `mediaFiles`.
ValidationExceptionA duplicate ID in MediaFiles is provided.Дублирующийся `id` в `mediaFiles`.
ValidationExceptionMediaFiles field contains an id that does not exists.Неизвестный `id` медиафайла.
ValidationExceptionThe account.id is required.Отсутствует `account.id`.
ValidationExceptionAccount or parentId fields should be provided.Отсутствуют `account` и `parentId`.
ValidationExceptionThe name_en is required.Отсутствует `name_en`.
ValidationExceptionThe requestId is required.Отсутствует `requestId`.
ValidationExceptionThe fixedAmount must be a decimal number with no more than 2 digits after the decimal point.Некорректная точность `fixedAmount`.
ValidationExceptionThe fixedAmount must be a greater than or equal to 0.01.`fixedAmount` слишком мал.
ValidationException(сообщение определяется валидатором merchant category code)Некорректный `merchantCategoryCode`.
ValidationExceptionThe requiredField.fieldId is required.Отсутствует `fieldId` в `requiredFields`.
ValidationExceptionMissing values for fields: {missing}Не заполнены обязательные поля.
ValidationExceptionAccount is not provided.Account недоступен.
ValidationExceptionAccount status must be "Enabled"Account не активен.