Как это работает у вас
Frontend (Tilda / ваш сайт) → Backend (API магазина) → ApiPay API → Backend магазина получает вебхук об успешной оплате → заказ переводится в «Оплачен».
Деньги идут напрямую на ваш Kaspi-счёт, ApiPay к ним доступа не имеет; тариф — фиксированная подписка, не процент с оборота.
Компоненты ApiPay для магазина
Магазину доступен весь Kaspi API:
| Компонент | Зачем магазину |
|---|---|
| Счёт по номеру | Основной сценарий: push покупателю, счёт живёт 24 часа |
| QR-счёт | Когда нужна ссылка или картинка на экране: у счёта по номеру ссылки на оплату нет — покупатель платит из push в своём Kaspi |
| Вебхук | Автоматическое подтверждение заказа; поллинг статуса — запасной вариант |
| Песочница | Отладка интеграции без реальных денег; переход в прод — тумблер «Рабочий режим» |
| Подписки (авто-выставление) | Для повторяющихся заказов; см. грабли — это не автосписание |
| Кабинет apipay.kz | Ручные счета и возвраты, пока интеграция в работе |
Пошаговый сетап
Шесть шагов:
- Зарегистрируйтесь на apipay.kz (вход по WhatsApp-OTP).
- Отладьте своё решение по документации apipay.kz/docs в режиме «Песочница»: создание счёта из корзины, обработка вебхука. Если у вас нет разработчика — скиньте вашему ИИ ссылку apipay.kz/for-ai и документацию: он соберёт интеграцию под ваш сайт (удобно собирать в Lovable).
- Подключите номер кассира (отдельная SIM — требования).
- Включите «Рабочий режим» — что при этом меняется.
- Проверьте боевой сквозной сценарий: заказ → счёт → оплата → вебхук → статус заказа.
- Выберите тариф по дневному объёму счетов — фиксированная подписка, не процент (Тарифы и комиссия).
Грабли именно магазинов
- «Всё настроили, а оплаты не приходят» — не выключена песочница. Sandbox-счёт создаётся с
is_sandbox: trueиkaspi_invoice_id: "SANDBOX-…", в Kaspi он не уходит и оплатить его нельзя. Лечится кнопкой «Включить рабочий режим». - Подписки ≠ автосписание. Автосписания нет: система сама создаёт обычный счёт в
next_billing_at, клиент получает push и подтверждает оплату вручную. Если нужен свой график — это те же обычные счетаPOST /invoices, поставленные на ваш крон. - Плагина под Tilda/WordPress нет. Интеграция собирается REST-запросами по документации apipay.kz/docs.
- Номер покупателя должен быть в Kaspi. Если покупатель ввёл номер с опечаткой, счёт может уйти чужому человеку или никому: валидируйте номер на форме и показывайте покупателю, куда ушёл счёт.
- Ключи при переключении режима не меняются. Организация, API-ключ и вебхук-секрет остаются те же — конфиг перебивать не нужно (Песочница и рабочий режим).
Для разработчиков: собрать checkout
Хост API — https://api.apipay.kz/api/v1, заголовок X-API-Key (ключ держите только на сервере, никогда в браузере).
Способ оплаты: счёт по номеру или QR
Счёт по номеру (POST /invoices) — когда у вас есть телефон клиента и ок «push в Kaspi» (доставка, менеджер оформляет заказ): Kaspi шлёт push, клиент платит в приложении, счёт живёт 24 часа, ссылки на оплату у него нет. QR (POST /invoices/qr) — оплата «здесь и сейчас» на экране или ссылкой: телефон не нужен, в ответе приходят qr_image_url, qr_token_url и qr_expires_at.
Окно на скан у QR короткое и задаёт его Kaspi — берите момент из qr_expires_at, константу в код не зашивайте. Остальное про QR — окно, лимиты, срок жизни картинки, сосуществование нескольких 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/мин + 10 000/сутки на ключ и 200/мин + 20 000/сутки на организацию, а за массовым перебором номеров может последовать деактивация ключа. Дёргайте точечно, перед созданием счёта.
Обработка вебхуков: статус → действие магазина
| Статус в вебхуке | Действие магазина |
|---|---|
pending |
Счёт создан в Kaspi (только счёт по номеру) — ждём оплату |
paid |
Пометить оплаченным, выдать/отгрузить. Может прийти после cancelled/expired — всё равно «деньги получены» |
cancelled |
Снять резерв, вернуть заказ в «ожидает оплаты» |
expired |
Снять резерв, предложить оплатить заново |
error (+ error_code) |
Показать «оплата не прошла», предложить заново (новый счёт) |
partially_refunded |
Отразить частичный возврат |
Обработчик обязан: проверить подпись X-Webhook-Signature: sha256=<hex> по сырому телу (raw body), а не по перепарсенному JSON; ответить 200 быстро (до 5 с), а обработку отдать в очередь; дедуплицировать по (invoice.id, invoice.status); реагировать на последний статус (cancelled → paid = деньги получены). Код проверки подписи, разбор доставки, ретраев и circuit breaker — в статье Вебхуки ApiPay: настройка и проверка подписи. Если вебхук не пришёл — страховка через поллинг GET /invoices/{id}.
Адресом вебхука ставьте постоянный домен своего сервиса, а не шортенер и не временную заглушку.
QR-UX: сканирование и ожидание
Событие invoice.qr_scanned означает, что клиент отсканировал QR и на экране оплаты, — уберите QR и покажите «Ожидается оплата». Состояние транзиентно: возможен переход в cancelled (клиент свернул приложение), тогда UI откатывает «Ожидается» и предлагает новый QR. Реагируйте на paid/cancelled/expired по каждому invoice.id.
Возвраты при отмене заказа
POST /invoices/{id}/refund — полный (по умолчанию) или частичный (amount либо позиционный return_items[]). Результат приходит вебхуком invoice.refunded. Причины отказов и окно возврата — Возвраты Kaspi через API. Если Kaspi отказал или оплата шла мимо ваших счетов — есть вторая механика, POST /qr-refunds: покупатель сканирует возвратный QR тем же Kaspi, которым платил (Возврат по QR через 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для витрины). - Проверить, что боевой
webhook_urlдоступен снаружи и подпись сходится.
Частые вопросы
Нужен ли Kaspi-магазин или регистрация в Kaspi Merchant?
Нет. Нужны только Kaspi Pay вашего ИП/ТОО и отдельный номер под роль «Кассир».
Нужна ли собственная интеграция с Kaspi API или отдельный договор?
Нет. ApiPay — независимый сервис поверх вашего Kaspi Pay: он работает через штатную роль «Кассир», деньги идут напрямую на ваш счёт. Это не официальная интеграция Kaspi.
Сколько идёт подтверждение оплаты?
Доставка статуса обычно занимает секунды, в отдельных случаях — до 10 минут. Стройте UX «подтверждение придёт на почту/WhatsApp», а не «ждите на странице».
Можно без бэкенда, прямо из Tilda?
Форме Tilda нужен обработчик, который вызовет API и примет вебхук, — это минимальный бэкенд (или low-code-связка). Ваш ИИ соберёт его по apipay.kz/for-ai.
Что с чеками ОФД?
Если у вас подключена Kaspi ОФД, счета создаются с корзиной (cart_items) и чек формируется по позициям каталога — см. статью про 422 и корзину.