HTTP API
HTTP API
Base URL: /api/v1/merchant/pay
Краткая сводка по endpoint'ам
| Метод | Endpoint | Назначение |
|---|---|---|
| POST | /available | Получение доступных сумм встречных предложений |
| POST | /new | Инициация нового платежа |
| POST | /new-async | Инициация нового платежа (с асинхронным получение реквизитов) |
| POST | /confirm | Подтверждение пользователем, что он совершил оплату |
| POST | /cancel | Отмена платежа |
| POST | /status | Получение текущего статуса платежа |
| POST | /receipt/attach | Прикрепление одного или нескольких чеков к платежу |
| Webhook POST | credentialsWebhookUrl | Webhook для предоставления реквизитов |
| Webhook POST | statusWebhookUrl | Webhook для уведомления об изменении статуса |
Заголовки безопасности и подписи
Все взаимодействия с API требуют использования криптографической подписи.
Запросы клиента к API
Каждый запрос должен содержать следующие заголовки:
X-KeyId— идентификатор ключа, полученный при создании мерчантаX-Signature— подпись тела запроса, полученная с помощью приватного ключа
Внимание: значения
X-KeyIdиX-Signature, используемые клиентом и сервером, всегда различаются, так как используются разные ключи.
Ответы сервера
Ответы сервера также подписываются:
- Заголовки
X-KeyIdиX-Signatureприсутствуют в ответе. - Подпись (
X-Signature) создается на основе тела ответа, закодирована в base64 и может быть проверена публичным ключом сервера.
Webhook-запросы от сервера
Webhook-запросы также подписываются нашим приватным ключом. Вы можете проверять их с помощью предоставленного нами публичного ключа.
Алгоритм и генерация ключей
- Алгоритм: ed25519
- Публичный ключ (в base64) передаётся на сервер вручную
- Не используйте
opensslдля генерации ed25519, т.к. он добавляет лишние метаданные - Рекомендуется использовать библиотеку соответствующего языка
Пример генерации пары ключей ed25519
package main
import (
"crypto/ed25519"
"encoding/base64"
"fmt"
)
func main() {
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
panic(err)
}
pubBase64 := base64.StdEncoding.EncodeToString([]byte(pub))
privBase64 := base64.StdEncoding.EncodeToString([]byte(priv))
fmt.Printf("Public key base64: %s\nPrivate key base64: %s", pubBase64, privBase64)
}
Пару ключей в base64 можно получить, запустив этот код на Go Playground.
Примеры формирования подписи
Go
import (
"crypto/ed25519"
"encoding/base64"
"os"
)
func signMessage(msg []byte, privateKeyPath string) (string, error) {
privKeyBytes, err := os.ReadFile(privateKeyPath)
if err != nil {
return "", err
}
priv := ed25519.PrivateKey(privKeyBytes)
signature := ed25519.Sign(priv, msg)
return base64.StdEncoding.EncodeToString(signature), nil
}
На Python
import base64
from nacl.signing import SigningKey
with open("private.key", "rb") as f:
sk = SigningKey(f.read())
message = b'{"example":"data"}'
signed = sk.sign(message)
signature = base64.b64encode(signed.signature).decode()
# signature прикрепляется как заголовок X-Signature
На JavaScript (Node.js)
const nacl = require('tweetnacl');
const bs58 = require('bs58');
const bs64 = require('base64-js');
const fs = require('fs');
const privateKey = fs.readFileSync('private.key');
const message = Buffer.from(JSON.stringify({ example: 'data' }));
const keyUint8 = new Uint8Array(privateKey);
const signature = nacl.sign.detached(message, keyUint8);
const signatureBase64 = bs64.fromByteArray(signature);
// signatureBase64 прикрепляется как заголовок X-Signature
На Javascript
<script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
function generateSignature(data, privateKeyBase64) {
if (typeof nacl === 'undefined') {
throw new Error('Библиотека tweetnacl не загружена. Добавьте её в Collection Scripts.');
}
console.log("Data for signing:", data);
console.log("Data type:", typeof data);
// ВАЖНО: В Go примере подписывается JSON как есть, БЕЗ сортировки!
// Если data - строка, используем её как есть
// Если data - объект, преобразуем в JSON строку
let jsonString;
if (typeof data === 'string') {
// Проверяем, валидный ли это JSON
try {
JSON.parse(data); // Только проверка
jsonString = data; // Используем как есть
} catch (e) {
// Если не JSON, преобразуем объект в строку
jsonString = JSON.stringify(data);
}
} else {
// Если это объект, преобразуем в JSON строку
jsonString = JSON.stringify(data);
}
console.log("Final JSON string:", jsonString);
// Декодируем приватный ключ
const keyUint8Array = nacl.util.decodeBase64(privateKeyBase64);
console.log("Private key length:", keyUint8Array.length);
// Подписываем строку как есть (без сортировки!)
const messageUint8Array = nacl.util.decodeUTF8(jsonString);
const signature = nacl.sign.detached(messageUint8Array, keyUint8Array);
const signatureBase64 = nacl.util.encodeBase64(signature);
console.log("Generated signature:", signatureBase64);
return signatureBase64;
}
Endpoints
POST /available
Описание: Возвращает список доступных сумм встречных предложений для заданных валюты и метода оплаты.
Content-Type: application/json
Пример запроса
{
"sum": 100.5,
"currency": "RUB",
"paymentMethod": "card", // "card" | "sbp". Обязательно.
"customerAccount": "string", // Опционально; сейчас не влияет на подбор сумм.
"sumDriftUp": 5, // Опционально. Допустимое отклонение суммы вверх.
"sumDriftDown": 5 // Опционально. Допустимое отклонение суммы вниз.
}
Успешный ответ
{
"success": true,
"availableAmounts": [100.5, 105.0, 110.0]
}
Ошибка
{
"success": false,
"errorDescription": "invalid currency"
}
При ошибке парсинга JSON или
sum <= 0ответ приходит в упрощённом виде{"error": "..."}(без полейsuccess/errorDescription).
POST /new
Описание: Инициирует новый платёж.
Content-Type: application/json
Пример запроса
{
"sum": 100.5,
"currency": "RUB", // Only "RUB".
"paymentMethod": "card", // "card" | "sbp".
"statusWebhookUrl": "https://merchant.com/status", // Опционально.
"customerAccount": "string",
"payerBank": "100000000111" // Опционально (см. примечание ниже). Код банка плательщика (отправителя)
}
- Опциональные поля:
statusWebhookUrl,payerBank. Остальные — обязательные. - Поле
payerBankможет быть обязательным по условиям работ.
Успешный ответ
{
"success": true,
"orderId": "abc123",
"sum": 100,
"payeeAccount": "1234567890",
"payeeAccountBank": "Bank",
"payeeName": "HolderName",
"currency": "RUB",
"dealSum": 90,
"dealCurrency": "RUB",
"dealRate": 0.9,
"deeplink": "https://deeplink.example.com/frame/abc123"
}
Ошибка
{
"success": false,
"errorDescription": "invalid currency"
}
POST /new-async
Описание: Инициирует новый платёж.
Content-Type: application/json
Пример запроса
{
"sum": 100.5,
"currency": "RUB", // Only "RUB".
"paymentMethod": "card", // "card" | "sbp".
"credentialsWebhookUrl": "https://merchant.com/credentials",
"statusWebhookUrl": "https://merchant.com/status", // Опционально.
"customerAccount": "string",
"payerBank": "100000000111" // Опционально (см. примечание ниже). Код банка плательщика (отправителя)
}
- Опциональные поля:
statusWebhookUrl,payerBank. Остальные — обязательные. - Поле
payerBankможет быть обязательным по условиям работ.
Успешный ответ
{
"success": true,
"orderId": "abc123"
}
Ошибка
{
"success": false,
"errorDescription": "invalid currency"
}
POST /confirm
Описание: Пользователь подтверждает, что совершил платеж. Инициирует проверку платежа со стороны нашей системы.
Content-Type: application/json
Пример запроса
{
"orderId": "abc123"
}
Успешный ответ
{
"success": true,
"orderId": "abc123"
}
Ошибка
{
"success": false,
"errorDescription": "invalid orderId"
}
POST /cancel
Описание: Отменяет ранее созданный платёж.
Ограничение: Отмена возможна только пока платёж ожидает подтверждения оплаты пользователем либо находится на стадии арбитража (включая
receiptRequired). В остальных случаях — в том числе для финальных статусовdoneSuccess/doneFail— сервер возвращает HTTP403сerrorDescription: "payment could not be canceled".
Content-Type: application/json
Пример запроса
{
"orderId": "abc123"
}
Успешный ответ
{
"success": true,
"orderId": "abc123"
}
Ошибка
{
"success": false,
"orderId": "abc123",
"errorDescription": "invalid orderId"
}
POST /status
Описание: Получение текущего статуса платежа по orderId. Возможные статусы:
Content-Type: application/json
Пример запроса
{
"orderId": "abc123"
}
Успешный ответ
{
"success": true,
"orderId": "abc123",
"sum": 100,
"payeeAccount": "1234567890",
"payeeAccountBank": "Bank",
"payeeName": "HolderName",
"dealSum": 90,
"dealCurrency": "RUB",
"dealRate": 0.9,
"deeplink": "https://deeplink.example.com/frame/abc123",
"status": "pending"
}
Примечание: поле
payeeNameв ответе/statusвозвращается транслитерированным в латиницу (например,Иванов→Ivanov).
Ошибка
{
"success": false,
"errorDescription": "order not found"
}
Возможные статусы status:
free- Заявка была успешна создана, но подходящие платёжные реквизиты пока не найдены.pending- Платеж находится в процессе обработки (реквизиты ищутся или ожидается оплата пользователем).receiptRequired- Для завершения платежа требуется прикрепить чек.doneSuccess- Платёж успешно завершён.doneFail- Платёж завершён с ошибкой.arbitrageCancelledByMerchant- Платёж отменён мерчантом на стадии арбитража (финальный статус).
Webhook: credentialsWebhookUrl (при использовании асинхронного метода)
Описание: Вызывается сервером для выдачи реквизитов.
Успешный запрос
{
"success": true,
"orderId": "abc123",
"sum": 100.5,
"payeeAccount": "1234567890",
"payeeAccountBank": "Bank",
"payeeName": "HolderName",
"currency": "RUB",
"dealSum": 90,
"dealCurrency": "RUB",
"dealRate": 0.9,
"deeplink": "https://deeplink.example.com/frame/abc123"
}
Ошибка
{
"success": false,
"orderId": "abc123"
}
Webhook: statusWebhookUrl (опционально)
Описание: Уведомление об новом статусе платежа.
Пример:
{
"success": true,
"orderId": "abc123",
"status": "doneSuccess",
"dealSum": 90,
"dealCurrency": "RUB",
"dealRate": 0.9,
"deeplink": "https://deeplink.example.com/frame/abc123"
}
Обратите внимание: через данный вебхук могут быть переданы только следующие статусы:
receiptRequired,doneSuccess,doneFail
POST /receipt/attach
Описание: Прикрепление чека.
Content-Type: multipart/form-data
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
orderId | string | да | Идентификатор заказа |
file | файлы | да | Чеки. Формат определяется по Content-Type: application/pdf, image/jpeg, image/jpg, image/png |
Пример с curl:
curl -X POST https://host/api/v1/merchant/pay/receipt/attach -F "orderId=abc123" -F "file=@receipt.pdf"
Успешный ответ
{
"success": true,
"orderId": "abc123"
}
Ошибка
{
"success": false,
"errorDescription": "invalid file format"
}
Currencies
const (
CurrencyRUB = "RUB"
)
var (
AvailableCurrencies = []string{CurrencyRUB}
)
Payment methods
const (
PaymentMethodCard = "card"
PaymentMethodSBP = "sbp"
)
var (
AvailablePaymentMethods = []string{PaymentMethodCard, PaymentMethodSBP}
)