Kaspi Pay — доминирующий способ оплаты в Казахстане. Более 13 миллионов активных пользователей, 85%+ взрослого населения страны. Если вы создаёте продукт для казахстанского рынка, интеграция Kaspi Pay — это не опция, а необходимость. Это полное руководство по подключению Kaspi Pay к вашему проекту.
Обзор методов оплаты Kaspi
Kaspi предлагает несколько платёжных флоу в зависимости от вашего сценария:
| Метод | Сценарий использования | Действие пользователя |
|---|---|---|
| QR-код | Офлайн-магазины, счета | Пользователь сканирует QR в приложении Kaspi |
| Deep Link | Мобильные приложения | Переход в приложение Kaspi |
| Платёжная ссылка | Web, WhatsApp, SMS | Пользователь нажимает ссылку → приложение Kaspi |
| eCommerce API | Веб-касса | Редирект на страницу оплаты Kaspi |
Для большинства веб- и мобильных интеграций: используйте eCommerce API (флоу с редиректом) или платёжную ссылку.
Получение доступа к API
- Зарегистрируйтесь как мерчант на kaspi.kz/merchantapi
- Пройдите верификацию бизнеса (БИН/ИИН, банковский счёт в Казахстане)
- Получите:
TradePointId, учётные данные API, доступ к тестовой среде
Тестовая среда: https://testpay.kaspi.kz
Продакшн: https://pay.kaspi.kz
Платёжный флоу (eCommerce API)
1. Ваш сервер создаёт заказ → отправляет в Kaspi API
2. Kaspi возвращает URL оплаты
3. Вы перенаправляете пользователя на этот URL (или открываете в WebView на мобильном)
4. Пользователь оплачивает в приложении Kaspi / веб-интерфейсе
5. Kaspi отправляет вебхук на ваш сервер
6. Ваш сервер подтверждает оплату → обновляет заказ
Шаг 1: Создание платёжного заказа
// Пример на Node.js
const axios = require('axios');
const crypto = require('crypto');
const KASPI_TRADE_POINT_ID = process.env.KASPI_TRADE_POINT_ID;
const KASPI_API_KEY = process.env.KASPI_API_KEY;
const BASE_URL = 'https://pay.kaspi.kz'; // или testpay.kaspi.kz
async function createKaspiOrder(orderData) {
const payload = {
amount: orderData.amount, // В тенге, целое число (например, 5000 = 5000 ₸)
orderId: orderData.orderId, // Ваш уникальный ID заказа
returnUrl: orderData.returnUrl, // Редирект после оплаты
failUrl: orderData.failUrl, // Редирект при ошибке
tradePointId: KASPI_TRADE_POINT_ID,
description: orderData.description,
};
// Генерация подписи
const signatureString = `${payload.amount}${payload.orderId}${KASPI_API_KEY}`;
const signature = crypto
.createHash('sha256')
.update(signatureString)
.digest('hex');
const response = await axios.post(
`${BASE_URL}/api/v1/orders/create`,
{ ...payload, signature },
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${KASPI_API_KEY}`,
},
}
);
return response.data; // Содержит { paymentUrl, orderId }
}
// Использование в обработчике оформления заказа
app.post('/checkout', async (req, res) => {
const { cartTotal, orderId } = req.body;
const kaspiOrder = await createKaspiOrder({
amount: Math.round(cartTotal),
orderId: `ORDER-${orderId}`,
returnUrl: `https://yoursite.kz/payment/success?orderId=${orderId}`,
failUrl: `https://yoursite.kz/payment/fail?orderId=${orderId}`,
description: `Заказ #${orderId}`,
});
// Перенаправляем пользователя на страницу оплаты Kaspi
res.json({ paymentUrl: kaspiOrder.paymentUrl });
});
Шаг 2: Обработка вебхука
Kaspi отправляет POST-запрос на ваш URL вебхука при изменении статуса оплаты.
// Обработчик вебхука
app.post('/webhooks/kaspi', express.raw({ type: 'application/json' }), async (req, res) => {
const payload = JSON.parse(req.body);
// Проверка подписи
const expectedSignature = crypto
.createHash('sha256')
.update(`${payload.orderId}${payload.amount}${KASPI_API_KEY}`)
.digest('hex');
if (payload.signature !== expectedSignature) {
return res.status(400).json({ error: 'Неверная подпись' });
}
switch (payload.status) {
case 'APPROVED':
await db.orders.update({
where: { kaspiOrderId: payload.orderId },
data: {
status: 'paid',
paidAt: new Date(),
kaspiTransactionId: payload.transactionId,
}
});
await fulfillOrder(payload.orderId);
break;
case 'DECLINED':
case 'CANCELLED':
await db.orders.update({
where: { kaspiOrderId: payload.orderId },
data: { status: 'cancelled' }
});
break;
}
// Всегда отвечайте 200, чтобы подтвердить получение
res.status(200).json({ received: true });
});
Шаг 3: Проверка статуса оплаты (резервный polling)
Не полагайтесь только на вебхуки — они могут не дойти. Проверяйте статус на URL возврата:
async function checkKaspiOrderStatus(kaspiOrderId) {
const response = await axios.get(
`${BASE_URL}/api/v1/orders/${kaspiOrderId}/status`,
{
headers: { 'Authorization': `Bearer ${KASPI_API_KEY}` }
}
);
return response.data.status; // 'APPROVED' | 'PENDING' | 'DECLINED' | 'CANCELLED'
}
// Обработчик URL возврата
app.get('/payment/success', async (req, res) => {
const { orderId } = req.query;
const order = await db.orders.findOne({ id: orderId });
// Всегда верифицируйте — не доверяйте параметрам URL
const kaspiStatus = await checkKaspiOrderStatus(order.kaspiOrderId);
if (kaspiStatus === 'APPROVED') {
res.render('success', { order });
} else {
res.render('pending', { order }); // Оплата может ещё обрабатываться
}
});
Мобильная интеграция: Flutter Deep Link
Для мобильных приложений используйте deep link для прямого открытия приложения Kaspi:
// pubspec.yaml
dependencies:
url_launcher: ^6.2.0
// payment_service.dart
import 'package:url_launcher/url_launcher.dart';
class KaspiPaymentService {
/// Открывает приложение Kaspi для оплаты. Возвращает в ваше приложение через deeplink.
Future<void> openKaspiPayment(String paymentUrl) async {
final uri = Uri.parse(paymentUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
// Приложение Kaspi не установлено — открываем веб-версию
await launchUrl(
Uri.parse(paymentUrl),
mode: LaunchMode.inAppWebView,
);
}
}
}
Обработка входящего deep link в приложении:
// В AndroidManifest.xml
// <intent-filter>
// <action android:name="android.intent.action.VIEW" />
// <data android:scheme="yourapp" android:host="payment" />
// </intent-filter>
// В Flutter-приложении
class PaymentResultScreen extends StatefulWidget {
@override
void initState() {
super.initState();
_handleIncomingLink();
}
void _handleIncomingLink() {
// yourapp://payment?status=success&orderId=ORDER-123
final uri = Uri.parse(widget.deepLink);
final status = uri.queryParameters['status'];
final orderId = uri.queryParameters['orderId'];
if (status == 'success') {
// Верифицируйте на сервере перед показом экрана успеха
_verifyPayment(orderId);
}
}
}
Оплата через QR-код
Для розничной торговли или оплаты по счёту:
async function generateKaspiQR(amount, orderId) {
const response = await axios.post(`${BASE_URL}/api/v1/qr/create`, {
amount,
orderId,
tradePointId: KASPI_TRADE_POINT_ID,
signature: generateSignature(amount, orderId),
});
// response.data.qrCode — QR-изображение в base64
// response.data.qrToken — токен для проверки статуса оплаты
return response.data;
}
// Polling статуса QR (пользователь ещё не подтвердил)
async function pollQRStatus(qrToken, maxAttempts = 60) {
for (let i = 0; i < maxAttempts; i++) {
await sleep(3000); // Проверяем каждые 3 секунды
const status = await axios.get(`${BASE_URL}/api/v1/qr/${qrToken}/status`, {
headers: { Authorization: `Bearer ${KASPI_API_KEY}` }
});
if (status.data.status === 'APPROVED') return 'paid';
if (status.data.status === 'DECLINED') return 'failed';
// 'PENDING' — продолжаем polling
}
return 'timeout';
}
Типичные ошибки и их решения
| Код ошибки | Значение | Решение |
|---|---|---|
INVALID_SIGNATURE |
Несовпадение подписи | Проверьте порядок полей в строке подписи |
ORDER_ALREADY_EXISTS |
Дублирующийся orderId | Используйте уникальные ID (UUID или timestamp) |
TRADE_POINT_NOT_FOUND |
Неверный TradePointId | Проверьте переменную окружения |
AMOUNT_TOO_SMALL |
Ниже минимума (100 ₸) | Валидируйте сумму перед отправкой |
INVALID_RETURN_URL |
URL не добавлен в белый список | Зарегистрируйте URL возврата в портале мерчанта |
Тестирование
В тестовой среде Kaspi:
- Принимается любая сумма
- Используйте endpoint
testpay.kaspi.kz - Тестовое приложение Kaspi предоставляется отдельно (запросите у поддержки мерчантов)
Важно перед запуском в продакшн
- Зарегистрируйте все домены
returnUrlиfailUrlв портале мерчанта до выхода в прод - Сохраняйте
kaspiTransactionIdиз вебхука — потребуется для возвратов - API возврата:
POST /api/v1/orders/{orderId}/refundс суммой и подписью - Ограничение запросов: 100 запросов в минуту на один TradePointId
Нужна помощь с интеграцией Kaspi Pay? Напишите в Aunimeda →
Aunimeda разрабатывает сайты и веб-приложения для бизнеса в Казахстане — корпоративные сайты, интернет-магазины, порталы и платформы под заказ.
Связаться с нами и обсудить проект. Смотрите также: Разработка интернет-магазина, Мобильные приложения в Алматы