Оплата через Kapital Bank на PHP и Laravel: kapitalbank-checkout
Открытый PHP-пакет для Kapital Bank / Birbank Checkout API: создание платежа, статус, возвраты, проверка вебхуков и интеграция с Laravel.
Приём оплаты картой через Kapital Bank означает работу с Kapital Bank / Birbank Checkout API: получить OAuth2-токен, создать платёж, отправить покупателя на страницу банка, проверить результат, обработать возвраты и проверить уведомления банка. Всё это мы собрали в открытую PHP-библиотеку learning-machine/kapitalbank-checkout. В статье показываем, как с её помощью принимать оплату Kapital Bank на PHP и Laravel и на какие детали стоит обратить внимание.
Что умеет пакет
- OAuth2-токен с кешированием через любой PSR-16 кеш, чтобы токен не запрашивался при каждом вызове.
- Создание платежа и перенаправление покупателя на платёжную страницу банка.
- Статус платежа: Pending, Succeeded, Canceled или Unknown.
- Полные и частичные возвраты.
- Проверка вебхуков: подпись HMAC-SHA256 в base64 в заголовке
X-Signature. - Ядро, не зависящее от фреймворка, на стандартах PSR-18, PSR-17 и PSR-16, и необязательная интеграция с Laravel, подключаемая автоматически.
Нужен PHP 8.1 или новее, лицензия MIT.
Установка
Пакет опубликован на Packagist, поэтому он ставится одной командой Composer:
composer require learning-machine/kapitalbank-checkout
Создание платежа на чистом PHP
Создайте клиент со своими учётными данными. В примере используется тестовая среда https://precheckout.kapitalbank.az/api; в рабочей среде базовый URL — https://checkout.kapitalbank.az/api.
use LearningMachine\KapitalBank\Checkout\Amount;
use LearningMachine\KapitalBank\Checkout\CheckoutClient;
$client = CheckoutClient::fromConfig([
'base_url' => 'https://precheckout.kapitalbank.az/api',
'client_id' => getenv('KAPITALBANK_CLIENT_ID'),
'client_secret' => getenv('KAPITALBANK_CLIENT_SECRET'),
'merchant_id' => getenv('KAPITALBANK_MERCHANT_ID'),
'terminal_id' => getenv('KAPITALBANK_TERMINAL_ID'),
], cache: $psr16Cache);
$payment = $client->createPayment(
Amount::fromDecimal('12.50'),
'Order #123',
'https://shop.example/payment/return',
$idempotencyKey
);
// сохраните $payment->id и $payment->idempotencyKey вместе с заказом
header('Location: ' . $payment->redirectUrl());
Суммы — это объекты-значения: Amount::fromDecimal('12.50') или Amount::fromMinor(1250) для той же суммы в минимальных единицах (гяпиках). Валюта по умолчанию — AZN. Передача десятичной суммы строкой избавляет от сюрпризов с плавающей точкой.
Ключ идемпотентности — уникальное значение, которое вы генерируете для этой попытки оплаты и храните вместе с заказом. Именно он делает возможным безопасный повтор, о чём ниже.
Проверка статуса
Когда покупатель возвращается на ваш URL возврата, не доверяйте одному лишь редиректу: спросите банк.
$p = $client->getPayment($paymentId);
$p->status; // Status::Pending, Status::Succeeded, Status::Canceled или Status::Unknown
Только Succeeded означает, что деньги списаны. Для Unknown исходная строка от банка доступна в $p->rawStatus.
Возвраты
$refund = $client->refund($paymentId, Amount::fromDecimal('2.50'), $refundRowUuid);
Переданная сумма определяет, сколько вернётся: вся сумма или её часть. Третий аргумент — UUID записи о возврате в вашей собственной базе данных. Важная деталь: публичный справочник API описывает только объект возврата, но не запрос. Тело, которое принимает рабочий API, — {"id": "<payment uuid>", "amount": 2.5}, и именно его отправляет пакет; $refund->requestBody покажет его, если вы хотите записать его в лог.
Вебхуки
Банк уведомляет ваш сервер о событиях платежа. Проверьте подпись, прежде чем доверять чему-либо в теле запроса:
use LearningMachine\KapitalBank\Checkout\Webhook\WebhookParser;
$parser = new WebhookParser(getenv('KAPITALBANK_WEBHOOK_SECRET'));
$event = $parser->parse($rawBody, $request->headers->get('X-Signature'));
if ($event->isSucceeded()) {
// идемпотентно отметьте заказ оплаченным
}
// всегда отвечайте HTTP 200, иначе банк доставит уведомление повторно
Передавайте сырое тело запроса в точности как получено; заново закодированная JSON-строка не совпадёт с подписью. При неверной подписи parse() выбрасывает исключение InvalidWebhook. На корректные события всегда отвечайте HTTP 200, в том числе на отменённые платежи, и делайте зачисление идемпотентным, потому что одно и то же событие может прийти несколько раз.
Ошибки, важные для денег
use LearningMachine\KapitalBank\Checkout\Exception\AuthenticationFailed;
use LearningMachine\KapitalBank\Checkout\Exception\BankRejected;
use LearningMachine\KapitalBank\Checkout\Exception\OutcomeUnknown;
try {
$payment = $client->createPayment($amount, $description, $returnUrl, $idempotencyKey);
} catch (BankRejected $e) {
// банк ответил ошибкой; сообщите банку $e->error->traceId
// для ответов 5xx $e->isServerError() возвращает true
} catch (OutcomeUnknown $e) {
// тайм-аут или обрыв соединения: банк МОГ обработать запрос
} catch (AuthenticationFailed $e) {
// не удалось получить токен; ничего, связанного с деньгами, не отправлено
}
OutcomeUnknown — случай, который нужно продумать заранее. Запрос мог дойти до банка, а мог и не дойти, поэтому никогда не создавайте новый платёж вслепую. Либо повторите запрос с тем же ключом идемпотентности, либо проверьте платёж через getPayment(). В BankRejected есть traceId, по которому поддержка банка найдёт ваш запрос.
Интеграция с Laravel
В Laravel сервис-провайдер подключается автоматически. Опубликуйте конфигурацию:
php artisan vendor:publish --tag=kapitalbank-config
и заполните .env:
KAPITALBANK_BASE_URL=https://precheckout.kapitalbank.az/api
KAPITALBANK_CLIENT_ID=
KAPITALBANK_CLIENT_SECRET=
KAPITALBANK_MERCHANT_ID=
KAPITALBANK_TERMINAL_ID=
KAPITALBANK_WEBHOOK_SECRET=
После этого внедряйте CheckoutClient или WebhookParser там, где они нужны; оба зарегистрированы как синглтоны. В контроллере вебхука читайте сырое тело через $request->getContent() и не забудьте исключить маршрут вебхука из CSRF-защиты. Пакет также работает с Http::fake() в Laravel, так что процесс оплаты можно покрыть тестами без обращений к банку.
Используйте сами или доверьте интеграцию нам
Пакет открыт на GitHub; issues и pull requests приветствуются. Подробнее — на странице проекта и в нашем разделе open source. Если нужна помощь с интеграцией оплаты в ваш сайт или систему, опишите задачу.
- PHP
- Laravel
- Payments
- Open Source