Как работать с Halyk Bank API для онлайн-платежей в Казахстане (2015)
Коротко: EPAY.kz (Halyk Bank эквайринг) работает по redirect-модели: вы создаёте форму с параметрами заказа, клиент переходит на страницу банка для оплаты, банк возвращает клиента на ваш сайт и отправляет postback. Проверяйте подпись postback через RSA публичный ключ банка.
Что такое EPAY.kz в 2015
EPAY - платёжный шлюз Народного Банка Казахстана. Принимал VISA, Mastercard, AmericanExpress. Интеграция через два метода:
- Form POST - форма HTML → редирект на страницу банка
- XML API - для серверного взаимодействия (более сложный)
В 2015 году для интернет-магазинов обычно использовался Form POST.
Form POST интеграция
<?php
// payment/Epay.php
class Epay {
private string $merchantId; // Выдаётся банком
private string $terminalId; // Выдаётся банком
private string $backRefUrl; // URL возврата после оплаты
private bool $testMode;
private string $gatewayUrl;
public function __construct(
string $merchantId,
string $terminalId,
string $backRefUrl,
bool $testMode = false
) {
$this->merchantId = $merchantId;
$this->terminalId = $terminalId;
$this->backRefUrl = $backRefUrl;
$this->testMode = $testMode;
$this->gatewayUrl = $testMode
? 'https://epay-test.kkb.kz/jsp/process/logon.jsp'
: 'https://epay.kkb.kz/jsp/process/logon.jsp';
}
/**
* Сгенерировать HTML-форму для оплаты
*/
public function createPaymentForm(array $order): string {
$amount = number_format($order['total'], 2, '.', '');
$orderId = str_pad($order['id'], 6, '0', STR_PAD_LEFT); // "000042"
$currency = '398'; // KZT по ISO 4217
$description = 'Заказ #' . $order['id'];
// Создать XML-строку для подписи (специфика EPAY)
$xml = '<document>'
. '<merchant cert_id="' . $this->merchantId . '" name="' . $this->merchantId . '">'
. '<order order_id="' . $orderId . '" currency="' . $currency . '" amount="' . $amount . '">'
. '<department merchant_id="' . $this->terminalId . '" amount="' . $amount . '"/>'
. '</order>'
. '</merchant>'
. '</document>';
$signedXml = $this->signXml($xml);
$params = [
'Signed_Order_B64' => base64_encode($signedXml),
'BackRef' => $this->backRefUrl . '?orderId=' . $order['id'],
'email' => $order['email'] ?? '',
'PostLink' => 'https://myshop.kz/payment/epay/postback',
'FailurePostLink' => 'https://myshop.kz/payment/epay/postback',
'Language' => 'rus', // rus или kaz
];
$html = '<form method="POST" action="' . $this->gatewayUrl . '" id="epay-form">' . "\n";
foreach ($params as $name => $value) {
$html .= '<input type="hidden" name="' . htmlspecialchars($name)
. '" value="' . htmlspecialchars($value) . '">' . "\n";
}
$html .= '<button type="submit">Оплатить картой</button>' . "\n";
$html .= '</form>' . "\n";
return $html;
}
/**
* Обработка postback от EPAY
* Bank отправляет POST с response_code
*/
public function processPostback(array $post): bool {
// EPAY отправляет XML в поле 'response'
$responseXml = $post['response'] ?? '';
if (empty($responseXml)) return false;
$xml = simplexml_load_string(base64_decode($responseXml));
if (!$xml) return false;
// Проверить подпись через RSA открытый ключ банка
if (!$this->verifySignature($responseXml)) {
error_log('EPAY postback: invalid signature');
return false;
}
$responseCode = (string)($xml->bank->results->payment['response_code'] ?? '-1');
return $responseCode === '00'; // '00' = успешная оплата
}
private function signXml(string $xml): string {
// Подпись через приватный ключ магазина (выдаётся банком как .pfx)
$pfxPath = '/etc/ssl/epay/merchant.pfx';
$pfxPassword = EPAY_PFX_PASSWORD;
$p12 = file_get_contents($pfxPath);
openssl_pkcs12_read($p12, $certs, $pfxPassword);
$privateKey = $certs['pkey'];
openssl_sign($xml, $signature, $privateKey, OPENSSL_ALGO_SHA1);
// Добавить подпись в XML
return $xml . '<signature>' . base64_encode($signature) . '</signature>';
}
private function verifySignature(string $encodedResponse): bool {
// Проверить подпись ответа через публичный ключ банка
$bankCertPath = '/etc/ssl/epay/bank.cer';
$bankCert = file_get_contents($bankCertPath);
$bankPubKey = openssl_get_publickey($bankCert);
$xml = simplexml_load_string(base64_decode($encodedResponse));
if (!$xml) return false;
$signatureB64 = (string)($xml->signature ?? '');
$signature = base64_decode($signatureB64);
// Убрать тег signature из XML перед проверкой
$xmlWithoutSig = preg_replace('/<signature>.*?<\/signature>/s', '', base64_decode($encodedResponse));
$result = openssl_verify($xmlWithoutSig, $signature, $bankPubKey, OPENSSL_ALGO_SHA1);
return $result === 1;
}
}
Контроллер оплаты
<?php
// Показать форму оплаты
public function showEpayForm(int $orderId): void {
$order = OrderRepository::findForUser($orderId, currentUserId());
$epay = new Epay(
EPAY_MERCHANT_ID,
EPAY_TERMINAL_ID,
'https://myshop.kz/payment/epay/return',
!IS_PRODUCTION
);
$form = $epay->createPaymentForm($order);
// Сохранить факт инициации
OrderRepository::update($orderId, ['status' => 'payment_initiated', 'payment_method' => 'epay']);
// Отобразить промежуточную страницу с формой
renderView('payment/epay', ['form' => $form, 'order' => $order]);
}
// Postback - вызывается банком
public function postback(): void {
$epay = new Epay(EPAY_MERCHANT_ID, EPAY_TERMINAL_ID, '...');
$orderId = (int)($_GET['orderId'] ?? 0);
$success = $epay->processPostback($_POST);
$order = OrderRepository::find($orderId);
if ($success && $order && $order['status'] === 'payment_initiated') {
$xml = simplexml_load_string(base64_decode($_POST['response']));
$approvalCode = (string)($xml->bank->results->payment['approval_code'] ?? '');
OrderRepository::markAsPaid($orderId, [
'payment_method' => 'epay',
'payment_approval' => $approvalCode,
'paid_at' => date('Y-m-d H:i:s'),
]);
}
// Банк ожидает HTTP 200 - иначе повторит postback
http_response_code(200);
echo 'OK';
}
Сравнение EPAY vs Kaspi Pay vs наличные (2015)
| EPAY (Halyk) | Kaspi Pay | Наличные курьеру | |
|---|---|---|---|
| Комиссия | 2-2.5% | 1.2-1.8% | 0% |
| Охват | 25% аудитории | 45% аудитории | 100% |
| Возраст аудитории | 30-55 лет | 18-40 лет | Все |
| Риск невыкупа | 0 | 0 | 15-25% |
| Время интеграции | 2-3 дня | 1-2 дня | 0 |
Наш рекомендуемый стек 2015 года: Kaspi Pay + EPAY + наличные при доставке. Три метода покрывали 95%+ потенциальных покупателей.