Рекуррентные платежи

Ошибки и безопасные повторы

Порядок действий при недоступности провайдера, конфликте операции и неудачном списании

Сохраняйте правильные идентификаторы

ИдентификаторНазначениеПовтор
external_idСоздание одной подпискиТо же значение и исходные параметры создания
UUID подпискиПолучение, изменение, отмена и запуск попытки списанияБерётся из объекта подписки Cashera
Idempotency-KeyОдин update или одна внеочередная попыткаТот же ключ, подписка, действие и JSON
UUID транзакцииРезультат конкретного списанияБерётся из charge-now, истории или webhook

Не подставляйте ID провайдера в URL Cashera. Не используйте UUID подписки как UUID каждого нового платежа. API-ключ хранится только на вашем сервере, не в браузере плательщика и не в публичном примере.

До первого запроса

  1. Создайте запись операции в вашей БД с уникальной связью с подпиской и бизнес-действием
  2. Сохраните исходный payload и ключ до внешнего HTTP, а не после ответа
  3. Повтор из очереди или второй обработчик события должен загрузить эту запись
  4. Не вычисляйте новый ключ из текущего времени при каждой попытке

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

Что делать с HTTP-ответом

ОтветЧто означаетДействие
200 / 201 созданияВозвращён объект новой или существующей подписки; HTTP-код не подтверждает оплатуПри pending_agreement открыть непустой confirmation_url. При active/past_due проверить историю, не повторять привязку
200 updateНовые условия подтвержденыСохранить объект подписки; доступ не продлевать как за оплату
202 charge-nowПопытка принята, либо возвращена та же попытка при повтореСохранить UUID транзакции, проверить её status и дождаться paid
400Провайдер отклонил запросПроверить message и состояние. Если ранее был тайм-аут, не считать исходную операцию гарантированно неисполненной
401Нет действующей авторизации CasheraПроверить X-Api-Key. Не делать бесконечных повторов
403Действие запрещено для мерчантаПроверить включение кассы, заморозку и доступ к методу
404Объект не найден или не принадлежит ключуПроверить UUID и владельца. Для нового метода также уточнить, обновлена ли версия сервера
409Конфликт параметров ключа, состояние не подходит, выполняется другая операция или удерживается блокировкаПрочитать message. Для той же незавершённой операции повторять исходный запрос с паузой, не менять ключ
422Ошибка параметров или финансовых ограниченийПроверить JSON-типы, копейки, дату и пару interval/interval_count. При неясном результате предыдущей попытки сначала сверить её
429Превышена допустимая частота запросовСнизить частоту, учесть Retry-After при его наличии, сохранить исходный ключ
5xx / тайм-аут / обрыв связиРезультат может быть неизвестен; запрос мог исполнитьсяПроверить локальное состояние и повторить ту же операцию ограниченно, без нового ключа
409 Conflict
{
"message": "A subscription operation is still unresolved. Reuse its original idempotency key."
}

Ошибки содержат message, а ошибки валидации могут дополнительно содержать errors по полям. Не рассчитывайте на полеtraceId провайдера в публичном ответе Cashera и не используйте точный текст сообщения как постоянный машинный код.

Создание завершилось тайм-аутом или без ссылки

  1. Получите подписку по исходному external_id
  2. Если pending_agreement и ссылка непустая, используйте её; не создавайте новую подписку
  3. Если active или past_due, не запускайте привязку повторно, проверьте историю платежей
  4. Если pending_agreement и ссылка отсутствует, ограниченно повторите тот же POST создания с исходными параметрами
  5. Если creation_unknown, автоматическая замена запрещена: обратитесь в поддержку с UUID и external_id
  6. Если failed или cancelled, повтор создания не возобновляет эту подписку

GET читает сохранённое состояние и сам не запрашивает новую ссылку.all_providers_unavailable: false не гарантирует наличие ссылки: проверяйте именно confirmation_url.

Изменение или списание завершилось неопределённо

  1. Не помечайте операцию успешной и не выдавайте доступ только по факту отправки
  2. Для update запросите подписку; для charge-now, если UUID платежа уже известен, запросите этот платёж
  3. Повторите тот же POST с сохранённым Idempotency-Key и исходным JSON
  4. Используйте задержки с увеличением и случайным разбросом, ограничьте число автоматических попыток
  5. Если ошибка сохраняется, остановите автоматические запросы и обратитесь в поддержку; запись операции и ключ сохраните

Cashera сверяет незавершённые операции в фоне. Даже если GET показывает старые условия, предыдущий update мог уже примениться у провайдера, но ещё не подтвердиться локально. Не обходите это новым ключом. Здесь нет публичного метода «сбросить операцию» или «принудительно зачислить».

Успешный HTTP-код с отсутствующим, неполным или неразбираемым JSON также не подтверждает результат. Сохраните код и ключ, проверьте состояние и ограниченно повторите исходный запрос. Не подставляйте вымышленные значения суммы, UUID или статуса вместо отсутствующих полей.

Новый ключ после тайм-аута может создать новое списание

Сначала установите результат исходной попытки. Полученный после тайм-аута отказ тоже не всегда доказывает, что исходная попытка не исполнилась. Пока результат не выяснен, не создавайте замену, даже если клиент повторно нажал кнопку.

Списание не удалось, но подписка не отменена

Статус платежа и статус подписки независимы. canceled илиfailed у одной транзакции не равен cancelled у подписки.past_due означает проблему со списанием и не является финальным статусом подписки. Смотрите next_charge_at и историю платежей.

Если next_charge_at равен null, не показывайте выдуманную дату повторной попытки. API не гарантирует отмену через N дней или фиксированный график повторов при отказе. Для прекращения подписки используйте отмену, для осознанной новой попытки после проверки предыдущей используйте charge-now.

Что передать в поддержку

  • UUID подписки, её external_id и UUID платежа, если он известен
  • HTTP-метод и путь запроса, время с часовым поясом, код ответа и message
  • Ключ операции и отправленные параметры без API-ключей, секретов и данных плательщика
  • Последовательность попыток: был ли тайм-аут, какие ответы пришли после него

Никогда не присылайте X-Api-Key, X-Secret, пароли, банковские коды или полную ссылку подтверждения. Для поиска достаточно идентификаторов.