Интеграция Kaspi Pay API в 2026: полное руководство для разработчиков
Kaspi Pay в 2026 году - это уже не просто платёжный сервис, это финансовая инфраструктура Казахстана. 14+ миллионов активных пользователей, более 90% платёжеспособного населения страны. Kaspi Gold - карта номер один, Kaspi.kz - маркетплейс с крупнейшим трафиком в РК. Если ваш продукт работает с казахстанской аудиторией, Kaspi Pay - это не опция.
В 2026 году Kaspi обновил Merchant API до версии v2 с новой системой подписей, улучшенными вебхуками и поддержкой рассрочки (Kaspi Рассрочка). Эта статья охватывает всё актуальное.
Какой метод оплаты выбрать
| Метод | Где применять | Как работает |
|---|---|---|
| eCommerce API (редирект) | Веб-сайт, SPA | Редирект на hosted-страницу Kaspi |
| Payment Link | WhatsApp, Telegram, SMS, email | Пользователь тапает ссылку → Kaspi app |
| QR-код | Офлайн-касса, счёт-фактура, экран | Сканирование камерой Kaspi |
| Deep Link | Нативное iOS/Android приложение | Прямое открытие Kaspi app |
| Kaspi Рассрочка | Интернет-магазин, B2C | Покупка в рассрочку 3/6/12 мес. |
Рекомендация для 2026: для e-commerce используйте eCommerce API + Kaspi Рассрочку параллельно - рассрочка увеличивает конверсию на товарах от 30 000 ₸.
Регистрация мерчанта
- Зайдите на kaspi.kz/merchantapi
- Заполните анкету: БИН компании или ИИН ИП, расчётный счёт в казахстанском банке
- Подтвердите e-mail и телефон руководителя
- Дождитесь верификации (обычно 1–3 рабочих дня)
- Получите в личном кабинете:
TradePointId,ApiKey, доступ к тест-окружению
Окружения:
Тест: https://testpay.kaspi.kz/api/v2
Продакшн: https://pay.kaspi.kz/api/v2
В тестовом окружении подтверждайте оплату через тестовое приложение Kaspi (запросите ссылку у менеджера поддержки мерчантов).
Подпись запросов (API v2)
В v2 изменился алгоритм подписи - теперь используется HMAC-SHA256 вместо SHA256:
const crypto = require('crypto');
function generateSignature(params, apiKey) {
// Сортируем ключи алфавитно, склеиваем значения
const sortedValues = Object.keys(params)
.sort()
.map(key => params[key])
.join('');
return crypto
.createHmac('sha256', apiKey)
.update(sortedValues)
.digest('hex');
}
Шаг 1: Создание платёжного заказа
// Node.js / Express
import axios from 'axios';
import crypto from 'crypto';
const KASPI_TRADE_POINT_ID = process.env.KASPI_TRADE_POINT_ID;
const KASPI_API_KEY = process.env.KASPI_API_KEY;
const KASPI_BASE_URL = process.env.NODE_ENV === 'production'
? 'https://pay.kaspi.kz/api/v2'
: 'https://testpay.kaspi.kz/api/v2';
function generateSignature(params) {
const sortedValues = Object.keys(params).sort().map(k => params[k]).join('');
return crypto.createHmac('sha256', KASPI_API_KEY).update(sortedValues).digest('hex');
}
export async function createKaspiOrder(order) {
const params = {
amount: String(Math.round(order.amount)), // тенге, целое
description: order.description,
failUrl: order.failUrl,
orderId: order.orderId, // ваш уникальный ID
returnUrl: order.returnUrl,
tradePointId: KASPI_TRADE_POINT_ID,
};
const body = {
...params,
signature: generateSignature(params),
};
const { data } = await axios.post(`${KASPI_BASE_URL}/orders/create`, body, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${KASPI_API_KEY}`,
},
timeout: 10_000,
});
// data.paymentUrl - редиректим пользователя сюда
// data.orderId - kaspi-side ID, сохраните в БД
return data;
}
// Роут оформления заказа
app.post('/api/checkout', async (req, res) => {
const { amount, cart } = req.body;
const orderId = `ORD-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
// Сохраняем заказ в БД со статусом 'pending'
await db.orders.create({
orderId,
amount,
cart,
status: 'pending',
createdAt: new Date(),
});
try {
const kaspi = await createKaspiOrder({
amount,
orderId,
description: `Заказ ${orderId}`,
returnUrl: `${process.env.SITE_URL}/payment/success?orderId=${orderId}`,
failUrl: `${process.env.SITE_URL}/payment/fail?orderId=${orderId}`,
});
await db.orders.update({ orderId }, { kaspiOrderId: kaspi.orderId });
res.json({ paymentUrl: kaspi.paymentUrl });
} catch (err) {
await db.orders.update({ orderId }, { status: 'error' });
res.status(502).json({ error: 'Не удалось создать платёж Kaspi' });
}
});
Шаг 2: Обработка вебхука
Kaspi отправляет POST-запрос при любом изменении статуса платежа. Настройте URL вебхука в личном кабинете мерчанта.
app.post('/webhooks/kaspi', express.raw({ type: 'application/json' }), async (req, res) => {
let payload;
try {
payload = JSON.parse(req.body.toString());
} catch {
return res.status(400).json({ error: 'Bad JSON' });
}
// Верифицируем подпись
const { signature, ...rest } = payload;
const expected = generateSignature(rest);
if (signature !== expected) {
console.warn('Kaspi webhook: invalid signature', payload);
return res.status(400).json({ error: 'Invalid signature' });
}
const order = await db.orders.findOne({ kaspiOrderId: payload.orderId });
if (!order) return res.status(404).json({ error: 'Order not found' });
switch (payload.status) {
case 'APPROVED':
await db.orders.update(
{ kaspiOrderId: payload.orderId },
{
status: 'paid',
paidAt: new Date(),
kaspiTransactionId: payload.transactionId,
paidAmount: payload.amount,
}
);
await sendConfirmationEmail(order);
await notifyWarehouse(order);
break;
case 'DECLINED':
case 'CANCELLED':
await db.orders.update(
{ kaspiOrderId: payload.orderId },
{ status: payload.status.toLowerCase() }
);
break;
case 'REFUNDED':
await db.orders.update(
{ kaspiOrderId: payload.orderId },
{ status: 'refunded', refundedAt: new Date() }
);
break;
}
// Kaspi ждёт 200 - иначе будет повторная отправка
res.status(200).json({ received: true });
});
Шаг 3: Проверка статуса (polling на returnUrl)
Вебхуки могут задерживаться. Всегда дополнительно проверяйте статус на странице успешной оплаты:
async function getKaspiOrderStatus(kaspiOrderId) {
const { data } = await axios.get(
`${KASPI_BASE_URL}/orders/${kaspiOrderId}/status`,
{ headers: { Authorization: `Bearer ${KASPI_API_KEY}` } }
);
// 'APPROVED' | 'PENDING' | 'DECLINED' | 'CANCELLED' | 'REFUNDED'
return data.status;
}
app.get('/payment/success', async (req, res) => {
const { orderId } = req.query;
const order = await db.orders.findOne({ orderId });
if (!order) return res.redirect('/');
// Не доверяем параметру URL - проверяем напрямую у Kaspi
const kaspiStatus = await getKaspiOrderStatus(order.kaspiOrderId);
if (kaspiStatus === 'APPROVED') {
if (order.status !== 'paid') {
// Вебхук ещё не пришёл - обрабатываем здесь
await db.orders.update({ orderId }, { status: 'paid', paidAt: new Date() });
}
return res.render('success', { order });
}
if (kaspiStatus === 'PENDING') {
// Оплата ещё обрабатывается - показываем экран ожидания
return res.render('pending', { order, pollInterval: 3000 });
}
return res.render('fail', { order });
});
Kaspi Рассрочка (Рассрочка 0-0-12)
Kaspi Рассрочка - убийца продаж. Покупатель платит без процентов (3, 6 или 12 месяцев), вы получаете 100% суммы сразу за вычетом комиссии.
// Создание заказа с поддержкой рассрочки
export async function createOrderWithInstallment(order) {
const params = {
amount: String(Math.round(order.amount)),
description: order.description,
failUrl: order.failUrl,
orderId: order.orderId,
returnUrl: order.returnUrl,
tradePointId: KASPI_TRADE_POINT_ID,
// Разрешаем рассрочку: передаём доступные периоды
installments: JSON.stringify([3, 6, 12]),
};
const body = { ...params, signature: generateSignature(params) };
const { data } = await axios.post(`${KASPI_BASE_URL}/orders/create`, body, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${KASPI_API_KEY}`,
},
});
return data; // paymentUrl откроет страницу с выбором: оплата разом или рассрочка
}
Вебхук для рассрочки содержит дополнительное поле:
{
"status": "APPROVED",
"paymentType": "INSTALLMENT",
"installmentPeriod": 12,
"orderId": "...",
"transactionId": "...",
"amount": 120000
}
Мобильная интеграция: Flutter
// pubspec.yaml
dependencies:
url_launcher: ^6.3.0
http: ^1.2.0
// kaspi_payment_service.dart
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class KaspiPaymentService {
final String _backendUrl;
KaspiPaymentService(this._backendUrl);
/// Создаёт заказ на вашем бэкенде, открывает Kaspi
Future<PaymentResult> startPayment({
required double amount,
required String orderId,
required String description,
}) async {
// 1. Создаём заказ на сервере
final response = await http.post(
Uri.parse('$_backendUrl/api/checkout'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'amount': amount.round(),
'orderId': orderId,
'description': description,
}),
);
if (response.statusCode != 200) {
return PaymentResult.error('Ошибка создания заказа');
}
final data = jsonDecode(response.body);
final paymentUrl = data['paymentUrl'] as String;
// 2. Открываем Kaspi app или браузер
final uri = Uri.parse(paymentUrl);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
return PaymentResult.pending(orderId);
} else {
// Fallback - WebView
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
return PaymentResult.pending(orderId);
}
}
/// Проверяет статус заказа (вызывается при возврате в приложение)
Future<String> checkStatus(String orderId) async {
final response = await http.get(
Uri.parse('$_backendUrl/api/orders/$orderId/status'),
);
final data = jsonDecode(response.body);
return data['status'] as String; // 'paid' | 'pending' | 'failed'
}
}
// Использование в экране оформления заказа
class CheckoutScreen extends StatelessWidget {
final _kaspi = KaspiPaymentService('https://your-backend.kz');
Future<void> _pay(BuildContext context, double total) async {
final orderId = 'APP-${DateTime.now().millisecondsSinceEpoch}';
final result = await _kaspi.startPayment(
amount: total,
orderId: orderId,
description: 'Заказ из приложения',
);
if (result.isPending) {
// Приложение ушло на фон - при возврате проверяем статус
// Используйте AppLifecycleObserver или universal_links
}
}
}
Отслеживание возврата из Kaspi app:
// Подпишитесь на AppLifecycleState в виджете корзины
class _CartState extends State<CartScreen> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed && _awaitingPayment) {
_checkPaymentResult();
}
}
Future<void> _checkPaymentResult() async {
final status = await _kaspi.checkStatus(_currentOrderId);
if (status == 'paid') {
Navigator.pushReplacementNamed(context, '/order-success');
} else if (status == 'failed') {
_showErrorDialog();
}
// 'pending' - ждём ещё
}
}
QR-платёж (для кассы или счёта)
// Генерация QR
async function createKaspiQR(amount, orderId) {
const params = {
amount: String(amount),
orderId,
tradePointId: KASPI_TRADE_POINT_ID,
};
const { data } = await axios.post(`${KASPI_BASE_URL}/qr/create`, {
...params,
signature: generateSignature(params),
}, {
headers: { Authorization: `Bearer ${KASPI_API_KEY}` }
});
// data.qrCode - base64 PNG QR-изображения, отрисуйте на экране кассы
// data.qrToken - токен для polling статуса
return data;
}
// Polling статуса QR (3 секунды × 60 попыток = 3 минуты)
async function waitForQRPayment(qrToken) {
for (let attempt = 0; attempt < 60; attempt++) {
await new Promise(r => setTimeout(r, 3000));
const { data } = await axios.get(
`${KASPI_BASE_URL}/qr/${qrToken}/status`,
{ headers: { Authorization: `Bearer ${KASPI_API_KEY}` } }
);
if (data.status === 'APPROVED') return { success: true, transaction: data };
if (data.status === 'DECLINED') return { success: false, reason: 'declined' };
}
return { success: false, reason: 'timeout' };
}
Возврат денег (Refund API)
async function refundKaspiOrder(kaspiOrderId, amount) {
const params = {
amount: String(amount),
orderId: kaspiOrderId,
};
const { data } = await axios.post(
`${KASPI_BASE_URL}/orders/${kaspiOrderId}/refund`,
{ ...params, signature: generateSignature(params) },
{ headers: { Authorization: `Bearer ${KASPI_API_KEY}` } }
);
return data; // { status: 'REFUNDED', refundId: '...' }
}
Частичный возврат поддерживается - передайте сумму меньше оригинальной. kaspiTransactionId из вебхука при необходимости используется при спорах.
Типичные ошибки
| Код | Причина | Решение |
|---|---|---|
INVALID_SIGNATURE |
Неверный порядок полей или алгоритм | Используйте HMAC-SHA256, сортируйте ключи |
ORDER_ALREADY_EXISTS |
Дублирующийся orderId |
UUID или Date.now() + случайный суффикс |
TRADE_POINT_NOT_FOUND |
Неверный TradePointId |
Проверьте .env, не копируйте пробелы |
AMOUNT_TOO_SMALL |
Сумма меньше 100 ₸ | Валидируйте на клиенте перед отправкой |
INVALID_RETURN_URL |
URL не в белом списке | Добавьте все домены в портале мерчанта |
INSTALLMENT_NOT_ALLOWED |
Рассрочка не подключена | Свяжитесь с менеджером Kaspi для активации |
Чеклист перед продакшном
- Все
returnUrlиfailUrlдомены добавлены в портале мерчанта - URL вебхука зарегистрирован и возвращает 200 в течение 5 секунд
-
kaspiTransactionIdсохраняется в БД (нужен для возвратов) - Идентификаторы заказов уникальны и не повторяются
- Реализован polling как fallback на
returnUrl - Ошибки Kaspi API логируются с деталями запроса
- Переключились с
testpay.kaspi.kzнаpay.kaspi.kz - Секреты хранятся в переменных окружения, не в коде
Нужна помощь с интеграцией Kaspi Pay? Напишите нам →
Aunimeda разрабатывает интернет-магазины, мобильные приложения и платёжные интеграции для бизнеса в Казахстане.
Смотрите также: Разработка интернет-магазина в Алматы, Мобильные приложения Алматы, AI-агенты для бизнеса в Алматы