Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #135, МАРТ 2010 г.

Настраиваем прием платежей на своем сайте

Сергей «gurza» Попиневский brain@bidiko.ru


Есть идея! Да, совершенно точно этот проект сможет принести деньги! Пусть небольшие, но деньги. Но стоит ли затевать весь сыр-бор, если я не смогу организовать прием платежей? Будут ли связываться со мной крупные платежные системы или отправят лесом? Да и насколько сложно это технически? Вот и разберемся на практике.

Помнится, много лет назад я хотел организовать прием платежей через Webmoney. Все желание пропало, когда я прочитал, сколько волокиты с этим связано: невнятные технические решения, необходимость получения сертификата. Словом, игра не стоила свеч. С тех пор, конечно, многое изменилось, но проблемы отчасти остались те же. Если говорить о серьезном проекте с большими инвестициями, то проблем в сотрудничестве с платежными системами быть не должно. Но совсем другое дело – скажем, совсем небольшой Интернет-магазин, у которого на первых порах совсем нет оборота. А его требуют, выдвигают условия и, более того, заставляют пройти все круги ада, оформляя всевозможные документы.

Вот и получается логический тупик: пока у тебя нет нормальной клиентской базы и репутации, ты не можешь полноценно сотрудничать с разными платежными системами. А пока у тебя нет возможности принимать платежи, о какой клиентской базе может идти речь? К счастью, есть приятные исключения. Если ты прочитал статью «Налаживаем систему приема платежей» (или ее PDF-версию на нашем диске), то должен знать, каким образом можно принять платеж по кредитной карте или по SMS. Особо тепло мы отзывались о Robokassa'е (www.robokassa.ru), позволяющей избавиться от геморроя общения напрямую с разными системами оплаты. Задумайся, хочется ли тебе договариваться по отдельности с каждой системой электронных платежей, получать какие-то непонятные сертификаты, высылая кипу юридических бумаг, и ожидая, что какой-нибудь манагер проверит, все ли ты выслал и правильно ли заполнил анкеты. За небольшой процент с каждой транзакции можно одним махом подключить самые разные варианты платежей, предоставив пользователям максимальное удобство и право выбора.

Хочешь прием платежей через электронные деньги (Янднекс.Деньги, Webmoney и другие)? Запросто! Хочешь предложить самый простой вариант оплаты – через SMS? Тоже нет проблем. Для многих оплата по кредитной карте стала нормой – и ты можешь это предложить. Ах да, забыли про пресловутые терминалы для оплаты? Поддерживаются, сразу 9 различных сетей. И это далеко не весь список.

При этом ты ведешь бухгалтерию только с одной системой, предоставляешь пользователям удобный единый интерфейс – и отдаешь 5% с продаж за отсутствие головной боли. Но в данной ситуации, не менее важно и другое. Система крайне лояльно хорошо относится к небольшим проектам, в том числе самым начинающим! А значит, имея желание, прямые руки, минимальное знание PHP (+ curl) и фрейморка jQuery, тебе под силу поднять свой маленький бизнес в Сети.

Организация платежа через RoboKassa

В общем-то, язык программирования совсем необязательно должен быть PHP, но мы возьмем его для простоты примера. Взаимодействие с RoboKassa осуществляется через специальный API-интерфейс. Другими словами, есть несложные правила обмена данными между электронным магазином и сервисом для приема. Если следовать правилам, то работать с системой можно как угодно: будь у тебя скрипты на PHP, Perl или, скажем, ASP или Python – неважно.

Один из способов передать сообщение сервису – сформировать HTTP-запрос и передать его методом GET или POST по специальному URL’у https://merchant.roboxchange.com. Далее магазин отправляет пользователя по данному адресу для произведения им оплаты. Общая схема взаимодействия магазина и платежки RoboKassa выглядит следующим образом:

  1. Клиент магазина переходит по специальному URL’у и оказывается на сайте RoboKassa, еще раз читает все параметры заказа и сверяет стоимость, после чего подтверждает оплату.
  2. RoboKassa обменивается данными с той платежной системой, через которую хочет произвести оплату клиент. Данный процесс скрыт от наших глаз. В общем-то, ту комиссию, которую взымает RoboKassa, мы платим как раз за то, чтобы не задумываться, как реально происходит транзакция с конечной платежной системой.
  3. RoboKassa отправляет подтверждение об оплате Result-скрипту магазина. Если клиент отказался от оплаты, то он будет перенаправлен по URL-адресу страницы Fail (здесь ты можешь попытаться выяснить у клиента, что же ему не понравилось или что не получилось), а в случае успешного проведения платежа – страницы Success (обязательно поблагодари клиента и пожелай ему всего хорошего).

URL-адреса Result-скрипта и страниц Success, Fail могут быть указаны и изменены в личном кабинете на сайте RoboKassa в любое время.
Теперь о каждом пункте подробнее:

1.

В листинге ниже приведен алгоритм формирования URL-адреса, по которому мы должны перенаправить клиента для совершения оплаты:

//Номер заказа
$inv_id = 0;
//Дополнительные параметры запроса
$shp_item = $item;
$shp_user = 'TestUser';
//подпись
$crc = md5("$mrh_login:$out_summ:$inv_id:$mrh_pass1:Shp_item=$shp_item:Shp_user=$shp_user");
//формумируем URL
$url = "https://merchant.roboxchange.com/Index.aspx?MrchLogin=$mrh_login&OutSum=
$out_summ&InvId=$inv_id&Desc=$inv_desc&Shp_item=$shp_item&Shp_user=$shp_user&SignatureValue=$crc";

Разберемся с каждым из параметров запроса, передаваемого методом GET.

  • MrchLogin – логин мерчанта в системе RoboKassa. В исходниках используется тестовый логин – demo.
  • Параметр OutSum – стоимость товара/заказа в магазине.
  • Параметр InvId – номер заказа в магазине. Если передать ноль в качестве значения данного параметра, то номер будет сгенерирован непосредственно Робокассой. Так можно делать, если для оплаты платежей в твоем магазине используется только одна платежная система (в нашем случае – это единый интерфейс RoboKassa), либо для каждой платежки заведена отдельная таблица в базе данных для учета всех операций.
  • Соответственно Desc – это описание товара/заказа, которое будет отображаться для пользователя, после того, как он перейдет по URL’у.
  • Дополнительные параметры Shp_item, Shp_user требуются для однозначной идентификации платежа:
  • Shp_item – идентификатор товара в магазине.
  • Shp_user – идентификатор пользователя (например, можно запросить ФИО пользователя и передавать его в этом параметре). Обрати внимание, в нашем примере мы не задаем номер заказа (InvId), поэтому когда от RoboKassa будет получен ответ о проведении платежа, то однозначно идентифицировать, кто и за что заплатил, можно только по дополнительным параметрам в запросе/ответе.

Последний параметр, который мы еще не рассмотрели, SignatureValue – это цифровая подпись, которая представляет собой значение хеш-функции md5 от строки "$mrh_login:$out_summ:$inv_id:$mrh_pass1:Shp_item=$shp_item:Shp_user=$shp_user". Для того чтобы подпись не смог подделать злоумышленник, строка содержит переменную $mrh_pass1 – первый пароль мерчанта. Всего паролей два, оба задаются в личном кабинете на сайте RoboKassa. Важно помнить, что для однозначности дополнительные параметры запроса должны следовать в алфавитном порядке.

2.

Робокасса отображает пользователю интерфейс для оплаты, с учетом тех параметров, которые мы передали через URL. Клиент выбирает удобный для себя вариант оплаты покупки и подтверждает намерение произвести перевод денег.

3.

Для того чтобы сообщить магазину об исходе транзакции, Робокасса особым образом формирует ответ и передает его специальному Result-скрипту, который должен сделать следующее: во-первых, считать данные из ответа, во-вторых, сформировать по этим данным проверочную подпись (используется второй пароль) и проверить, чтобы подпись из ответа была равна проверочной. Если подписи различны, то скрипт должен вернуть строку Bad sign, иначе OK<%номер заказа%>. В листинге ниже я привел основную часть Result-скрипта.

//Считываем данные из ответа
$out_summ = $_REQUEST["OutSum"];
$inv_id = $_REQUEST["InvId"];
$shp_item = $_REQUEST["Shp_item"];
$shp_user = $_REQUEST["Shp_user"];
$crc = $_REQUEST["SignatureValue"];
$crc = strtoupper($crc);
//генерируем проверочную подпись
$my_crc = strtoupper(md5("$out_summ:$inv_id:$mrh_pass2:Shp_item=$shp_item:Shp_user=$shp_user"));

Остальные действия, которые могут быть выполнены в Result-скрипте, зависят от конкретной реализации твоего инет-магазина. Но нужно обязательно записать данные о проведенной операции, то есть, образно говоря, пробить чек.

Взгляд сверху

Теперь, когда ты знаком со спецификаций API-интерфейса RoboKassa, перейдем к разработке платежки для магазина. Наша основная цель – написать интерфейс для оплаты некоторой услуги. При разработке любого интерфейса мы должны в первую очередь помнить о его юзабильности, поэтому разрабатываемый интерфейс должен удовлетворять следующим критериям.

1. Требуется выводить полный список поддерживаемых платежных систем, логически разделенных по группам (электронные деньги, терминалы, банковские переводы и т.д.). Покупатель может выбрать тот способ, которым ему наиболее удобно внести плату. Покажем покупателю, что мы о нем заботимся и предоставляем большой выбор вариантов оплаты.

2. На странице со списком поддерживаемых платежек должна быть указана конечная стоимость услуги в этой платежки. Человек – существо рациональное, и очень часто бывает, что выбор платежки обусловлен только максимальной дешевизной. Покажем сразу конечную стоимость – честно поможем выбрать наиболее подходящий вариант.

Таким образом, нам нужно реализовать страницу со списком платежек, при входе на которую должен запускаться ajax-скрипт, запрашивающий курсы (по сути, конечную стоимость) по всем платежкам, затем производящий парсинг ответа и подставляющий полученные значения на страницу. Нам также понадобится библиотека curl: зачем это нужно и как с ней работать, станет понятно по ходу разработки функционала.

Создаем каркас

Интерфейс платежки будет находиться в payments.php. Входными данными этого скрипта является параметр $item – номер товара/заказа (зависит от реализации магазина), который передается методом GET. Скрипт payments.php сначала выводит информацию о заказе, а затем (для наглядности) таблицу с логотипами поддерживаемых платежных систем. Ниже приведена структура скрипта payments.php.

<?php
//Фильтрация параметра item
//Выбор товара из БД по идентификатору item
//Формирование запроса к платежной системе
?>
<div id="xmlConsole">
//Для отображения статуса запроса стоимости для каждой платежной системы
</div>
<div id="pay_systems">
//"Обертка" для скрытия/отображения способов оплаты
<table class="pay_table">
//Список поддерживаемых платежных систем
</table>
</div>

Для простоты в скрипте payments.php (как и все остальные скрипты ты можешь найти его на диске) вместо обращения к базе данных товаров магазина, я использую обычный оператор выбора switch. Когда же ты будешь писать рабочий скрипт для своего магазина, учти, что кроме выбора сведений о товаре из базы данных, тебе будет необходимо бронировать товар в соответствующей таблице. Естественно, что бронировать нужно на время, которое выделяется пользователю на оплату выбранного им товара. По истечении данного интервала времени товар должен быть автоматически снят с брони. Есть исключение: ты продаешь товар, который не может окончиться на складе (например, внутренняя электронная валюта сайта), или ты просто перекупаешь товар в других магазинах и автоматизировать процесс проверки наличия товара просто невозможно.

Рассмотрим структуру таблицы pay_table. Для каждой поддерживаемой платежки выделяем по две ячейки: первая для логотипа, вторая для указания стоимости платежа вида:

Цена: <b id="PayCode"></b> <b>руб.</b>

вместо PayCode будут указаны соответствующие идентификаторы для платежек. Например, для Яндекс.Денег – это PCR.
Когда мы разбирались с организацией платежа через Робокассу, то научились формировать URL (переменная $url) для инициализации процесса оплаты. Чтобы указать Робокассе, с помощью какой именно платежной системы клиент хочет произвести оплату, необходимо дописать к переменной $url идентификатор платежки.

<a href="<?php echo $url.'&IncCurrLabel=PCR'; ?>">ЯД</a>

Линки подобного вида повесим на каждый логотип платежки и строку, содержащую цену товара.

XML-интерфейс RoboKassa и jQuery

Еще одна проблема – отобразить стоимость товара в разных валютах, для чего нам потребуется узнать курсы для каждой из поддерживаемых платежек. Получить курсы можно через XML-интерфейс RoboKassa. Для этого необходимо составить и отправить XML-запрос методом POST по адресу www.roboxchange.com/xml/rate.asp. Запрос имеет следующий вид:

<robox.rate.req>
<out_curr>OUTCURR</out_curr>
<merchant_login>LOGIN</merchant_login>
<out_cnt>CNT</out_cnt>
</robox.rate.req>

Тут OUTCURR – идентификатор исходящей валюты (определяется при регистрации мерчанта в системе RoboKassa), LOGIN – логин мерчанта, CNT – на какую сумму будет совершена покупка.

XML-ответ от RoboKassa согласно спецификации протокола будет иметь вид

<robox.rate.resp>
<retval>nRetCode</retval>
<out_curr>sOutCurrLabel</out_curr>
<out_cnt>nOutCount</out_cnt>
<date>sDateODBC120</date>
<ratelist>
<rate>
<in_curr>sIncCurrLabel</in_curr>
<in_curr_name>sIncCurrName</in_curr_name>
<value>nValuet</value>
<ins_per_Xout>nInCount</ins_per_Xout>
</rate>
…
</ratelist>
</robox.rate.resp>

где:

  • nRetCode – код возврата, 0 – нет ошибок, либо код ошибки (для информации по кодам ошибки см. техническую докуменатцию RoboKassa – http://www.robokassa.ru/Doc/Ru/Interface.aspx);
  • sOutCurrLabel – идентификатор исходящей валюты;
  • nOutCount – количество денежных знаков исходящей валюты;
  • sDateODBC120 – дата, на которую возвращено состояние курсов (формат "yyyy-mm-dd hh:mm:ss", GMT);

каждый тег <rate> описывает один курс, поэтому их будет столько, сколько платежек было подключено через RoboKassa.
В теге <rate> нас интересуют атрибуты in_curr – идентификатор платежки, ранее в payments.php я его обозначил как PayCode; и ins_per_Xout, собственно то, ради чего и выполняем запрос, – цена, которую нужно оплатить пользователю, если он выберет эту платежку.
Теперь, когда мы разобрались со спецификаций XML-протокола RoboKassa, напишем парсер на jQuery (скрипт rk_xml_int.js). Запрос будем отсылать через метод ajax().

function getXML(url, cnt){
$.ajax({
url: url,
type: 'POST',
dataType: 'xml',
data: {cnt: cnt},
beforeSend: xmlStart,
success: xmlSuccess,
error: xmlError,
complete: xmlComplete
});
}

Функции getXML() передается два параметра. Первый параметр url – непосредственно URL-адрес, куда необходимо отправить запрос. Второй параметр cnt – стоимость товара в магазине. Функции, на которые ссылаются переменные в теле метода ajax(), а именно, xmlStart, xmlSuccess, xmlError, xmlComplete, я разберу позже. А пока необходимо решить одну проблему. Дело в том, что в качестве параметра url нельзя передавать ссылку на документ, который находится на другом домене.

PHP-прокси и XMLHttpRequest

Метод ajax() библиотеки jQuery для пересылки данных использует API-функцию XMLHttpRequest. Благодаря технологии XMLHttpRequest возможно выполнить HTTP-запрос, не перезагружая страницу. Чтобы злоумышленникам было сложнее проводить XSS-атаки, XMLHttpRequest не работает с внешними доменами. Например, если мы пишем скрипт script.js, который находится на сервере serv1.com, то не получится отправить запрос на serv2.com методом XMLHttpRequest.

Для решения этой проблемы мы напишем небольшой прокси-скрипт rk_rate_proxy.php. Схема взаимодействия следующая: rk_xml_int.js с помощью XMLHttpRequest отправляет XML-запрос rk_rate_proxy.php (расположен на нашем сервере), который с помощью библиотеки curl ретранслирует запрос к XML-интерфейсу RoboKassa, считывает ответ и возвращает его. Приступим. Код отправки XML-запроса через библиотеку curl выглядит следующим образом:

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: close'));

Переменная $url – адрес XML-интерфейса RoboKassa, объявляется непосредственно в rk_rate_proxy.php. Переменная $request – это, в свою очередь, XML-запрос, структуру которого мы разбирали ранее:

$request = '<robox.rate.req>';
$request .= '<out_curr>RUR</out_curr>';
$request .= '<merchant_login>demo</merchant_login>';
$request .= '<out_cnt>'.$cnt.'</out_cnt>';
$request .= '</robox.rate.req>';

Для запроса используется тестовая учетная запись мерчанта – demo. Библиотека curl очень проста в освоении, и очень изящна. Чтобы это оценить, посмотри на листинг чтения ответа и возвращения результата

$result = curl_exec($ch);
header('Content-type: text/xml');
echo $result;

Все, скрипт PHP-прокси rk_rate_proxy.php написан. Теперь вернемся к парсеру.

Добиваем парсер

Ajax-запросы теперь ходят через прокси, XMLHttpRequest работает, так как запрос идет на скрипт в своем домене. Осталось разобраться с функциями, на которые ссылаются переменные метода ajax().

Сначала второстепенные. Функция xmlStart() вызывается перед отправкой XML-запроса. С помощью данной функции я изменяю CSS-стиль для элемента div с id=xmlConsole. Стиль ajaxLoaderCSS отображает картинку в бэкграунде – статус выполнения запроса. Кстати спешу порекомендовать онлайн-сервис www.ajaxload.info, который поможет сгенерировать всевозможные статусбары на любой вкус и цвет. Код функции xmlStart() приведен в листинге ниже.

function xmlStart(xhrInstance) {
$("#xmlConsole").addClass("ajaxLoaderCSS");
}

Как ясно из названия xmlError(), данная функция вызывается, если в ходе выполнения XML-запроса произошла ошибка. В случае возникновения ошибки выводится сообщение для пользователя и скрывается div-"обертка" с логотипами платежных систем.

function xmlError(xhrInstance, message, optional) {
$("#xmlConsole").html('<h2><font color="red">Ошибка!</font> Попробуйте повторить попытку позже</h2>');
$("#pay_systems").css('display', 'none');
}

По завершению XML-запроса сначала выполняется функция xmlComplete(), в теле которой у элемента с id=xmlConsole удаляется CSS-стиль ajaxLoaderCSS, а затем xmlSuccess(), в теле которой и происходит вызов функции parseXML() – парсер для обработки XML-ответа. Реализация функции parseXML() наглядно демонстрирует основной принцип библиотеки jQuery – принцип ненавязчивого JavaScript, листинг ее приведен ниже.

function parseXML(xml){
//Для каждого тега <rate> в цикле выполняем следующие действия
$( xml ).find('rate').each(function(){
//читаем идентификатор платежки
var curr = $(this).find('in_curr').text();
//читаем стоимость в этой платежки
var val = $(this).find('ins_per_Xout').text();

//в соответствующей ячейке выводим результат
$('#'+curr).html(val);
});
}

Теперь парсер написан полностью. Чтобы инициализировать XML-запрос, распарсить его и вывести результат в payments.php пишем

<script type="text/javascript">
$(function() {
<?php
$tmp_out_summ = (int) $out_summ;
echo "getXML('rk_rate_proxy.php', $tmp_out_summ);";
?>
});
</script>

Функция getXML() будет вызвана при загрузке страницы, точнее после того, как браузер построит DOM-структуру документа.

Модернизируй

Вот и готов каркас для приема платежей через Робокассу. Полностью рабочие скрипты ты можешь использовать и своих проектах, практически сходу наладив прием платежей. Сложно ли это было? Нет! Сам процесс работы с Робокассой очень прост. Больше времени ушло на оболочку, внешний вид, обустройства интерфейса для того, чтобы пользователю было удобно. Но именно это и важно для конечного клиента. И теперь нам есть что ему предложить.

Cемь раз – отмерь

Крайне важно досконально оттестировать скрипты, имеющие отношение к оплате. Хочу обратить твое внимание на одну деталь, а точнее предупредить тестеров скриптов. Если твой Result-скрипт будет недоступен или вернет в качестве ответа "bad sign", то это не значит, что платеж клиента не пройдет и/или будет отменен, наоборот, он скорее пройдет. Если пункт 2 общей схемы взаимодействия твоего магазина и RoboKassa, описанной в разделе организации платежа через RoboKassa, выполнен успешно, то платеж уже откатить нельзя. В этот момент деньги уже снялись с электронного счета клиента, более того, они уже перешли на твой счет в RoboKassa, о чем ты незамедлительно получишь уведомление на рабочий email. Поэтому тестируй все внимательно, с деньгами все-таки приходится работать реальными! Основную отладку нужно проводить на тестовом сервере RoboKassa, URL и спецификацию по работе с которым легко найти в разделе технической документации на официальном сайте.

DVD

На диске ты найдешь исходники скриптов для организации приема платежей

Содержание