Перейти к содержанию

Ошибки

Все ошибки Merchant API возвращаются в одном формате — объект Error:

{
  "type": "error",
  "code": "invalid_request",
  "description": "amount.value must match ^[0-9]{1,13}\\.[0-9]{2}$",
  "parameter": "amount.value",
  "request_id": "req_7f3c2a1b9d4e"
}
Поле Описание
type всегда error
code машинный код из справочника ниже; на него и на HTTP-статус опирается ваш код
description текст для разработчика; может меняться, не парсите его
parameter имя поля запроса, вызвавшего ошибку (путь через точку: amount.value, receipt.items.0.vat_code); только когда применимо
request_id идентификатор запроса; совпадает с заголовком X-Request-Id — указывайте его в обращениях в поддержку

Заголовок Retry-After (секунды) добавляется к ответам 429 и 503. Неизвестный маршрут и неверный HTTP-метод тоже отвечают объектом Error.

Справочник кодов

HTTP code Когда Что делать
400 invalid_request тело или параметры не проходят валидацию по схеме: нет обязательного поля, неверный формат amount.value, Idempotency-Key короче 8 символов, невалидный JSON, а также адрес возврата confirmation.return_url: только https, без учётных данных в адресе (https://user:pass@…), без непубличных диапазонов (10.*, 192.168.*, 127.*, 169.254.*) и без внутренних имён (localhost, *.local, *.internal) — по этому адресу уходит браузер плательщика с нашей страницы результата. В тестовом контуре (ключ test_) ограничения по досягаемости адреса сняты: https://localhost:3000/return принимается, см. тестовый режим; требования https, отсутствия учётных данных и управляющих символов действуют в обоих контурах исправить запрос по parameter; повторять без изменений бессмысленно
401 invalid_credentials нет заголовка Authorization, неверный shop_id/secret_key, ключ отозван или истёк после ротации проверить ключ, режим (test_/live_) и регион (ru/kz)
403 forbidden ключ не имеет права на операцию: тестовый эндпоинт с боевым ключом, IP вне allowlist магазина, магазин заблокирован проверить тип ключа и настройки магазина
404 not_found объект не найден или принадлежит другому магазину (чужие объекты неотличимы от несуществующих); неизвестный маршрут проверить идентификатор и путь
409 duplicate_order_id платёж с таким order_id уже есть в магазине; идентификатор существующего платежа — в description использовать существующий платёж (GET /payments/{payment_id}) или новый order_id
409 idempotency_conflict тот же Idempotency-Key с другим телом запроса для нового запроса сгенерировать новый ключ; для повтора — отправить прежнее тело
409 idempotency_in_progress параллельный повтор: первый запрос с этим ключом ещё обрабатывается повторить через 1–2 секунды с тем же ключом и телом
409 evidence_deadline_passed доказательства по спору загружаются после evidence_deadline_at — в том числе когда автомат уже перевёл спор в expired (у этого кода приоритет над invalid_state) срок продлить нельзя; следить за событием dispute.evidence_required и загружать заранее, см. Споры
422 invalid_state переход недопустим для текущего статуса: capture/cancel не из waiting_for_capture, возврат не из succeeded, изменение финального статуса, simulate с несовместимым исходом; тот же код — для операции над спором в статусе, в котором она не разрешена запросить объект, проверить status
422 amount_limit сумма одной операции вне допустимого диапазона способа оплаты, тарифа и валюты (по умолчанию 1,00–300 000,00 RUB; 100–5 000 000 KZT) проверить лимиты в GET /payment-methods и в кабинете
422 limits_exceeded превышен портфельный лимит: суточный или месячный оборот магазина, число платежей в минуту, средний чек, а также портфельные лимиты мерчанта дождаться следующего окна или запросить пересмотр лимитов в кабинете; сумма одной операции здесь ни при чём
422 method_unavailable способ оплаты недоступен магазину, валюте или региону, либо временно отключён GET /payment-methods; не ограничивать payment_method
422 refund_exceeds_captured сумма возврата больше Payment.refundable_amount (= captured − refunded − charged_back − pending_refunds − disputed). Возможно и при refunded_amount: "0.00" — когда остаток занят чарджбэком, возвратом в пути или открытым спором взять актуальный refundable_amount из GET /payments/{payment_id}; при открытом споре дождаться его исхода (Споры)
422 receipt_invalid чек не проходит проверки профиля: сумма позиций не равна сумме платежа, нет контакта покупателя, неверный vat_code для профиля исправить позицию по parameter
422 shop_not_live боевой ключ у магазина, который ещё не активирован (проверка не завершена) пройти чек-лист выхода в боевой режим; до активации использовать test_-ключ
422 insufficient_merchant_balance возврат по инициативе мерчанта, сумму которого не покрывают вместе ещё не выпущенная часть этого же платежа и положительный остаток available из GET /balance (модель A) пополнить баланс или уменьшить сумму возврата; возврат за счёт резерва или депозита — только по решению оператора либо по спору
422 payments_suspended приём платежей магазина заморожен риск-контролем (состояние счёта мерчанта, а не отказ авторизации ключа). Заморозка выплат приём платежей не останавливает и этого кода не даёт — она видна в Shop.settlement.status.payouts_frozen (модель A) обратиться в поддержку; чтение объектов продолжает работать
429 too_many_requests превышен лимит запросов (по умолчанию 600 в минуту на ключ, 120 созданий платежей в минуту) подождать Retry-After секунд, экспоненциальная задержка
500 internal_error внутренняя ошибка KASSA; подробности не раскрываются повторить с тем же Idempotency-Key; при повторении — в поддержку с request_id
501 not_implemented операция объявлена в контракте, но ещё не реализована на текущем этапе (в описании ошибки назван этап). Для таких операций возвращается именно 501: молчаливый 404 запрещён, чтобы неготовая операция не выглядела опечаткой в пути дождаться этапа из описания; следить за changelog
503 provider_unavailable банк или провайдер недоступен; также при недоступности внутренней инфраструктуры лимитов денежные операции отвечают 503 (чтение продолжает работать) повторить после Retry-After с тем же Idempotency-Key

Лимиты: amount_limit или limits_exceeded

Коды не пересекаются, и по ним видно, что делать дальше:

  • amount_limitпер-транзакционные min/max: сумма одной операции вне диапазона для выбранного способа оплаты, тарифа и валюты. Повтор той же суммы не поможет — нужна другая сумма или другой способ;
  • limits_exceededпортфельные лимиты: суточный и месячный оборот, число платежей в минуту, средний чек. Та же сумма пройдёт позже — в следующем окне или после пересмотра лимитов.

Одноимённое значение cancellation_details.reason = limits_exceeded — из другого справочника: это причина отмены уже созданного платежа, а не отказ на входе.

Рекомендации по обработке

  • Повторяйте безопасно. Сетевые ошибки, 429, 500, 503 — повтор с тем же Idempotency-Key и тем же телом; ответ будет тем же, платёж не задвоится.
  • Не повторяйте 4xx без изменений (кроме 409 idempotency_in_progress): запрос отклонён по содержанию.
  • Логируйте request_id каждого неуспешного ответа.
  • Неизвестный code обрабатывайте по классу HTTP-статуса: контракт может пополняться новыми кодами (с записью в changelog), старые коды не меняются.
  • Валидация до отправки. Формат amount.value (^[0-9]{1,13}\.[0-9]{2}$), длина order_id (1–128), return_url — публичный адрес по https без учётных данных (в песочнице допустим локальный), metadata ≤ 16 ключей — проверяйте на своей стороне, чтобы не тратить лимит запросов.