Дарим сниженную ставку пожизненно при регистрации
Документация

API для мерчантов

Всё, что нужно для интеграции: как подписывать запросы, какие ручки есть и что означают ответы. Приём платежей по СБП и банковским картам.

ПараметрЗначение
Базовый адресhttps://api.e00q.micropenis.online
Префикс путей/api/v1/
ФорматJSON, Content-Type: application/json; charset=utf-8

Быстрый старт

1. Получите API-ключ

Ключ выдаётся при подключении и выглядит как ak_<идентификатор>_<секрет>. Секретную часть не публикуйте: подпись подделает любой, у кого есть ключ целиком. Ключ заводится в личном кабинете.

2. Проверьте связь

Единственная ручка без подписи:

bash
curl -s https://api.e00q.micropenis.online/api/v1/ping
{"message":"pong"}

3. Отправьте первый подписанный запрос

bash
API_KEY='ak_демо_секрет'
BODY='{"merchant_order_id":"order-1","subject":"Заказ 1","amount":{"amount_minor":"10000","currency":"RUB"}}'

SIG=$(printf '%s' "$BODY" | base64 | tr -d '\n' \
  | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $2}')
TS=$(python3 -c 'import time; print(int(time.time()*1000))')
NONCE=$(python3 -c 'import uuid; print(uuid.uuid4())')

curl -s -X POST https://api.e00q.micropenis.online/api/v1/payments \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $API_KEY" -H "X-Signature: $SIG" \
  -H "X-Timestamp: $TS" -H "X-Nonce: $NONCE" \
  -d "$BODY"

Аутентификация

Токенов нет: каждый запрос несёт ключ и подпись тела. Четыре заголовка, все обязательные.

ЗаголовокСодержимое
X-Api-KeyКлюч целиком, включая секретную часть
X-SignatureПодпись тела, нижний регистр hex
X-TimestampВремя отправки, Unix-миллисекунды
X-NonceУникальное значение на каждый запрос (подойдёт UUID)

Как считается подпись

  1. Тело сериализуется в компактный JSON — без отступов и переводов строк.
  2. Компактный JSON кодируется в base64. Шаг обязателен: он выравнивает поведение разных языков на не-ASCII символах.
  3. Считается HMAC-SHA256 от base64-строки на ключе; результат — hex в нижнем регистре.
signature = lower(hex(HMAC_SHA256(base64(compact_json(body)), api_key)))

Запросы без тела (GET) подписываются по пустой строке.

Примеры

go
payload := base64.StdEncoding.EncodeToString(compactJSON)
mac := hmac.New(sha256.New, []byte(apiKey))
mac.Write([]byte(payload))
signature := hex.EncodeToString(mac.Sum(nil))
python
payload = base64.b64encode(compact_json.encode()).decode()
signature = hmac.new(api_key.encode(), payload.encode(), hashlib.sha256).hexdigest()
javascript
const payload = Buffer.from(compactJson).toString("base64");
const signature = crypto.createHmac("sha256", apiKey).update(payload).digest("hex");
php
$payload = base64_encode($compactJson);
$signature = hash_hmac('sha256', $payload, $apiKey);

Окно времени и повторы

X-Timestamp должен попадать в окно в несколько минут вокруг текущего времени — синхронизируйте часы по NTP. X-Nonce должен быть уникален: повтор ранее использованного значения отклоняется, даже если подпись верна. Так перехваченный запрос нельзя отправить второй раз.

Права ключа

Подпись отвечает на вопрос «кто пришёл», права — «что этому ключу можно». У каждой ручки своё право; они выдаются при подключении.

ПравоЧто открывает
payments:writeСоздание платежей
payments:readЧтение платежей
balances:readОстаток счёта
operations:readЛента операций

Ручка ответила 403 — запросите недостающее право у менеджера. Какого именно права не хватает, ответ не сообщает.

Формат ответа

Успех — данные всегда в поле data:

{"data": {"payment_uuid": "0192f7a1-8c3d-7c2b-9f4e-1a2b3c4d5e6f"}}

Списки дополняются meta:

{"data": [], "meta": {"next_page_token": ""}}

Пустой список приходит как [], а не null — можно обходить без проверки.

Ошибка — всегда одно поле error с текстом, иногда с разбором по полям:

{"error": "Unauthorized"}

{"error": "Validation failed", "errors": {"amount": ["required"]}}

Корреляция

В каждом ответе есть X-Request-ID. Свой идентификатор можно прислать в одноимённом заголовке запроса — принимается значение в формате UUID, иначе выдаётся своё. Логируйте его: это единственное, что нужно поддержке для поиска конкретного запроса.

Коды состояния

КодЧто означаетЧто делать
200Успех—
401Не аутентифицированПроверьте ключ, подпись, время и nonce
403Ключу не хватает права либо мерчант не активированОбратитесь к менеджеру
404Ручки или объекта не существуетПроверьте путь и идентификатор
405Метод не поддержанСмотрите заголовок Allow в ответе
409Заказ с таким идентификатором уже есть с другими параметрамиИспользуйте новый merchant_order_id
413Тело запроса слишком большоеУменьшите объём данных
422Запрос не прошёл проверкуИсправьте поля, указанные в errors
429Слишком частоДождитесь Retry-After
503, 504Временная недоступностьПовторите позже, запрос не выполнен

401 одинаков для всех причин — неверный ключ, неверная подпись, просроченное время и повтор nonce дают один и тот же ответ. Иначе по разнице ответов подбирались бы значения.

503 и 504 не означают отказ в доступе: ключ в порядке, запрос просто не выполнен — повторите его позже.

Ограничение частоты

Частота ограничена по адресу и по ключу. Превышение даёт 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 42

{"error":"Too many requests"}

Retry-After — точное число секунд до момента, когда освободится место, а не фиксированная пауза. Не повторяйте запрос раньше: повторы внутри этого времени только продлевают ограничение. Остаток лимита в ответе не сообщается.

Платежи

POST /api/v1/payments — создать платёж

Требует права payments:write. Повтор запроса с тем же merchant_order_id возвращает существующий платёж, а не создаёт второй. Отдельного заголовка идемпотентности нет — ключом служит сам идентификатор заказа.

запрос
{
  "merchant_order_id": "order-42",
  "subject": "Подписка на месяц",
  "amount": {"amount_minor": "10000", "currency": "RUB"},
  "payment_method": "PAYMENT_METHOD_SBP",
  "payer": {"id": "payer-7", "name": "Иван"},
  "payload": "любая ваша строка"
}
ПолеОбязательноОписание
merchant_order_idдаВаш идентификатор заказа. Уникален в пределах мерчанта, он же ключ идемпотентности
subjectнетНазначение платежа, видит плательщик
amountдаСумма в минимальных единицах валюты
payment_methodнетСпособ оплаты; не указан — берётся ваш способ по умолчанию
payerнетПлательщик: id и name. Нужен антифроду; стабильный id между платежами одного человека улучшает проходимость
payloadнетСквозная строка, возвращается как есть
ответ
{
  "data": {
    "payment_uuid": "0192f7a1-8c3d-7c2b-9f4e-1a2b3c4d5e6f",
    "merchant_order_id": "order-42",
    "subject": "Подписка на месяц",
    "amounts": {
      "amount":     {"amount_minor": "10000", "currency": "RUB"},
      "charged":    {"amount_minor": "10500", "currency": "RUB"},
      "commission": {"amount_minor": "500",   "currency": "RUB"},
      "amount_rub": {"amount_minor": "10500", "currency": "RUB"},
      "quote_id": "",
      "rate": "1.0"
    },
    "commission_payer": "COMMISSION_PAYER_CLIENT",
    "payment_method": "PAYMENT_METHOD_SBP",
    "status": "PAYMENT_STATUS_PENDING",
    "external_id": "",
    "payment_url": "https://pay.e00q.example/0192f7a1",
    "created_at": "2026-08-25T09:00:00Z",
    "expires_at": "2026-08-25T10:00:00Z",
    "paid_at": "",
    "payload": "любая ваша строка"
  }
}

payment_url — ссылка, которую вы показываете плательщику. По ней открывается страница оплаты: QR СБП или форма карты.

GET /api/v1/payments/{payment_uuid} — платёж по идентификатору

Требует права payments:read. Чужой платёж отвечает 404: другой код подтвердил бы, что такой платёж существует.

GET /api/v1/payments — список платежей

ПараметрОписание
page_sizeРазмер страницы. Не указан — размер по умолчанию
page_tokenКурсор следующей страницы из meta.next_page_token
statusФильтр по исходу платежа
period_start, period_endПериод создания, полуинтервал [начало, конец), RFC 3339

Листание идёт курсором, а не номером страницы: список пополняется во время обхода, и нумерация показала бы одну запись дважды, а соседнюю пропустила. Пустой next_page_token — страница последняя. Содержимое курсора непрозрачно, не разбирайте его.

Счёт и операции

GET /api/v1/balances — остаток

Требует права balances:read. Счёт определяется вашим ключом: назвать чужой счёт в параметрах нельзя и не нужно.

{
  "data": {
    "amount": {"amount_minor": "125000", "currency": "USD"},
    "projection_lag_seconds": "0",
    "synced_at": ""
  }
}

Остаток может быть отрицательным — это задолженность, а не ошибка. Не приводите знак к нулю на своей стороне.

GET /api/v1/operations — лента операций

Требует права operations:read. Параметры те же, что у списка платежей.

{
  "data": [
    {
      "operation_uuid": "0192f7a1-8c3d-7c2b-9f4e-1a2b3c4d5e6f",
      "type": "payment",
      "amount": {"amount_minor": "25000", "currency": "RUB"},
      "payment_uuid": "0192f7a1-8c3d-7c2b-9f4e-1a2b3c4d5e6f",
      "created_at": "2026-08-25T07:36:12Z"
    }
  ],
  "meta": {"next_page_token": ""}
}

Важно, чтобы не потерять время:

  • лента показывает те же платежи, что и GET /api/v1/payments, только в форме операций;
  • свести по ней остаток нельзя — удержаний комиссии и выплат в ней нет; остаток берите из /api/v1/balances;
  • operation_uuid равен payment_uuid — отдельной сущности нет, второй идентификатор хранить не нужно;
  • amount — сумма платежа, а не сумма зачисления: они расходятся на комиссию, когда её платите вы.

Курсы валют

GET /api/v1/rates — витрина спот-курсов. Требует подписи, отдельного права не требует.

{
  "data": [
    {"from_currency": "USDT", "to_currency": "RUB", "rate": "78.50",
     "fetched_at": "2026-08-25T09:00:00Z"}
  ]
}

Курс — десятичная строка, а не число: округление курса это потерянные деньги. Считайте на своей стороне в decimal-типе.

Словари

Деньги

{"amount_minor": "10000", "currency": "RUB"}

amount_minor — сумма в минимальных единицах валюты (копейки, центы), строкой: JSON-число потеряло бы точность на больших значениях. 10000 при RUB — это 100 рублей 00 копеек.

Суммы платежа

ПолеЧто это
amountСумма, которую запросили вы
chargedСумма, предъявленная плательщику: amount плюс комиссия, если её платит плательщик
commissionКомиссия платформы по этому платежу
amount_rubСумма в рублях. Пока quote_id пуст — справочная величина по текущему курсу
rateПрименённый курс, десятичная строка

Статусы платежа

ЗначениеЧто означает
PAYMENT_STATUS_PENDINGСоздан, ждёт действия плательщика
PAYMENT_STATUS_CONFIRMEDДеньги получены
PAYMENT_STATUS_CANCELEDНе состоялся: не оплачен, истёк срок или отклонён
PAYMENT_STATUS_CHARGEBACKEDРанее подтверждённые деньги списаны обратно

Повторная оплата — это новый платёж с новым merchant_order_id, а не возврат прежнего в PENDING.

Способы оплаты

ЗначениеЧто означает
PAYMENT_METHOD_UNSPECIFIEDВзять ваш способ по умолчанию
PAYMENT_METHOD_SBPСистема быстрых платежей
PAYMENT_METHOD_CARDБанковская карта

Плательщик комиссии

ЗначениеЧто означает
COMMISSION_PAYER_MERCHANTКомиссию платите вы: плательщик платит сумму заказа, вам зачисляется сумма минус комиссия
COMMISSION_PAYER_CLIENTКомиссию платит плательщик: он платит сумму плюс комиссию, вам зачисляется полная сумма заказа

Значение фиксируется в платеже на момент создания: смена ваших настроек не переписывает уже созданные платежи.

Частые ошибки интеграции

СимптомПричина
401 при верном ключеПодписали не то, что отправили: подпись считается по компактному JSON, а библиотека сериализует с отступами
401Пропустили base64 — шаг обязателен
401Hex в верхнем регистре; ожидается нижний
401Разъехались часы: расхождение больше окна. Синхронизируйте по NTP
401Переиспользовали nonce. UUID подходит; счётчик в памяти процесса — нет, после перезапуска он начнётся заново
401Прислали ключ не целиком: X-Api-Key — полное значение с секретной частью
405 на POSTПодмена метода не поддерживается: X-HTTP-Method-Override игнорируется
Плательщик видит не ту суммуЧитаете amount вместо charged — они различаются, когда комиссию платит плательщик

Практики интеграции

  • Повторяйте по идемпотентному ключу. Если ответ на создание платежа не дошёл, повторите запрос с тем же merchant_order_id — придёт тот же платёж, второго списания не будет.
  • Уважайте Retry-After. На 429 подождите указанное число секунд.
  • 503 и 504 повторяемые. Повторяйте с нарастающей паузой.
  • Логируйте X-Request-ID. Это единственное, что нужно поддержке для поиска запроса.
  • Не выводите секретную часть ключа в логи и не кладите её в клиентский код: подпись считается на вашем сервере.
  • Сверяйте состояние по ручке платежа, а не по собственным ожиданиям: статус меняет платформа.

Поддержка

При обращении приложите:

  • X-Request-ID из ответа;
  • время запроса и открытый префикс ключа (ak_<идентификатор>, без секретной части);
  • путь и метод.

Ни ключ целиком, ни подпись, ни тело запроса присылать не нужно.