Перейти к содержимому
Learning Machine

Оплата через Kapital Bank на PHP и Laravel: kapitalbank-checkout

Оплата через 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

Связаться с нами

Отправьте нам Ваш Проект или Идею для расчёта сроков и стоимости. Мы рады любым Вашим идеям!

Rasul Rza 75, Баку, Азербайджан
[email protected]

Сайт защищён reCAPTCHA; действуют Политика конфиденциальности и Условия использования Google.

Отправляя форму, вы соглашаетесь с политикой конфиденциальности.