Коротко
Первым делом остановите поток: удалите API-ключ в кабинете. С момента удаления все запросы с этим ключом получают 401, то есть новые счета не создаются — и пока вы правите код, покупатели не увидят лишних счетов. Причину ищите уже после остановки: обычно это отсутствие идемпотентности, цикл повторов или неправильная обработка вебхуков. Надёжное решение — передавать заголовок Idempotency-Key в каждом запросе на создание счёта.
Экстренная остановка
- Заходите в кабинет, открываете раздел API-ключей
- Удаляете ключ, которым выставляются счета
- Правите код
- Создаёте новый ключ и меняете его на сервере
Удаление ключа — самый быстрый «рубильник». Ни передеплой, ни остановка сервера для этого не нужны.
Что не меняется: ранее созданные счета остаются на месте, настройки вебхуков сохраняются, привязка Kaspi не рвётся. Недействительным становится только сам ключ.
Учтите: это останавливает всю интеграцию, то есть правильные счета тоже перестанут создаваться. Если сломан один участок и ключей у вас несколько, удаляйте только его ключ.
Уборка лишних счетов
После остановки потока:
- Закройте неоплаченные лишние счета через
POST /api/v1/invoices/{id}/cancel— тогда покупатель не оплатит их случайно - Если покупатель всё-таки заплатил дважды, лишнее возвращаете: Возврат не проходит
- Отфильтруйте список счетов по
externalOrderIdи посмотрите, сколько их вышло на один заказ
Не торопитесь: счёт в статусе pending ещё может быть оплачен, поэтому уборку начинайте только после остановки потока.
Поиск причины
| Признак | Вероятная причина |
|---|---|
| Ровно два счёта на заказ | Покупатель дважды нажал «Оплатить» или форма ушла дважды |
| Десятки счетов на один заказ | Цикл в коде: повтор при ошибке без условия выхода |
| Счета выходят с ровным интервалом | Задача по расписанию создаёт новый счёт на каждом проходе |
| Счёт появляется на каждый вебхук | Обработчик вебхука сам создаёт счёт, а вебхук повторяется 11 раз |
Пришла ошибка tariff_daily_burst | Сработала суточная защита — она и сделана для отлова таких циклов |
tariff_daily_burst — это не бизнес-лимит, а предохранитель от зациклившейся интеграции. Если он сработал, не спешите повышать тариф: сначала проверьте код. Месячный лимит приходит с другим кодом — tariff_limit_reached.
Как работает Idempotency-Key
Вы добавляете к запросу на создание счёта заголовок Idempotency-Key. При повторной отправке с тем же ключом новый счёт не создаётся: возвращается прежний, с HTTP 200 и признаком idempotentReplay: true в ответе.
POST /api/v1/invoices
X-API-Key: qp_live_…
Idempotency-Key: order-10482
Content-Type: application/json
{ "amount": 12500, "externalOrderId": "10482" }
Как выбирать ключ:
- Привязывайте его к заказу: номер заказа, идентификатор корзины, идентификатор попытки оплаты
- Не делайте его случайным. Новый UUID на каждый запрос лишает идемпотентность смысла
- Не допускайте коллизий. Если по одному заказу нужны два разных платежа, добавьте номер:
order-10482-1,order-10482-2 - Заодно заполняйте
externalOrderId: он возвращается в вебхуке и удобен для поиска счетов
Идемпотентность — не разовая заплатка, а норма. Держите её включённой и тогда, когда всё работает: она сама выручит при обрыве сети, таймауте или перезапуске.
Как правильно писать повторы
Чтобы повтор запроса на создание счёта был безопасным:
- Повторяйте только то, что имеет смысл повторять: 502, 503 и 429 с учётом
Retry-After. Ошибки 4xx повторять бесполезно — надо исправлять запрос - Наращивайте паузу: 1, 2, 4, 8 секунд. Не долбите в цикле
- Ограничьте число попыток: например, после пяти остановиться и пометить заказ для ручного разбора
- В каждой попытке шлите тот же
Idempotency-Key, а не новый - Не считайте таймаут отказом. Запрос мог дойти, счёт мог создаться, а потеряться мог только ответ. Ровно этот случай и закрывает ключ идемпотентности
Идемпотентность в обработке вебхуков
Если вы отвечаете не 2xx, мы повторяем доставку 11 раз. Поэтому одно событие приходит несколько раз — это нормально. Если ваш обработчик на каждое пришедшее уведомление создаёт новый счёт или заново проводит заказ, дубли берутся именно отсюда.
Берите пару (invoice.id, status) как ключ и не обрабатывайте повторно то, что уже обработали. Если обработка долгая, сначала ответьте 200, а работу делайте в фоне — иначе мы засчитаем таймаут и отправим снова.
Профилактика
- Отключайте кнопку «Оплатить» после первого нажатия
- Перед созданием нового счёта проверяйте, нет ли по этому заказу открытого
- Специально проверьте повтор в песочнице: отправьте дважды с одним
Idempotency-Keyи убедитесь, что во втором ответе пришлоidempotentReplay: true - Подключите Telegram-бота, чтобы резкий рост числа счетов не остался незамеченным
Вопросы и ответы
Если удалить ключ, пропадут ли старые счета? Нет. Счета, их статусы и история сохраняются. Недействительным становится только ключ.
Сколько живёт Idempotency-Key? Не рассчитывайте переиспользовать один ключ сколь угодно долго: он защищает от повторов в ближайшее время. Если берёте за ключ номер заказа, на практике этого достаточно.
Считаются ли счета из песочницы в лимит? Нет, счета песочницы в месячный лимит не идут.
Покупатель оплатил оба счёта, что теперь? Вернуть лишнюю сумму. Частичный возврат тоже поддерживается.
Достаточно ли externalOrderId для защиты от дублей? Нет, это поле для поиска и учёта. От повторов защищает заголовок Idempotency-Key.