# Статусы и ошибки

## Статусы заявки { #statuses }

<div class="pp-statusmap" data-lang="ru"></div>

| Статус | Финальный | Что значит |
| --- | --- | --- |
| <span class="pp-status">created</span> | нет | Заявка принята, реквизиты ещё не выданы |
| <span class="pp-status pp-status--wait">processing</span> | нет | Реквизиты выданы, ждём перевод плательщика |
| <span class="pp-status pp-status--ok">completed</span> | да | Оплата подтверждена |
| <span class="pp-status">canceled</span> | да | Не оплачено: истёк срок заявки, её отменили или оплату не удалось подтвердить после выдачи реквизитов |
| <span class="pp-status pp-status--bad">error</span> | да | Реквизиты не выданы, причина — в `error_code` |
| <span class="pp-status pp-status--warn">appeal</span> | нет | Оплату по закрытой заявке перепроверяют |

Ответ на создание может сразу прийти в любом статусе, кроме `appeal`. Финальный статус
меняется только после проверки оплаты или решения службы поддержки — см.
[Смена статуса после закрытия](callbacks.md#status-changes).

**`error` бывает только до выдачи реквизитов**: из `created`, обычно сразу в ответе на
создание. Если реквизиты уже выданы (`processing`), заявка заканчивается только `completed`
или `canceled`. Если после выдачи оплату не удалось подтвердить, заявка тоже становится
`canceled`, без `error_code`.

Срок заявки задаёт настройка проекта: по умолчанию 15 минут с `created_at`. Заявка, не
оплаченная к концу срока, становится `canceled`.

**Callback приходит только о `completed` и `canceled`** — при подтверждении оплаты,
отмене, истечении срока, после проверки оплаты и после решения службы поддержки.
О статусе `error` callback не приходит: его видно в ответе на создание и при чтении
заявки. Подробнее — в разделе
[Callback-и](callbacks.md#when).

## Коды `error_code` { #error-codes }

`error_code` и `error_comment` есть только у заявки в статусе `error` — в ответе на
создание и при чтении. Код объясняет, почему реквизиты не выданы. У `canceled` кода нет,
в том числе когда оплату не удалось подтвердить. Для плательщика во всех
случаях одно действие: оплатить заново, новой заявкой с новым `merchant_payment_id`.

| Код | Что случилось |
| --- | --- |
| `provider_unavailable` | Платёж сейчас нельзя принять: способ оплаты временно недоступен |
| `provider_no_requisites` | Не нашлось свободных реквизитов |
| `rate_unavailable` | Нет курса для расчёта |
| `provider_result_ambiguous` | Выдача реквизитов не подтвердилась, реквизиты не выданы |
| `provider_result_unknown` | Результат выдачи реквизитов неизвестен, реквизиты не выданы |
| `stale_dispatch` | Реквизиты не удалось получить вовремя |
| `invalid_payment` | Заявка не прошла проверку полей |
| `json_invalid` | Тело запроса не разобралось |

Список может пополняться: обрабатывайте незнакомый код как общую ошибку.

## Ошибки HTTP { #http-errors }

Тело ошибки — всегда JSON:

```json
{"error": "signature_invalid"}
```

`Content-Type: application/json`, заголовок `X-Request-ID` возвращается как в запросе.

| HTTP | `error` | Когда | Что делать |
| --- | --- | --- | --- |
| 400 | `bad_request` | Тело не JSON, неизвестное поле, лишний текст после объекта | Исправить запрос |
| 400 | `invalid_payment` | Поле не прошло проверку: сумма, номер заказа, гео, метод, банк, `callback_url` и т. п. | Исправить запрос |
| 401 | `token_invalid` | Нет токена, он неизвестен, отозван, истёк, или проект не активен | Проверить токен в кабинете |
| 401 | `signature_invalid` | Подпись не прошла проверку | [Чек-лист подписи](signing.md#debug-checklist) |
| 403 | `insufficient_scope` | У токена нет нужного права | Выпустить токен с `payments:create` / `payments:read` |
| 403 | `ip_not_allowed` | Адрес не в allowlist проекта | Добавить адрес в кабинете |
| 403 | `project_blocked` | API проекта закрыт сотрудником | Связаться с менеджером |
| 403 | `merchant_blocked` | Мерчант заблокирован | Связаться с менеджером |
| 403 | `merchant_archived` | Мерчант удалён | Связаться с менеджером |
| 404 | `payment_not_found` | Заявки нет или она чужая | Проверить `id` |
| 409 | `request_replayed` | Этот `X-Request-Nonce` уже был | Повторить с новым nonce и новой подписью |
| 409 | `payment_idempotency_conflict` | `merchant_payment_id` уже занят заявкой с другими полями | Новый номер или те же поля, см. [Идемпотентность](idempotency.md) |
| 413 | `request_too_large` | Тело больше лимита проекта (по умолчанию 64 КБ) | Уменьшить тело |
| 422 | `payment_not_covered` | Метод, валюта или сумма вне вашего договора | Проверить условия с менеджером |
| 422 | `payment_rate_unavailable` | Нет курса | Повторить позже новой заявкой |
| 422 | `payment_not_routable` | Сейчас платёж этим способом принять нельзя | Повторить позже новой заявкой |
| 429 | `rate_limited` | Превышен лимит запросов | Подождать `Retry-After` секунд |
| 429 | `too_many_concurrent_requests` | Больше 20 одновременных запросов проекта | Подождать `Retry-After` (1 с) и повторить тот же запрос |
| 500 | `internal_error` | Сбой на стороне API | Повторить с паузой |
| 503 | `rate_limit_unavailable` | Временно недоступен учёт лимитов | Подождать `Retry-After` и повторить |
| 503 | `replay_protection_unavailable` | Временно недоступна проверка nonce | Подождать `Retry-After` и повторить |

Если заявка при `422` успела сохраниться (в статусе `error`), в теле есть её `id`:

```json
{"error": "payment_not_routable", "payment_id": "3f1c2b9e-6a8d-4c4b-9a71-2c7f5d1e0a42"}
```

## Когда повторять { #retries }

| Ответ | Повторять? | Как |
| --- | --- | --- |
| Таймаут, обрыв связи | да | Тот же запрос и `merchant_payment_id`, **новые** nonce и подпись |
| `429`, `503` | да | Через `Retry-After` секунд, затем с растущей паузой |
| `500` | да | С растущей паузой: 1, 2, 4, 8 секунд… |
| `409 request_replayed` | да | С новым nonce и новой подписью |
| `401`, `403`, `400`, `404`, `413` | нет | Сначала исправить причину |
| `422`, статус `error` | нет | Для новой попытки — новая заявка с новым `merchant_payment_id` |

Повтор с тем же `merchant_payment_id` безопасен: второй заявки не будет. Подробнее —
в разделе [Идемпотентность и лимиты](idempotency.md).

```python
# Повтор с растущей паузой: тело и merchant_payment_id те же, подпись — новая.
for attempt in range(5):
    headers = sign_request("POST", url, body, key, key_id)  # новый nonce каждый раз
    response = send(url, body, headers)
    if response.status in (429, 503):
        time.sleep(int(response.headers.get("Retry-After", "1")))
        continue
    if response.status >= 500 or response.status == 0:  # 0 — таймаут или обрыв
        time.sleep(2 ** attempt)
        continue
    break
```