Повторные запросы
Как custom_id защищает от двойного списания и как узнать судьбу операции.
Идемпотентность
У каждой денежной операции есть custom_id, который придумываете вы (удобнее всего UUID). Повтор запроса с тем же custom_id и тем же телом возвращает результат первой попытки с idempotent_replay: true и денег не списывает. Тот же custom_id с другим телом отбивается 409 custom_id_conflict. Пока первая попытка выполняется, повтор получает 409 operation_in_progress: подождите секунду и повторите.
Правило одно: новая операция это новый custom_id, ретрай упавшего запроса это тот же custom_id. При 503 upstream_unavailable, 500 и таймауте повторяйте с тем же custom_id, двойного списания не будет.
Сохраните custom_id у себя до отправки запроса: если ответ потеряется по дороге, повтор без сохранённого id превратится в новую покупку.
Узнать судьбу операции, ничего не повторяя, можно через GET /operations/{custom_id}: in_progress (ещё выполняется), done (готово, ответ в result, ровно тот, что получил бы повтор; у пополнения статус в result живой), failed (отказ, тело в error, тот же custom_id можно отправить снова) или unknown (запрос к поставщику ушёл, а ответ потерялся: подписка могла появиться). При unknown не повторяйте подписку на срок вслепую: сначала проверьте GET /subscriptions?external_user_id=... или напишите в поддержку с custom_id. У «Навсегда» повтор с тем же custom_id безопасен. 404 operation_not_found значит, что запрос до нас не дошёл и его можно отправить с тем же custom_id. Удобно для восстановления после перезапуска вашего сервера: пройдитесь по незавершённым заказам и спросите каждый.