Интеграция Платрона с Play framework

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

Под интеграцией я понимаю создание view, в который в качестве параметров GET запроса контроллером передаются нужные величины из БД из application.conf. А после передачи управления и получения уведомление из Платрона происходит обновление данных в БД и пользователь видит прошел или нет его платеж, и перенаправляется на страничку магазина.

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


platron1

Сыылка для кнопки:

<a href="https://www.platron.ru/payment.php?pg_amount=4&pg_currency=RUB&pg_merchant_id=8888&pg_language=ru&pg_description=%D0%9F%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%BA%D0%B0+%D0%BD%D0%B0+2+%D0%B4%D0%BD%D1%8F&pg_salt=aaaaaaaaaa&pg_sig=7b893059b970ff94003b3ac962d19cb5">Купить Подписку на 4 дня (4 руб.)</a>

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

В результате проб и ошибок мне удалось подобрать такую тестовую конфигурацию формы для передачи управления на сторону платежной системы (некоторые значения я изменил, но хеши не пересчитывал. Так что могут быть несовпадения). Соль в параметре pg_salt должна быть случайной строкой. В контроллере нужно выдать такой view:

<form method="GET" action="https://www.platron.ru/payment.php">
    <input type="hidden" name="pg_amount" value="100.0" />
    <input type="hidden" name="pg_currency" value="RUB" />
    <input type="hidden" name="pg_order_id" value="275" />
    <input type="hidden" name="pg_merchant_id" value="8888" />
    <input type="hidden" name="pg_salt" value="391fa6cfaf8d4ce8976364db3578c323" />
    <input type="hidden" name="pg_language" value="ru" />
    <input type="hidden" name="pg_description" value="some item" />
    <input type="hidden" name="pg_sig" value="c15c46a50064d726a71078e2f4d772a2" />
    <input type="hidden" name="pg_success_url" value="http://vb.ru/notify/success.php" />
    <input type="hidden" name="pg_success_url_method" value="GET" />
    <input type="hidden" name="pg_failure_url" value="http://vb.ru/notify/fail.php" />
    <input type="hidden" name="pg_failure_url_method" value="GET" />
    <input type="hidden" name="pg_result_url" value="http://vb.ru/notify/result.php" />
    <input type="hidden" name="pg_request_method" value="GET" />
    <p><input type="submit" value="Заказать"/></p>
</form>

Параметр pg_order_id вернется в уведомлении из Платрона и вы сможете идентифицировать заказ, который был оплачен.

При отправке получается GET запрос вида:

https://www.platron.ru/payment.php?pg_amount=100.0&pg_currency=RUB&pg_order_id=275&pg_merchant_id=8888&pg_salt=da53d7764fe94330bed5d223376b3954&pg_language=ru&pg_description=some+item&pg_sig=8778ff60c9d16b397852ef215f1dcb6b&pg_success_url=http%3A%2F%2Fvb.ru%2Fnotify%2Fsuccess.php&pg_success_url_method=GET&pg_failure_url=http%3A%2F%2Fvb.ru%2Fnotify%2Ffail.php&pg_failure_url_method=GET&pg_result_url=http%3A%2F%2Fvb.ru%2Fnotify%2Fresult.php&pg_request_method=GET

Параметры pg_success_url, pg_success_url_method, pg_failure_url, pg_failure_url_method, pg_result_url в данном случае передаются в запросе и в общем случае могут быть свои у каждой транзакции. Если вам не нужна такая гибкость (у вас что, нет маркетологов? 🙂 ), то их можно прописать в админке.

Порядок параметров в запросе (определяется порядком полей в форме) не важен. Для получения подписи на стороне Платрона они будут выстроены в алфавитном порядке (имя скрипта/службы — в данном случае payment.php — и секретный ключ всегда начинают и заканчивают строку соответственно):

payment.php;100.0;RUB;some item;http://vb.ru/notify/fail.php;GET;ru;8888;275;GET;http://vb.ru/notify/result.php;da53d7764fe94330bed5d223376b3954;http://vb.ru/notify/success.php;GET;secret_key

Для наглядности приведу имена параметров в том же порядке:

pg_amount;pg_currency;pg_description;pg_failure_url;pg_failure_url_method;pg_language; ...

Не светите свой секретный ключ в HTML форме, части URL или еще как-то. Он задействован в генерации MD5 хеша для подписи. Значит, утечка ключа может повлечь прием подделанных платежей (злоумышленник сможет сгенерировать подпись и отправить подтверждение оплаты своего заказа на pg_result_url) и финансовые потери.

Я не загромождаю страничку параметрами, пользователь все равно не может их редактировать. Из всей это формы я вывожу в заголовок странички только ID заказа и сумму платежа, все поля формы — невидимые.

Как с минимальными усилиями получить из Map алфавитный порядок ключей? Правильно: используя TreeSet. Сначала грузим пары (ключ;значение) в Map в произвольном порядке:

params.put("pg_salt", "my random salt");
params.put("pg_language", "ru");
params.put("pg_currency", "RUB");
...

Строковые и численные константы можно вынести в конфиг application.conf и читать их в контроллере — в этом случае для правок требуется меньшая квалификация, чем в случае правки java исходника контроллера. К тому же, в этом случае правки сосредоточены в одном файле.

Теперь перебираем ключи и конкатенируем строку:

        SortedSet<String> keys = new TreeSet</string><string>(params.keySet());
        for (String key : keys) { 
            String value = params.get(key);
            sb.append(value + DELIMITER); // DELIMITER = ';'
        }
		// use sb.toString()

Останется только в начало строки прилепить имя сервиса/скрипта, а в конец — секретный ключ. От этой строки теперь можно посчитать MD5:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
    public static String MD5hash (String source){
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e){
            log.error("error getting MD5 instance",e);
            e.printStackTrace();
        }
        md.update(source.getBytes());
 
        byte byteData[] = md.digest();
        //convert the byte to hex format method 1
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < byteData.length; i++) {
            sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
    }

Для отладки генерации подписи есть полезная страничка: ссылка для отладки.
Она покажет вам, какая строка (от нее считается MD5) должна получиться при распарсивании вашего УРЛа и объединения параметров в строку.

Вернемся к HTML форме.

Скрипт по адресу http://vb.ru/notify/success.php просто сохраняет запрошенный URL в БД:

< ?php
include("include.php");
 
$url =  "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
 
$escaped_url = htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' );
echo '<a href="' . $escaped_url . '">' . $escaped_url . '';
 
$link = mysql_connect($mysql_host, $mysql_user, $mysql_pass) 
or die ("Sorry, can't connect to database."); 
mysql_select_db($mysql_database); 
 
$q = "INSERT INTO play_requests (url) VALUES ('" . $url . "')"; 
$statresult = mysql_query($q);
mysql_close($link);
?>

Остальные скрипты работают аналогично. Если один и тот же скрипт у вас принимает разные виды уведомлений, то для уникальности можно добавить еще одно поле в БД со значениями «success», «fail», «result», «check».
Вообще, в плане настройки уведомлений на разные сервисы у Платрона богатейшие возможности. У каждой транзакции в общем случае свои адреса для уведомлений. Это пригодится, если у вас несколько магазинов в одной учетной записи:


platron2

Вот что важно знать про URL-ы. Надежным средством узнать прошел платеж или нет, является только Result URL, причем при некорректном ответе он может быть запрошен платроном многократно. Пара Success URL и Fail URL служат только для того, чтоб перенаправить покупателя и показать ему статус платежа. Их нельзя использовать в качестве источника данных для обновления БД, т.к. переход на них может вообще не произойти, к примеру, если нарушена передача данных в сети.

Не забудьте о том, что в ответ на уведомление на Result URL Платрон ждет не просто ответ 200 OK, а составленный особым образом XML:

< ?xml version="1.0" encoding="utf-8"?>
<response>
<pg_salt>kdjdope983</pg_salt>
<pg_status>ok</pg_status>
<pg_description>Товар передан покупателю</pg_description>
<pg_sig>9bfc5f602d287096ed237952f56bd05c</pg_sig>
</response>

Обратите внимание на то, что подпись для XML подсчитывается от строки, которая по-прежнему начинается с имени скрипта/сервиса : «result.php;[params];secret_key» (если уведомление пришло на http://myshop.ru/result.php).

You can leave a response, or trackback from your own site.

Leave a Reply