Qut Pay Сайт Кабинет База знаний Инструкции Документация API ҚАЗРУС
ГлавнаяБаза знаний → Решение проблем

Поздняя оплата — счёт закрыт, а деньги пришли

Обновлено: 2026-09-14 · Версия в Markdown

Коротко

Даже после того как счёт перешёл в cancelled или expired, деньги по нему могут прийти с опозданием. Это нормальная, хотя и редкая ситуация. В таком случае к вам приходит событие invoice.paid с меткой late: true. Дальше у вас два пути: оказать услугу или вернуть деньги. Третьего — проигнорировать — нет: деньги лежат на счёте мерчанта, а покупатель ждёт свой товар.

Главное, чтобы ваш код умел ждать такое событие. Многие интеграции обрабатывают invoice.paid только если счёт ещё открыт, и молча теряют поздние оплаты.

Почему так бывает

Счёт живёт и у нас, и в Kaspi, и отсчёт времени с двух сторон не всегда совпадает.

Kaspi не сообщает точное время оплаты. В payload нет поля «в какую секунду покупатель подтвердил», поэтому измерить задержку по данным невозможно. Вы видите только факт: счёт был закрыт, деньги пришли.

Как это увидеть

ПризнакГде видно
Событие invoice.paid с меткой late: trueВ payload вебхука
Статус счёта перешёл из закрытого в paidGET /api/v1/invoices/{id}
В Kaspi Pay транзакция есть, а в вашей системе заказ закрытПри сверке счёта Kaspi со своей базой

Если вебхуки не слушать, такую оплату вы заметите только в конце месяца при сверке. Поэтому событие invoice.paid стоит обрабатывать всегда.

Готовность в коде

Не считайте позднюю оплату отдельным событием — это обычный invoice.paid, просто с меткой. Главное правило: не привязывайте обработку оплаты к прежнему статусу счёта.

app.post('/webhooks/qutpay', async (req, res) => {
  const event = req.headers['x-webhook-event'];
  const body = JSON.parse(req.rawBody); // после проверки подписи

  if (event === 'invoice.paid') {
    const orderId = body.invoice.externalOrderId;
    const order = await orders.find(orderId);

    // Идемпотентность: обрабатывали ли мы уже этот счёт?
    if (order.paidInvoiceId === body.invoice.id) return res.sendStatus(200);

    if (body.late) {
      // Счёт был закрыт, но деньги пришли
      if (order.status === 'cancelled') {
        await flagForReview(order, body.invoice); // решает человек
      } else {
        await fulfil(order, body.invoice); // выдаём услугу
      }
    } else {
      await fulfil(order, body.invoice);
    }
  }

  res.sendStatus(200);
});

Обратите внимание на три вещи:

Как принять решение

СитуацияПравильное действие
Заказ ещё не выполнен, товар в наличииВыдайте услугу, отметьте заказ оплаченным
Товара нет, заказ закрытВерните деньги и сообщите покупателю
Покупатель заплатил дваждыВерните лишнее
Услуга срочная (подписка, абонемент)Откройте период с этого момента
Мероприятие прошло, билет недействителенВерните деньги

Возврат делается через POST /api/v1/invoices/{id}/refund. Если в ответ пришло refund_unknown или refund_pending_unknown, не отправляйте повторно — сначала прочитайте статус счёта, иначе можно вернуть деньги дважды.

Как говорить с покупателем

Поздняя оплата не вина покупателя. Он нажал «подтвердить», деньги ушли — с его точки зрения всё прошло правильно.

Чтобы это случалось реже

Если счета не оплачиваются вообще, причина другая: Проблема у вас или у Kaspi.

Вопросы и ответы

Часто ли бывают поздние оплаты? Редко. Но при сотнях счетов в месяц вы с ними столкнётесь, поэтому код стоит подготовить заранее.

Куда приходят деньги, если счёт закрыт? Прямо на ваш счёт Kaspi. Статус счёта на движение денег не влияет.

В каком событии приходит метка late: true? В invoice.paid, если до этого счёт был cancelled или expired.

Если я проигнорирую событие, оно повторится? При ответе не 2xx — да, до 11 раз. Но если вернуть 200 и внутри ничего не сделать, событие потеряется.

Сколько времени есть на возврат? Срок определяется на стороне Kaspi, поэтому тянуть не стоит. Если возврат не проходит, сначала прочитайте статус счёта.

Связанные статьи

Счёт оплачен, а денег нетПричин три: включён тестовый режим, деньги ищут не там, или кассир принадлежит другой организации Kaspi. Как проверить каждую и что делать дальше.Подпись вебхука не сходитсяПодпись считается как HMAC-SHA256(secret, timestamp + "." + rawBody). Самая частая ошибка — разобрать тело в JSON и собрать обратно в строку. Примеры получения raw body для Express, Laravel, Django.Проблема у вас или у Kaspi — диагностика за две минутыТри вопроса показывают, на чьей стороне сбой: в вашей интеграции, в привязке Kaspi или в самом сервисе. К каждому ответу — конкретное действие и список того, что собрать для поддержки.

Остались вопросы? WhatsApp +77788813333 · kazprose@gmail.com
Написать можно и из кабинета: Поддержка.

Qut Pay — независимый сервис, не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.