Почему магазины приходят к ApiPay
Часто малому и среднему бизнесу нужен REST-приём Kaspi, а собственной интеграции ещё нет. ApiPay — независимый сервис поверх вашего Kaspi Pay: даёт REST API (счёт по номеру и вебхук) через штатную роль «Кассир», без отдельного Магазина на Kaspi.kz и без оборотных требований.
Разблокировка простая: если у магазина уже есть Kaspi Pay, счета можно выставлять через REST API ApiPay — для этого нужен только номер кассира, отдельный Магазин на Kaspi.kz не требуется.
Как это работает у вас
Итоговая схема:
Frontend (Tilda / ваш сайт) → Backend (API магазина) → ApiPay API → Backend магазина получает вебхук об успешной оплате → заказ переводится в «Оплачен».
По-человечески: покупатель вводит номер телефона при оформлении → получает счёт push'ем в своём Kaspi → оплатил → ApiPay шлёт вебхук → вы закрываете сделку. Деньги идут напрямую на ваш Kaspi-счёт, ApiPay к ним доступа не имеет; тариф — фиксированная подписка, не процент с оборота.
Компоненты ApiPay для магазина
Магазину доступен весь Kaspi API:
| Компонент | Зачем магазину |
|---|---|
| Счёт по номеру | Основной сценарий: push покупателю, счёт живёт 24 часа |
| Ссылка на оплату из счёта | Можно отправить покупателю в WhatsApp — работает те же 24 часа |
| Вебхук | Автоматическое подтверждение заказа; поллинг статуса — запасной вариант |
| Песочница | Отладка интеграции без реальных денег; переход в прод — тумблер «Рабочий режим» |
| Подписки (авто-выставление) | Для повторяющихся заказов; см. грабли — это не автосписание |
| Кабинет apipay.kz | Ручные счета и возвраты, пока интеграция в работе |
Пошаговый сетап
Инструкция из 4 шагов, которую поддержка отправляет каждому магазину, — плюс два практических шага:
- Зарегистрируйтесь на apipay.kz (вход по WhatsApp-OTP).
- Отладьте своё решение по документации apipay.kz/docs в режиме «Песочница»: создание счёта из корзины, обработка вебхука. Если у вас нет разработчика — скиньте вашему ИИ ссылку apipay.kz/for-ai и документацию: он соберёт интеграцию под ваш сайт (это стандартная рекомендация поддержки, так подключаются магазины на Tilda и WordPress; удобно собирать в Lovable).
- Подключите номер кассира (отдельная SIM — требования).
- Включите «Рабочий режим» — у вас будет 3 дня на живые тесты (что меняется при переходе — внимание: тестовые ключи перегенерируются).
- Проверьте боевой сквозной сценарий: заказ → счёт → оплата → вебхук → статус заказа.
- Выберите тариф по дневному объёму счетов — фиксированная подписка, не процент; актуальные тарифы на apipay.kz.
Грабли именно магазинов
- «Всё настроили, а оплаты не приходят» — причина №1 у новых магазинов: песочница не выключена. Счета в тестовом режиме в Kaspi не передаются; покупатель на ссылке видит «Пожалуйста, повторите позднее». Решается одной кнопкой «Включить рабочий режим».
- Подписки ≠ автосписание. Магазины нередко ждут автосписаний, как в карточных эквайрингах. Оплата по номеру так не работает, и в этом суть модели: автосписания нет — счёт просто выставляется автоматически, а клиент получает push и сам нажимает «Оплатить». Если нужен свой график, это те же обычные счета, настроенные по крону на вашей стороне. Поняв это, часть магазинов отказывается от подписок и заметно упрощает интеграцию.
- Плагина под Tilda/WordPress нет — и это осознанная позиция: REST-запросы по /docs собирает ваш ИИ за вечер. Не ждите «модуль из каталога».
- Номер покупателя должен быть в Kaspi. Если покупатель ввёл номер с опечаткой, счёт может уйти чужому человеку или никому: валидируйте номер на форме и показывайте покупателю, куда ушёл счёт.
- Переход в прод перегенерирует ключи. Тестовые организации и ключи удаляются при включении рабочего режима — перебейте ключ и вебхук-секрет в конфиге сайта.
Мини-кейс: магазин на конструкторе сайтов
Типичный путь магазина на конструкторе сайтов (например, Tilda), у которого ещё нет собственной интеграции с Kaspi, выглядит так. Сначала магазин регистрируется на apipay.kz и отлаживает решение по документации apipay.kz/docs в режиме «Песочница». Готового плагина под конструктор нет, поэтому интеграцию собирают REST-запросами — как правило, с помощью ИИ-помощника. Затем подключают номер кассира и включают «Рабочий режим», получив несколько дней на живые тесты. За пару-тройку дней интеграция обычно выходит на рабочий режим, а магазин подбирает подходящий тариф по объёму счетов.
Для разработчиков: собрать checkout
Ниже — практическая часть для backend/full-stack разработчика магазина. Хост API — https://api.apipay.kz/api/v1, заголовок X-API-Key (ключ держите только на сервере, никогда в браузере).
Способ оплаты: счёт по номеру или QR
Два способа, выбор — под ваш UX:
Счёт по номеру (POST /invoices) |
QR (POST /invoices/qr) |
|
|---|---|---|
| Как платит клиент | Kaspi шлёт push на номер, клиент платит в приложении | На странице показываем QR-картинку/ссылку, клиент сканирует |
| Нужен телефон | да (phone_number) |
нет |
| Ответ | 201 со status: "processing" |
201 сразу status: "pending" + QR-поля |
| pending-вебхук | да (processing → pending) |
нет (статус уже в синхронном ответе) |
description |
до 500 символов | до 100 (наименование позиции в QR-чеке) |
| Жизнь счёта | 24 часа | минуты (TTL ~5 минут) |
| Спец-лимит | общий 200/min | 60/min на организацию |
| Особое событие | — | invoice.qr_scanned |
| Несколько QR | — | сосуществуют: новый QR не гасит старый |
Счёт по номеру — когда есть телефон клиента и ок «push в Kaspi» (доставка, менеджер оформляет). QR — оплата «здесь и сейчас» на экране. Ответ QR несёт qr_image_url (PNG 600×600), qr_token_url и qr_expires_at. Почему QR живёт минуты — в статье QR-счёт: TTL и лимиты.
Идемпотентный checkout
Повторный клик «Оплатить» или двойной сабмит не должны плодить счета. Защита — поле external_order_id_idempotency = ID заказа:
curl -X POST https://api.apipay.kz/api/v1/invoices \
-H "X-API-Key: ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{ "phone_number": "8XXXXXXXXXX", "amount": 5000,
"description": "Заказ #1042",
"external_order_id": "order-1042",
"external_order_id_idempotency": "order-1042" }'
Повтор с тем же ключом → 409 duplicate_idempotency_key с телом { invoice_id, status }: покажите существующий счёт, не создавайте второй. external_order_id — метка для матчинга в вебхуке (по ней магазин находит заказ). Не проверяйте номер на каждый pageview через POST /clients/check — у него лимит 60/min + 10 000/день, массовый перебор ведёт к блокировке ключа; дёргайте точечно, перед созданием счёта.
Обработка вебхуков: статус → действие магазина
| Статус в вебхуке | Что произошло | Действие магазина |
|---|---|---|
pending |
Счёт создан в Kaspi (только счёт по номеру; для QR нет) | Ждём оплату |
paid |
Оплачен | Пометить оплаченным, выдать/отгрузить. Может прийти после cancelled/expired — всё равно «деньги получены» |
cancelled |
Отменён клиентом/магазином/кассиром | Снять резерв, вернуть заказ в «ожидает оплаты» |
expired |
Истёк (24ч по номеру / минуты по QR) | Снять резерв, предложить оплатить заново |
error (+ error_code) |
Терминальная техническая ошибка | Показать «оплата не прошла», предложить заново (новый счёт) |
partially_refunded |
Первый частичный возврат оформлен | Отразить частичный возврат |
Вебхук приходит как POST {ваш webhook} с заголовком X-Webhook-Signature: sha256=<hex>. Проверяйте подпись по сырому телу (raw body), не по перепарсенному JSON:
import crypto from 'crypto'
function verify(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const got = Buffer.from(header || '')
const exp = Buffer.from(expected)
if (got.length !== exp.length) return false
return crypto.timingSafeEqual(exp, got)
}
// secret = process.env.APIPAY_WEBHOOK_SECRET
Три правила обработчика: отвечать 200 быстро (до 5 с), обработку — в очередь; дедуп по (invoice.id, invoice.status) (повторы доставки возможны); реагировать на последний статус (cancelled → paid = деньги получены). Полный разбор доставки, ретраев и circuit breaker — в статье Вебхуки ApiPay: настройка и проверка подписи; здесь не дублируем. Если вебхук не пришёл (пауза breaker'а, сеть) — страховка через поллинг GET /invoices/{id}.
QR-UX: сканирование, ожидание, таймауты
Событие invoice.qr_scanned (в payload qr_substate: "scanned") означает, что клиент отсканировал QR и на экране оплаты; status при этом остаётся pending. Реакция UI: убрать QR, показать «Ожидается оплата». Важно: это транзиентно: возможен scanned → cancelled (клиент свернул приложение) — UI обязан откатить «Ожидается» и предложить новый QR. expired по QR приходит только когда Kaspi отдал терминал, а не по локальному таймеру. QR сосуществуют — реагируйте на paid/cancelled/expired по каждому invoice.id.
Возвраты при отмене заказа
POST /invoices/{id}/refund — полный (по умолчанию) или частичный (amount либо позиционный return_items[]). Результат приходит вебхуком invoice.refunded (completed/failed+error_code). Статуса refunded у счёта нет: полный возврат оставляет paid + is_fully_refunded=true. Причины отказов и окно возврата — Возвраты Kaspi через API.
Чек-лист: sandbox → прод
- Прогнать весь жизненный цикл в песочнице:
POST /invoices/{id}/simulate-status(paid/cancelled/expired/error/qr_scanned), проверить доставку черезGET /webhook-logs?invoice_id=…; магические номера lookup —87770000001(есть Kaspi) /87770000002(нет). - Идемпотентность checkout (
external_order_id_idempotency= ID заказа), дедуп и HMAC вебхуков. - Снятие резерва склада по
expired/cancelled; обработкаcancelled → paidкак «деньги получены». - Уважать
429/Retry-After; даты в ответах — UTC (переводите вAsia/Almatyдля витрины). - Что меняется при переходе — Песочница и рабочий режим: тестовые ключи и вебхук-секрет перевыпускаются, перебейте их в конфиге.
Частые вопросы
Нужен ли Kaspi-магазин или регистрация в Kaspi Merchant?
Нет. Нужны только Kaspi Pay вашего ИП/ТОО и отдельный номер под роль «Кассир».
Законно ли работать через ApiPay, если у нас нет собственной интеграции с Kaspi API?
ApiPay — независимый сервис, работающий через штатную роль «Кассир» в Kaspi Pay; деньги идут напрямую на ваш счёт. Мы не официальная интеграция Kaspi и не утверждаем обратного.
Сколько идёт подтверждение оплаты?
Обычно секунды; у неактивной организации — до ~10 минут. Стройте UX «подтверждение придёт на почту/WhatsApp», а не «ждите на странице».
Можно без бэкенда, прямо из Tilda?
Форме Tilda нужен обработчик, который вызовет API и примет вебхук, — это минимальный бэкенд (или low-code-связка). Ваш ИИ соберёт его по apipay.kz/for-ai.
Что с чеками ОФД?
Если у вас подключена Kaspi ОФД, счета создаются с корзиной (cart_items) и чек формируется по позициям каталога — см. статью про 422 и корзину.