Три способа получить оплату
| Счёт по номеру | Оплата по ссылке или QR | Печатный QR под сделку | |
|---|---|---|---|
| Эндпоинт | POST /invoices |
POST /invoices/qr |
POST /static-qr |
| Что уходит покупателю | Ничего не отправляете: приходит push в Kaspi | Ссылка qr_token_url или картинка qr_image_url |
Ссылка print_url или напечатанный лист |
| Нужен номер покупателя | Да, 8XXXXXXXXXX |
Нет | Нет |
| Сколько ждёт оплату | 24 часа | До qr_expires_at — окно задаёт Kaspi |
Пока не оплачен или не отключён |
| Когда выбирать | Покупатель «где-то там», реагирует на push | Покупатель в чате или у экрана прямо сейчас | Платить будут потом: акт, коробка, договор, витрина |
Одно простое правило: ссылка «сейчас» — qr_token_url, ссылка «потом» — print_url, без ссылки вовсе — счёт по номеру телефона.
Когда какая ссылка подходит
- Магазин в Instagram или WhatsApp. Покупатель написал, договорились о сумме — отправляете
qr_token_urlпрямо в переписку, он открывает и платит, не выходя из чата. Если разговор оборвался и покупатель вернулся через час, ссылка уже не сработает: выставьте новую. - Доставка. Курьер у двери —
qr_token_urlв чат или QR на экране телефона курьера. Если заказ оставляют у двери и оплата ожидается позже, в коробку кладётся печатный лист (print_url). - Чат с покупателем и переписка по сделке. Сумма согласована — ссылка уходит одним сообщением. Ссылку удобно сопровождать суммой текстом: покупатель увидит её и на странице оплаты.
- Услуги и работы по акту. Оплату ждут после приёмки — печатный лист под сделку. Он лежит в документах и работает через неделю так же, как в день выпуска.
- Сайт или бот без своего сервера. API-ключ — серверный секрет, в код, исполняемый в браузере, его класть нельзя. Пока сервера нет, выставляйте счёт в кабинете и отправляйте ссылку руками; когда сервер появится, тот же сценарий переносится в
POST /invoices/qrбез изменений для покупателя. Разбор по площадкам — «ApiPay для интернет-магазина» и «Kaspi-оплата в Telegram-боте».
Как получить ссылку на оплату
curl -X POST https://api.apipay.kz/api/v1/invoices/qr \
-H "X-API-Key: ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"description": "Заказ №123",
"external_order_id": "order-123"
}'
Ответ приходит сразу — 201 со статусом pending:
{
"id": 1,
"amount": "5000.00",
"status": "pending",
"is_qr_token": true,
"qr_token_url": "https://qr.kaspi.kz/...",
"qr_image_url": "https://.../storage/qr/abc.png",
"qr_expires_at": "2026-09-03T07:03:00+00:00"
}
qr_token_url— это и есть ссылка на оплату. Отправьте её покупателю в мессенджер: он открывает её на телефоне, где установлено приложение Kaspi, и платит там — сканировать ничего не нужно. Открытая на компьютере ссылка к оплате не приведёт: покажите рядомqr_image_url, чтобы человек навёл на экран телефон.qr_image_url— готовая картинка QR той же оплаты: показывайте её на экране, если покупатель рядом. Рисовать QR самим не нужно.qr_expires_at— момент, до которого ссылку успевают открыть. Считайте остаток какqr_expires_at − nowи не зашивайте длительность окна константой: её задаёт Kaspi.
То же самое из кода:
const res = await fetch('https://api.apipay.kz/api/v1/invoices/qr', {
method: 'POST',
headers: {
'X-API-Key': process.env.APIPAY_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 5000,
description: 'Заказ №123',
external_order_id: 'order-123',
external_order_id_idempotency: 'order-123',
}),
})
const invoice = await res.json()
// Ссылка на оплату — отправляем покупателю в чат
await sendMessage(chatId, `Оплата 5000 ₸: ${invoice.qr_token_url}`)
// invoice.qr_expires_at — до этого момента ссылку успеют открыть
Ключ X-API-Key живёт только на сервере. Ссылку покупателю отправляет ваш код, а не браузер покупателя.
Ссылка перестала открываться — что делать
Ничего чинить не нужно: истёкшая ссылка означает ровно одно — покупатель не успел, нужен новый QR-счёт. Продления нет, повторный запрос на тот же заказ создаст новую ссылку. Приложение Kaspi в этот момент показывает покупателю «Попробуйте позже» — разбор этого экрана в статье «QR показывает «Попробуйте позже»».
Если ссылку регулярно не успевают открыть — это сигнал, что сценарий не «сейчас», а «потом»: перенесите его на печатный лист или на счёт по номеру телефона.
Ссылки сосуществуют: новая не отменяет прежние, каждая созданная оплачиваема до своего истечения. Двум покупателям в очереди можно спокойно выдать по своей ссылке.
Как понять, что покупатель заплатил
Ждите вебхук со статусом paid — или, если вебхуков ещё нет, читайте GET /invoices/{id}. Настройка — «Как настроить вебхуки ApiPay».
⚠️ Событие invoice.qr_scanned — не оплата. Оно приходит, когда покупатель открыл оплату: статус счёта остаётся pending, добавляется маркер qr_substate: "scanned". Как и любое событие, оно может продублироваться при повторной доставке — дедуплицируйте по паре (invoice.id, invoice.status). После него возможны и paid, и cancelled — покупатель мог закрыть приложение, не подтвердив. Интерфейс кассы должен уметь вернуться из «Ожидается подтверждение» обратно в исходное состояние.
Отменить QR-счёт нельзя: POST /invoices/{id}/cancel отвечает 409 qr_cancel_unsupported, статус не меняется. Делать при этом ничего не нужно — ссылка гаснет сама и счёт уезжает в expired. Отмена работает у счетов по номеру телефона. Если покупатель успел оплатить счёт с неверной суммой, деньги возвращаются обычным возвратом POST /invoices/{id}/refund, а не отменой.
⚠️ В песочнице отмена ведёт себя иначе: тестовый QR-счёт отменяется как обычный — 200 и статус cancelled. Не калибруйте по этому боевую логику: в рабочем режиме придёт 409 qr_cancel_unsupported. Подробнее — «Сколько живёт QR-счёт».
Что нужно знать до первого запроса
- Описание — до 100 символов. Это наименование позиции в чеке Kaspi.
- Частота — до 60 QR в минуту на организацию. Превышение даёт
429 qr_rate_limit; лимит общий на организацию, не на ключ и не на кассира, и действует в том числе в песочнице. Отдельно работает суточный лимит тарифа — «Дневной лимит счетов по тарифу». - Организация с каталогом. И ссылку на оплату, и печатный лист такой организации выдаёт только запрос с корзиной:
POST /invoices/qrиPOST /static-qrс однимamountвернут422сerror_code: catalog_requires_cart_items. Передавайтеcart_items— «Счета с корзиной». - Песочница. В тестовом режиме реальных оплат нет; поле
simulate(paid,cancelled,expired) позволяет проверить все исходы — «Песочница и рабочий режим».
Долгоживущая ссылка: печатный лист под сделку
Когда платить будут не сейчас, счёт выпускается один раз и ждёт покупателя:
curl -X POST https://api.apipay.kz/api/v1/static-qr \
-H "X-API-Key: ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"amount": 45000,
"description": "Ремонт стиральной машины",
"external_order_id": "deal_1024"
}'
В ответе — print_url, восьмисимвольный short_code для ручного ввода, manual_url (куда этот код вводят) и qr_image_url — непротухающая картинка QR для печати.
print_url — тоже ссылка на оплату, только долгоживущая. Её зашивают в QR-код на бумаге, но точно так же можно отправить в мессенджер: покупатель откроет страницу с названием магазина и суммой, нажмёт «Открыть Kaspi» и заплатит. Если приложение не открылось, на той же странице есть запасной путь — покупатель вводит свой номер телефона и получает запрос на оплату push-уведомлением. Что видит покупатель на самом счёте в Kaspi — «Что видит покупатель»: сама оплата в обоих случаях проходит в приложении Kaspi.
Лист привязан к одной сделке: после оплаты повторное открытие показывает «Оплачено». Сумма изменилась или сделка отменилась — отключите лист (DELETE /api/v1/static-qr/{id}) и выпустите новый; описание у выпущенного листа не меняется. Ссылка листа и её токен — это и есть доступ к оплате: не выкладывайте лист туда, где он не нужен. Подробности печати и вёрстки — «Печатный QR для оплаты по счёту или сделке».
А поле kaspi_qr_link — это тоже ссылка?
У обычного счёта по номеру телефона в карточке есть поле kaspi_qr_link — ссылка на оплату этого счёта по QR. Она вычисляется из идентификатора, который Kaspi присваивает счёту, поэтому появляется не сразу: пока счёт в processing, поле null, и в песочнице оно null всегда.
Строить на нём сценарий «отправить покупателю ссылку» не стоит: для этого есть qr_token_url — он приходит сразу в ответе на создание. Про сам счёт по номеру — «Как создать счёт по номеру».
Как это выглядит в кабинете
Разработчик для ссылки не обязателен. В кабинете: Счета → Создать счёт → переключатель «Ссылка или QR-код». Номер покупателя вводить не нужно — достаточно суммы и описания.
После создания под QR-кодом видна сама ссылка, а рядом две кнопки: «Открыть в Kaspi» — проверить, как это выглядит у покупателя, и «Скопировать ссылку» — вставить её в WhatsApp или Telegram.
Вопросы и ответы
Можно ли отправить ссылку на оплату Kaspi в WhatsApp?
Да. Создайте QR-счёт (POST /invoices/qr) и отправьте покупателю qr_token_url из ответа — на телефоне покупателя она открывает оплату в приложении Kaspi. В кабинете тот же результат даёт кнопка «Скопировать ссылку». Одно условие: покупатель должен открыть ссылку в отведённое окно, поэтому отправляйте её, когда человек на связи.
Сколько живёт ссылка на оплату?
qr_token_url действует до qr_expires_at из ответа — длительность этого окна задаёт Kaspi, поэтому берите поле, а не константу в коде. Продлить нельзя: нужен новый QR-счёт. Ссылка печатного листа (print_url) живёт, пока лист не оплачен, не отключён и не наступил заданный вами expires_at.
Чем оплата по ссылке отличается от печатного QR?
Сроком жизни и сценарием. qr_token_url — для «платим прямо сейчас»: покупатель в чате или у экрана. print_url — для «заплатят потом»: акт, коробка с заказом, договор, витрина. Технически обе ссылки ведут покупателя к одной и той же оплате в приложении Kaspi.
Нужен ли номер телефона покупателя?
Нет. Ни для qr_token_url, ни для print_url номер не нужен — в этом их смысл. Номер требуется только счёту по номеру телефона (POST /invoices), где покупателю приходит push.
Как понять, что по ссылке заплатили?
По вебхуку со статусом paid или запросом GET /invoices/{id}. Событие invoice.qr_scanned означает только, что покупатель открыл оплату: статус остаётся pending, и после скана возможна отмена. Дожидайтесь paid.
Можно ли отменить ссылку, если покупатель передумал?
У QR-счёта отмены нет — POST /invoices/{id}/cancel вернёт 409 qr_cancel_unsupported. Делать ничего не нужно: ссылка перестанет работать сама. Печатный лист отключается запросом DELETE /api/v1/static-qr/{id}.