Решил я написать небольшой док по запуску модной в наших краях системы BankLink (Pangalink). Сподвигло меня на это дело сразу несколько обстоятельств. Во-первых, совершенное отсутствие адекватного руководства на русском языке. Во-вторых отсутствие вообще какого-либо руководства о том что это такое и зачем оно собственно нужно. И в третьих, прикольная тема, работа которой мне очень понравилась. Да и чтобы самому не забыть в будущем.
1. Что это такое?
В Прибалтике набирает силу электронная комерция и как результат возникает логичный вопрос: как платить деньги? Есть много разных вариантов, но все они имеют как свои плюсы, так и минусы.
а) оплата товара по факту доставки. Не очень удобно возиться с наличкой. Возможен вариант что клиент откажется от товара, когда ему уже его привезут, а значит будут неоправданные транспортные расходы. Этот вариант работал лет 7 назад в Латвии. Сам покупал таким образом.
б) оплата посредством кредитной карточки. Далеко не у всех еще есть такие карточки. Оправданная паранноя по поводу кражи данных кредиток уменьшит колличество клиентов.
в) оплата через системы типа PayPal. Тоже не очень удобно, потому что занимает время на трансфер денег со счета фирмы посредника.
г) оплата через месный банк, через систему авторизации. Это он и есть тот самый BankLink. Тут есть тоже минусы: круг клиентов ограничивается клиентами банков, с которыми будет заключен договор.
В идеале, надо было бы снабдить свой магазин всеми вариантами оплаты. Кстати, Skype именно так и делает.
2. Алгоритм работы.
Клиент интернет магазина вводит необходимую сумму на сайте Продавца.
Продавец предлагает оплатить через BankLink того банка, который больше устраивает Покупателя.
Выбрав банк, данные об оплате перенаправляются в банк.
Банк предлагает авторизироваться клиенту и подтвердить платежку.
После подтверждения платежа, банк позвращает определенные данные на страницу Продавца.
3. Код.
Необходимые для рабты скрипты я брал вот тут. Невероятное человеческое спасибо Mihkel-Mikelis Putrinsh
3.1 Форма отправления данных в банк.
Все данные отправляются методом POST через безопасный HTTPS шлюз. Разберем тот набор данных, которые мы посылаем на примере этой формы.
include( 'BankLink.class.php' ); // набор функций, необходимых для данного скрипта, берется тут. $BL =& new BankLink(); $VK_a['VK_SERVICE'] = '1002'; // Это идентификатор типа услуги, которую мы запрашиваем у банка. В данном случае "Оплата счета" $VK_a['VK_VERSION'] = '008'; // Идентификатор типа защиты. В данный момент все банки используют тип 008, а в прошлом были 001, 003, 007 $VK_a['VK_SND_ID'] = 'KAUPMEES'; // Идентификатор продавца. Получается в банке при заключении договора. $VK_a['VK_STAMP'] = $TehinguId; // Идентификатор сессии. Может юыть произвольным и важен больше для продавца, чтобы отследить платежи. $VK_a['VK_AMOUNT'] = $TehinguSumma; // Сумма к оплате. $VK_a['VK_CURR'] = 'EEK'; // Валюта оплаты $VK_a['VK_REF'] = ''; // Ссылка. Некоторые фирмы для разделения платежей на одном счету используют номер ссылки. Если нет, то надо оставить пустым. $VK_a['VK_MSG'] = 'Оплата через BankLink от Покупателя'; // Пояснение оплаты. $VK_a['VK_MAC'] = $BL->createSignature( $VK_a ); // Цифровая подпись, о том как она генерится, поговорим ниже. $VK_a['VK_RETURN'] = 'vk_return.php'; // страница, куда банк будет возвращать после подтвержения оплаты. $VK_a['VK_CANCEL'] = 'vk_return.php'; // страница, куда банк будет возвращать после прерывания оплаты. Если это та же страница, что и VK_RETURN, то этот идентификатор можно не выставлять. $VK_a['VK_LANG'] = 'EST'; // Продпочтительный язык для платежки echo( '' );
3.2. Форма получения данных из банка.
Все данные толучаются из банка методом POST через безопасный HTTPS шлюз. Как это делать посмотрим на примере.
include( 'BankLink.class.php' ); // набор функций, необходимых для данного скрипта, берется отсюда http://comeback.webhost.ee/pangalink/BankLink.class.php.txt. $BL = new BankLink(); $sig_result = $BL->verifySignature( $_POST ); // Проверка подписи. Как это делается, обсудим ниже. switch ( $sig_result ) { case 1: switch( $_POST['VK_SERVICE'] ) // Проверка типа кода от банка. { case '1101': // Оплата прошла успешно echo( 'Оплата прошла!' ); break; case '1901': // Оплата была прервана Покупателем echo( 'Плата прервана!' ); break; default: // В случае другого кода (маловероятно, но мало ли) echo( 'Всякая белиберда' ); break; } break; case 0: // В случае не прохождения проверки подписи банка. echo( 'Не правильные данные банка' ); break; default: // В случае другого кода (маловероятно, но мало ли) echo( 'Всякая белиберда' ); break; }
4. Безопасность.
Безопасность в нашем деле превыше всего. Как же она осуществляется?
4.1 Продавец посылает данные в банк.
В параграфе 3.1 мы посылали переменную VK_MAC. Она создается следующим образом:
Значение функции МАС008 вычисляется при помощи алгоритма публичного ключа RSA. В учет также принимается длина пустых полей – «000».
MAC008(x1,x2,…,xn) := RSA( SHA-1(p(x1 )|| x1|| p(x2 )|| x2 || … ||p( xn )||xn),d,n)
где:
|| представляет собой действие по сложению строки /стринга/
x1, x2, …, xn являются параметрами запроса
p функция длины параметра. Длина – это номер в виде трехместной строки
d тайный экспонент RSA
n является модулем RSA
Вычисление сигнатуры проводится в соответствии со стандартом PKCS1 (RFC 2437).
Это выдержка с официального сайта ХансаПанка.
В нашем случае это выглят таким образом:
Методов добыть подпись есть немало. Один можно увидеть в этом файле, другой я распишу сам.
$mac_pre = ''; $reversed = array_reverse($VK_a); foreach ($reversed as $name=>$value) {// в это цикле мы создаем набор данных для подписи. Она будет содержать все те данные, которые мы обозначили выше в array VK_A. $value_len = strlen($value); if (strlen($value_len) < 3) { while (strlen($value_len) < 3) { $value_len = '0'.$value_len; } } $mac_pre = $value_len.$value.$mac_pre; } $fp = fopen('privkey.pem', 'r'); // открываем личный ключ в режиме чтения $private_key = fread($fp, 8192); fclose($fp); $private_key_id = openssl_get_privatekey($private_key, 'пароль'); //Получаем идентификационный номер ключа openssl_sign($mac_pre, $mac, $private_key_id); Создаем нашу подпись, подписанную нашим ключем. $mac = base64_encode($mac); кодируем в base64
Полученную таким образом подпись мы посылаем в банк вместе с другими данными. В процессе заключения договора Продавец послал Банку публичный ключ. И вот с помощью этого ключа Банк будет распознавать правильность нашей подписи.
4.2 В параграфе 3.2 мы проверяли правильность подписи Банка. Для этой цели банк выдает нам свой публичный ключ.
Я разберу метод проверки на этом примере.
var $keyDir = "/webroot/osslkeys"; // место расположения публичных ключей. function verifySignature( $VK_a ) { $VK_MAC = $VK_a['VK_MAC']; // получаем значение подписи банка $signature = base64_decode( $VK_MAC ); //декодируем из base64 switch( $VK_a['VK_SERVICE'] ) { case '1101': // в случае удачной оплаты счета получаем код 1101 $data = $this->_padIt( $VK_a['VK_SERVICE'] ) . // Код услуги $this->_padIt( $VK_a['VK_VERSION'] ) . // Идентификатор типа защиты $this->_padIt( $VK_a['VK_SND_ID'] ) . // Идентификатор банка (Например, ХансаПанк имеет идентификатор HP) $this->_padIt( $VK_a['VK_REC_ID'] ) . // Идентификатор Продавца (это будет тот же идентификатор, который мы посылали в нашем запросе) $this->_padIt( $VK_a['VK_STAMP'] ) . // Идентификатор сессии (это сессия банка, а не Продавца) $this->_padIt( $VK_a['VK_T_NO'] ) . // Номер платежного поручения $this->_padIt( $VK_a['VK_AMOUNT'] ) . // Сумма оплаченная $this->_padIt( $VK_a['VK_CURR'] ) . // Валюта $this->_padIt( $VK_a['VK_REC_ACC'] ) . // Номер счета Продавца $this->_padIt( $VK_a['VK_REC_NAME'] ) . // Наименование (Фамилия) Продавца $this->_padIt( $VK_a['VK_SND_ACC'] ) . // Номер счета Покупателя $this->_padIt( $VK_a['VK_SND_NAME'] ) . // Наименование (Фамилия) Покупателя $this->_padIt( $VK_a['VK_REF'] ) . // Номер ссылки $this->_padIt( $VK_a['VK_MSG'] ) . // Пояснение $this->_padIt( $VK_a['VK_T_DATE'] ); // Дата платежа break; case '1901': // в случае не удачной оплаты счета получаем код 1901 $data = $this->_padIt( $VK_a['VK_SERVICE'] ) . // Код услуги $this->_padIt( $VK_a['VK_VERSION'] ) . // Идентификатор типа защиты $this->_padIt( $VK_a['VK_SND_ID'] ) . // Идентификатор банка (Например, ХансаПанк имеет идентификатор HP) $this->_padIt( $VK_a['VK_REC_ID'] ) . // Идентификатор Продавца (это будет тот же идентификатор, который мы посылали в нашем запросе) $this->_padIt( $VK_a['VK_STAMP'] ) . // Идентификатор сессии (это сессия банка, а не Продавца) $this->_padIt( $VK_a['VK_REF'] ) . // Номер ссылки $this->_padIt( $VK_a['VK_MSG'] ); // Пояснение break; } $cert = $this->_readPubKey( $VK_a['VK_SND_ID'] ); // Читаем публичный ключ. Я $pubkey = openssl_get_publickey( $cert ); // получаем идентификационный номер ключа. $out = openssl_verify( $data, $signature, $pubkey ); // проверка подписи openssl_free_key( $pubkey ); return $out; } function _readPubKey( $VK_SND_ID = '' ) { $key_file = $this->keyDir . "/$VK_SND_ID/pubkey.pem"; // расположил публичные ключи с одинаковым названием в директориях с названиями по идентификаторам банка. Таким образом я решил вопрос с поддержкой нескольких разных банков. $fp = fopen( $key_file, "r" ); $cert = fread( $fp, 8192 ); fclose( $fp ); return $cert; }
5. Вот и все.
Вот собственно и все хитрости, о которых я хотел поведать.
В процессе исследования этой функциональности не пострадало ни одно животное.
Была использована информация со следующих сайтов:
HansaPank - техническое описание.
SEB EESTI Ühispank - техническая спецификация.
Sampo Pank - банковская ссылка.
Mihkel-Mikelis Putrinsh