Ошибки¶
Все ошибки 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 ключей — проверяйте на своей стороне, чтобы не тратить лимит запросов.