Qut Pay Сайт Кабинет База знаний Инструкции Документация API ҚАЗРУС
ГлавнаяБаза знаний → Справочник

PHP SDK

Обновлено: 2026-09-14 · Версия в Markdown

Коротко

PHP SDK — это один файл: src/QutPay.php. Работает на PHP 7.4 и новее с расширениями curl и json, других зависимостей нет. Подключается и через Composer, и обычным require.

Внутри три класса:

КлассДля чего
\QutPay\ClientВызовы API: создать счёт, прочитать, отменить, вернуть
\QutPay\WebhookПроверка подписи входящего уведомления
\QutPay\ApiExceptionОшибка: ->status (HTTP) и ->errorCode (машинный код)

Архив: https://api.qut.kz/downloads/qutpay-sdk-php.zip

Установка

Самый простой путь — скопировать src/QutPay.php из архива к себе и подключить напрямую:

require __DIR__ . '/vendor/qutpay/QutPay.php';

Если пользуетесь Composer, распакуйте архив в отдельную папку и подключите как локальный репозиторий:

{
  "repositories": [{ "type": "path", "url": "./vendor-local/qutpay-sdk" }],
  "require": { "qutpay/sdk": "*" }
}

Имя пакета — qutpay/sdk, автозагрузка PSR-4 настроена на пространство имён QutPay\.

Инициализация

$qp = new \QutPay\Client(getenv('QUTPAY_API_KEY'));       // baseUrl по умолчанию https://api.qut.kz
// При необходимости: new \QutPay\Client($key, 'https://api.qut.kz', 20);  // третий аргумент — таймаут в секундах

С пустым ключом конструктор бросит InvalidArgumentException. Не держите ключ в коде — читайте его из переменной окружения или конфига, который не попадает в репозиторий.

Создание счёта

$inv = $qp->createInvoice([
    'amount'          => 12500,
    'kind'            => 'qr',              // 'qr' (по умолчанию) или 'phone'
    'description'     => 'Заказ №4471',
    'externalOrderId' => '4471',
    'customer'        => ['name' => 'Айгуль', 'phone' => '77011234567', 'email' => 'a@b.kz'],
    'successUrl'      => 'https://site.kz/ok',
    'failUrl'         => 'https://site.kz/fail',
    'metadata'        => ['branch' => 'almaty-abay'],
    'idempotencyKey'  => 'order-4471',      // уходит в заголовок Idempotency-Key
]);

header('Location: ' . $inv['payUrl']);
exit;

Ответ — ассоциативный массив: id, status, payUrl, qrUrl, deepLink, qrImageUrl, expiresAt.

Остальные методы

МетодЧто делает
$qp->getInvoice($id)Читает счёт. $qp->getInvoice($id, true) дополнительно опрашивает Kaspi и обновляет статус
$qp->listInvoices(['status' => 'paid', 'limit' => 50])Список. Фильтры: status, externalOrderId, search, from, to, limit, offset
$qp->cancelInvoice($id)Отменяет открытый счёт
$qp->refundInvoice($id, $amount = null, $reason = null)Возврат. $amount = null — полный
$qp->simulateInvoice($id, 'paid')Только песочница: имитация оплаты
$qp->account()Краткая информация об организации — удобно для проверки ключа
$fresh = $qp->getInvoice($inv['id'], true);
if ($fresh['status'] === 'paid') {
    markOrderPaid('4471');
}

$qp->refundInvoice($inv['id'], 5000, 'Часть товара возвращена');

В SDK нет методов для подписок, bulk и форм-хуков. Вызывайте их напрямую через curl — эндпоинты описаны на api.qut.kz/docs.

Вебхук: чистый PHP

<?php
require __DIR__ . '/QutPay.php';

$secret = getenv('QUTPAY_WEBHOOK_SECRET');
$raw    = file_get_contents('php://input');   // нетронутое тело

if (!\QutPay\Webhook::verify($secret, $raw, getallheaders())) {
    http_response_code(401);
    exit;
}

$e       = json_decode($raw, true);
$event   = $e['event'] ?? '';
$invoice = $e['invoice'] ?? [];

// Идемпотентность: событие может прийти дважды
if (!alreadyHandled($invoice['id'], $invoice['status'])) {
    if ($event === 'invoice.paid')     markOrderPaid($invoice['externalOrderId'], $invoice);
    if ($event === 'invoice.refunded') markOrderRefunded($invoice['externalOrderId']);
    rememberHandled($invoice['id'], $invoice['status']);
}

http_response_code(200);   // без 2xx доставка повторится 11 раз

Webhook::verify($secret, $rawBody, $headers, $toleranceSec = 300) ищет заголовки без учёта регистра, проверяет префикс sha256=, отбрасывает доставки старше 5 минут и сравнивает через hash_equals.

В некоторых конфигурациях FastCGI функции getallheaders() нет. Тогда соберите заголовки вручную:

$headers = [
    'X-Webhook-Signature' => $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '',
    'X-Webhook-Timestamp' => $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '',
];

Вебхук: Laravel

В Laravel тело берите через $request->getContent() — он возвращает исходную строку. $request->all() не подходит.

// routes/api.php
Route::post('/qutpay-webhook', [QutPayController::class, 'handle']);
// app/Http/Controllers/QutPayController.php
use Illuminate\Http\Request;

class QutPayController extends Controller
{
    public function handle(Request $request)
    {
        $raw = $request->getContent();
        $ok  = \QutPay\Webhook::verify(
            config('services.qutpay.webhook_secret'),
            $raw,
            $request->headers->all()        // значения массивами — verify это понимает
        );
        if (!$ok) {
            return response()->noContent(401);
        }

        $e = json_decode($raw, true);
        ProcessQutPayEvent::dispatch($e);   // тяжёлую работу — в очередь

        return response()->noContent(200);
    }
}

Не забудьте про две вещи:

  1. CSRF. Держите маршрут в routes/api.php либо добавьте его в $except у VerifyCsrfToken — иначе Laravel вернёт 419, а мы посчитаем доставку неуспешной и начнём повторять.
  2. Быстрый ответ. Тяжёлую логику (письма, пересчёт отчётов) уводите в очередь, а 2xx возвращайте сразу.

В контроллере создания счёта:

$qp  = new \QutPay\Client(config('services.qutpay.key'));
$inv = $qp->createInvoice([
    'amount'          => $order->total,
    'description'     => "Заказ №{$order->id}",
    'externalOrderId' => (string) $order->id,
    'successUrl'      => route('orders.show', $order),
    'idempotencyKey'  => "order-{$order->id}",
]);

return redirect()->away($inv['payUrl']);

Обработка ошибок

На любой неуспешный ответ SDK бросает \QutPay\ApiException:

ПолеЗначение
->statusHTTP-статус. При сетевой ошибке — 0
->errorCodeМашинный код: invalid_amount, invoice_not_found, request_rate_limited… При обрыве сети network_error
->getMessage()Текст для человека
try {
    $inv = $qp->createInvoice([...]);
} catch (\QutPay\ApiException $e) {
    switch ($e->errorCode) {
        case 'kaspi_session_expired':
            notifyAdmin('Привязка кассира оборвалась');
            break;
        case 'tariff_limit_reached':
        case 'tariff_daily_burst':
            notifyAdmin('Лимит');          // повторять бессмысленно
            break;
        case 'request_rate_limited':
        case 'network_error':
            retryLater();                   // бэкофф 1, 2, 4, 8 секунд
            break;
        default:
            report($e);
    }
    abort(502);
}

Решение принимайте по errorCode: текст может измениться, код — нет. Полный список: Каталог ошибок.

Вопросы и ответы

Можно поставить из Packagist? Нет, пакет не опубликован в публичном реестре. Скачайте архив и подключите как репозиторий типа path либо просто скопируйте файл.

Нужен ли Guzzle? Нет. SDK использует только расширение curl.

У меня магазин на WordPress, нужен ли этот SDK? Нет. Для WooCommerce есть готовый плагин: Плагин WooCommerce.

Как получить raw body в Symfony? Так же, как в Laravel: $request->getContent() и $request->headers->all().

Подпись не сходится. Значит тело где-то изменилось: не пересобирайте его через json_decode/json_encode, проверьте, что кеш и защитные плагины не трогают тело запроса. Подробнее: Подпись вебхука не сходится.

Связанные статьи

Создание счёта: все поляПолный справочник по POST /api/v1/invoices — тип и ограничение каждого поля, все поля ответа, примеры на curl и Node, разница между qr и phone и список частых ошибок с решениями.Безопасность вебхуков и проверка подписиКак устроена подпись, почему обязателен raw body, как проверять timestamp, примеры кода для Express, Laravel, Django и чистого Node, идемпотентная обработка и разбор частых ошибок.API возвратов — полный и частичный возвратСправочник по методу POST /invoices/{id}/refund: поля запроса, полный и частичный возврат, ограничение суммы, все коды ошибок, что делать при refund_unknown и какие события приходят после возврата.Каталог ошибок — что возвращает API и что делатьВсе основные коды ошибок Qut Pay API по группам: авторизация, привязка Kaspi, счета, возвраты, лимиты тарифа, вебхуки, подписки. Причина и решение для каждой.Плагин WooCommerceКак установить плагин Qut Pay в магазин на WordPress, настроить API-ключ и вебхук, как меняются статусы заказа, как делать возвраты, где чек и что делать при типовых сбоях.

Остались вопросы? WhatsApp +77788813333 · kazprose@gmail.com
Написать можно и из кабинета: Поддержка.

Qut Pay — независимый сервис, не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.