Avans

Документация API

Приём платежей по СБП: один POST создаёт платёж и возвращает ссылку на оплату. Дальше — статусы, возвраты, вебхуки и коды ошибок. Здесь описано только то, что работает: ручек, которых нет, в этом тексте нет тоже.

Как получить ключ

Зарегистрируйте компанию, дождитесь одобрения заявки и создайте ключ в кабинете, раздел «Разработчикам». Тестовый ключ работает сразу и проводит платежи по всему пути, не двигая деньги.

Подключиться

Первый запрос

Адрес https://avans.pro, тело и ответы — JSON, ключ в заголовке Authorization. Примеры ниже можно выполнять как есть, подставив свой ключ.

Начало

Адрес, авторизация и режимы

Все запросы идут по HTTPS на https://avans.pro, тело и ответы — JSON. Ключ передаётся заголовком.

curl https://avans.pro/api/v1/balance \
  -H "Authorization: Bearer sk_live_..."

Ключ определяет и магазин, и режим. sk_test_… работает в тестовом контуре: платежи проходят весь путь и меняют статусы, но провайдер не задействован и деньги не двигаются. Ключи мерчант выпускает сам в кабинете, раздел «Разработчикам» → «Ключи»; секрет показывается один раз.

Секретный ключ — это доступ к деньгам магазина. Он рассчитан на серверную часть; в браузер, мобильное приложение и репозиторий его класть нельзя.

Ограничение — 300 запросов в минуту на ключ. При превышении приходит 429 rate_limited. Каждый вызов виден в кабинете, на вкладке «Логи».

Создать платёж

POST /api/v1/payments

POST/api/v1/payments
ПолеТипОписание
amountобяз.integerСумма в копейках. 1 500,50 ₽ — это 150050.
methodобяз.stringСпособ оплаты: sbp, crypto и прочие подключённые.
currencystringRUB, USD или EUR. По умолчанию RUB.
descriptionstringНазначение платежа, до 200 символов. Видно плательщику.
referencestringВаш номер заказа, до 64 символов. Возвращается как есть.
customer.namestringИмя покупателя.
customer.emailstringПочта: по ней покупатель опознаётся при повторных платежах.
customer.citystringГород, необязательно.
curl -X POST https://avans.pro/api/v1/payments \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1024" \
  -d '{
    "amount": 150050,
    "method": "sbp",
    "description": "Заказ 1024",
    "reference": "1024",
    "customer": { "name": "Иван", "email": "[email protected]" }
  }'

Ответ — 201:

{
  "id": "pay_6ihv8zfh0d33k6",
  "object": "payment",
  "amount": 150050,
  "currency": "RUB",
  "fee": 4351,
  "net": 145699,
  "status": "pending",
  "raw_status": "pending",
  "method": "sbp",
  "method_label": "СБП",
  "description": "Заказ 1024",
  "reference": "1024",
  "created": 1786542063,
  "captured": null,
  "confirmation_url": "https://avans.pro/sbp/pay_6ihv8zfh0d33k6",
  "test": false
}

confirmation_url — страница оплаты, куда нужно отправить покупателя. Платёж в этот момент ещё не оплачен: об оплате сообщит вебхук charge.succeeded.

fee и net — комиссия и сумма к зачислению, обе в копейках. Считаются по вашему тарифу в момент создания.

Повторы без двойных списаний

Заголовок Idempotency-Key

Если связь оборвалась и вы не знаете, дошёл ли запрос, повторите его с тем же Idempotency-Key — вернётся исходный платёж, второй не создастся.

Ключ выбираете вы: подойдёт номер заказа. Он действует в пределах вашего магазина и режима, поэтому один и тот же номер в тесте и в бою не столкнутся.

Заголовок необязателен, но при создании платежей его стоит слать всегда: без него повтор запроса — это второй платёж и второе списание с покупателя.

Один платёж

GET /api/v1/payments/{id}

GET/api/v1/payments/{id}

Самый частый запрос: «что с моим заказом». Принимает и наш идентификатор (pay_…), и ваш номер заказа из поля reference — хранить наш ради этого не обязательно. В ответе, кроме платежа, приходят его возвраты и поле matched_by: по чему нашли.

curl "https://avans.pro/api/v1/payments/pay_rhjdhf8kbw65ka"   -H "Authorization: Bearer sk_live_..."

curl "https://avans.pro/api/v1/payments/order-2841"   -H "Authorization: Bearer sk_live_..."

Список платежей

GET /api/v1/payments

GET/api/v1/payments
ПолеТипОписание
limitintegerСколько вернуть: от 1 до 100, по умолчанию 25.
starting_afterstringИдентификатор последнего платежа с прошлой страницы.
statusstringОтбор по статусу: succeeded, pending, failed, refunded, disputed. Принимает и внутренние значения из raw_status.
referencestringВаш номер заказа. Совпадение точное.
created_afterintegerТолько платежи позже этого времени. Секунды эпохи — как поле created в ответах.
created_beforeintegerТолько платежи раньше этого времени.
curl "https://avans.pro/api/v1/payments?limit=50" \
  -H "Authorization: Bearer sk_live_..."

Постранично — курсором, а не смещением: пока вы листаете, приходят новые платежи, и нумерация страниц смещалась бы под руками.

Возврат

POST /api/v1/refunds

POST/api/v1/refunds
ПолеТипОписание
paymentобяз.stringИдентификатор платежа (pay_…).
amountintegerСумма в копейках. Не указана — возврат полный. Частичный сейчас не поддерживается.
reasonstringПричина, до 200 символов. Попадёт в вашу отчётность.
Idempotency-KeyзаголовокПовтор с тем же ключом вернёт уже созданный возврат, а не сделает второй.
curl -X POST https://avans.pro/api/v1/refunds \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "payment": "pay_6ihv8zfh0d33k6", "amount": 50000 }'

Возврат только полный: подключённый провайдер не умеет возвращать часть суммы. Укажете amount меньше суммы платежа — придёт отказ, а не молчаливый возврат всего.

Возврат выполняется не мгновенно. Ответ означает, что заявка принята; деньги вернутся позже, и тогда платёж перейдёт в статус refunded. Отследить можно по статусу платежа или по вебхуку.

Список возвратов

GET /api/v1/refunds

GET/api/v1/refunds
ПолеТипОписание
limitintegerОт 1 до 100, по умолчанию 25.
starting_afterstringИдентификатор последнего возврата с прошлой страницы.
paymentstringТолько возвраты по этому платежу.
curl "https://avans.pro/api/v1/refunds?payment=pay_rhjdhf8kbw65ka"   -H "Authorization: Bearer sk_live_..."

История событий

GET /api/v1/events

GET/api/v1/events

То, что мы вам присылали. Нужен, если приёмник вебхуков полежал: вместо опроса платежей по одному заберите пропущенное отсюда. Тело события — ровно то же, что уходило на эндпоинт, разбор писать заново не придётся.

ПолеТипОписание
limitintegerОт 1 до 100, по умолчанию 25.
typestringТолько события этого типа, например charge.succeeded.
sinceintegerВремя в секундах эпохи — как поле created в ответах. Вернутся события позже него.
curl "https://avans.pro/api/v1/events?since=1755200000&type=charge.succeeded"   -H "Authorization: Bearer sk_live_..."

Рядом с событием приходит поле delivery: статус доставки, число попыток и код ответа вашего сервера — видно, дошло ли оно и почему нет.

Баланс

GET /api/v1/balance

GET/api/v1/balance
{
  "object": "balance",
  "livemode": true,
  "available": [{ "currency": "RUB", "amount": 1187550 }],
  "pending":   [{ "currency": "RUB", "amount": 250000 }],
  "reserved":  [{ "currency": "RUB", "amount": 0 }]
}

Всё в копейках. available — доступно к выплате, pending — принято, но ещё не разблокировано, reserved — удержано под споры.

Вебхуки

Уведомления о событиях

Адрес задаётся на вкладке «Вебхуки». Запрос приходит методом POST с телом события и тремя заголовками:

ПолеТипОписание
Avans-SignaturestringПодпись тела. Формат: t=<время>,v1=<подпись>.
Avans-EventstringТип события.
Avans-DeliverystringИдентификатор доставки: по нему отличают повтор.

События:

ПолеТипОписание
charge.succeededсобытиеПлатёж оплачен. Именно по нему отгружайте заказ.
charge.failedсобытиеПлатёж не прошёл, отменён или просрочен — заказ можно закрывать.
charge.refundedсобытиеОформлен возврат.
charge.disputedсобытиеОткрыт спор: сумма заморожена, нужны документы.
charge.chargebackсобытиеБанк вернул деньги плательщику окончательно, сумма списана.
payout.paidсобытиеВыплата отправлена.
payout.failedсобытиеВыплата не прошла.
balance.updatedсобытиеБаланс изменился.

Подпись считается по строке <время>.<тело как есть> алгоритмом HMAC-SHA256 на секрете эндпоинта:

const [t, v1] = header.split(",").map((p) => p.split("=")[1]);

const expected = crypto
  .createHmac("sha256", endpointSecret)
  .update(`${t}.${rawBody}`)
  .digest("hex");

// Сравнение постоянного времени: обычное сравнение строк
// позволяет подбирать подпись побайтно по времени ответа.
const valid = crypto.timingSafeEqual(
  Buffer.from(expected),
  Buffer.from(v1)
);

Подпись считается по СЫРОМУ телу запроса. Если разобрать JSON и собрать обратно, порядок полей и пробелы изменятся, и подпись не сойдётся — читайте тело до разбора.

Отвечайте 200 как можно быстрее, а работу выполняйте после. Любой другой ответ или таймаут считается неудачей, и доставка повторяется с возрастающими паузами. Повтор приходит с тем же Avans-Delivery — по нему отличайте повтор от нового события.

Ошибки

Формат и коды

{
  "error": {
    "code": "invalid_request",
    "message": "Укажите идентификатор платежа"
  }
}
ПолеТипОписание
invalid_request400Неверные или недостающие поля.
unauthorized401Ключ не передан, неверен или отозван.
forbidden403Действие недоступно этому ключу.
account_under_review403Магазин на проверке: приём временно остановлен.
not_found404Объект не найден.
conflict409Действие противоречит текущему состоянию.
rate_limited429Превышены 300 запросов в минуту. В заголовке Retry-After — через сколько секунд повторять.
provider_unavailable503Провайдер недоступен. Повторите позже.
internal_error500Ошибка на нашей стороне.

Сообщение в message написано для человека и может меняться. Ветвите логику по code.

Остались вопросы по интеграции

Напишите на [email protected] — отвечает человек, который писал это API, а не автоответчик.