Pull to refresh

Прием WebMoney без ухода с сайта

Reading time10 min
Views9.6K

Привет, хабр!

Хочу рассказать про прием WebMoney без перехода на сайт мерчанта Webmoney (merchant.webmoney.ru). Данный метод приема платежей может использоваться, и используется в оффлайн магазинах, небраузерных играх.

Интересно? Добро пожаловать под кат. Будет много php кода)

Как будет выглядеть со стороны клиента:

Для оплаты, ваш клиент должен будет ввести номер мобильного телефона\WMID\email. Далее ему одним из 3-х способов нужно будет подтвердить оплату:
  • Подтверждение SMS-кодом, присланным на мобильный телефон клиента
  • Подтверждение USSD-запросом, присланным на мобильный телефон клиента
  • Оплатой WM-счета, выписанного клиенту. В данный момент оплата ВМ-счетов возможна во всех мобильных (и вообще любых) приложениях по управлению кошельками WebMoney Transfer.

После оплаты нужно будет подтвердить факт оплаты на вашем сайте.

Для оффлайн магазинов наиболее интересны 1-й и 2-й способ подтверждения.

Подготовка:

Для начала выберем способ формирования подписи, их 3:
  1. Подпись с помощью WMSigner
  2. Подпись с помощью MD5
  3. Передача Secret Key

Я рассмотрю только первые 2 варианта, так как при использовании 3-го способа необходимо производить дополнительный проверки (проверка аутентичности соединения по https, во избежание подмены DNS):

1) WMSigner — модуль, формирующий подпись с использованием ключевого файла WM Keeper Classic. При его использовании, рекомендуется размещать конфиг модуля и ключевой файл выше директории сайта, во избежание кражи.
В архиве с модулем содержится README.rus, в котором все прекрасно описано, проблем у вас не должно возникнуть.

2) MD5
Для формирования подписи с помощью md5 предварительной подготовки не требуется, в большинстве языков программирования данная технология хэширования присутствует по умолчанию (в т.ч. и в php, на котором и будем программировать).

Данный метод является предпочтительным, если вы не используете других интерфейсов X, требующих для подписи только WMSigner.

Сама подпись будет представлять собой md5 хэш от склейки
wmid + lmi_payee_purse + lmi_payment_no + lmi_clientnumber + lmi_clientnumber_type + secret_key

WMID + кошелек_продавца + номер_платежа + номер_телефона\email\wmid_клиента + тип предыдущего_поля + секретный ключ

Кодим

В качестве языка программирования для осуществления взаимодействия я выбрал php. Посмотреть и скачать весь код (в одном файле) вы можете тут.

Форма платежа:

    <html>
        <head>
            <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        </head>
        <body>
            <form method="post" action="">
                <select name="purse">
                    <option value="wmr">WMR</option>
                    <option value="wmz">WMZ</option>
                </select><br />
                Сумма платежа в WMR разделитель через точку:<br />
                <input type="text" name="amount" VALUE="12.34"><br />
                Примечание к платежу:<br />
                <input type="text" name="desc" value="тестовый платеж"><br />
                Платим по:<br /> 
                <select name="number_type">
                    <option value="0">Телефон</option>
                    <option value="1">WMID</option>
                    <option value="2">Email</option>
                </select>
                <input type="text" name="number" value="79167777777"><br />

                Подтвреждение по:<br />
                <select name="confirmation_type">
                    <option value="1">SMS или оплатой счета</option>
                    <option value="2" selected>USSD или оплатой счета</option>
                    <option value="3">выбор автоматом/ по умолчанию смс</option>
                    <option value="4">только счетом</option>
                </select><br />
                <input type="submit" value="Инициировать платеж">
            </form>
        </body>
    </html>


Для начала — 2 общие функции.

function wmsign($input) // Подпись с помощью WMSigner
{
    global $_SETTINGS;
    // Открываем поток к WMSigner, на запись и чтение.
    $f=proc_open("./signer/wmsigner -i ./signer/wmsigner.ini -k ./signer/".$_SETTINGS['wmid'].".kwm", array(
        0=>array("pipe", "r"),
        1=>array("pipe", "w"),
        2=>array("file", "/tmp/error-output.txt", "a")), $pipes);

    if(is_resource($f))
    {
        $output="";
        // Отсылаем вышесгенерированную строку и закрываем поток на запись.
        fwrite($pipes[0], $input);
        fclose($pipes[0]);

        // Читаем хэш из входного потока
        while(!feof($pipes['1']))
        {
            $output .= fread($pipes[1], '1024');
        }
        // Закрываем входной поток и закрываем поток WMSigner
        fclose($pipes['1']);
        proc_close($f);
    }
    return $output;
}

function post($post,$url="https://merchant.webmoney.ru/conf/xml/XMLTransRequest.asp") // Формирование post запроса
{
    $ch=curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_FAILONERROR, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 3);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    $result=curl_exec($ch);
    curl_close($ch);
    return $result;
}


Проверяем входные параметры, составляем XML запрос к WebMoney, подписываем его и отправляем. Далее проверяем статус запроса, если все без ошибок — выдаем форму на подтверждение платежа. Если СМС — то спрашиваем еще и код, полученный по смс.


$_SETTINGS=array(
    'wmid'=>'123456789012', //ваш wmid
    'wmr'=>'R123456789012', // R кошелек
    'wmz'=>'R123456789012', // Z кошелек
    'amount'=>'123.45', // Сумма
    'desc'=>'Тестирование X20', // Примечание
    'sing_method'=>'sing', // Тип подписи, md5\sing (by default)\secret_key
    'secret_key'=>'t4er43d#4' // Секретный ключ, из настроек магазина в мерчанте.
);
if($_SERVER['REQUEST_METHOD']=="POST")
{

// Тут различные проверки входных данных. Если найдена ошибка - присваиваем переменной $error = True;

        switch($_REQUEST['purse']) // Указываем нужный кошелек, в зависимости от того, в какой валюте платит покупатель. По умолчанию - WMZ
        {
            case "wmr": $purse=$_SETTINGS['wmr'];
                break;
            default: $purse=$_SETTINGS['wmz'];
        }

        if(!(is_numeric($_REQUEST['number_type'])&&$_REQUEST['number_type']<3&&$_REQUEST['number_type']>=0)) // Проверяем тип оплатs
        {
            $error=True;
            $errors['number_type']="Неверный тип";
        }

        if(!(is_numeric($_REQUEST['confirmation_type'])&&$_REQUEST['confirmation_type']<=4&&$_REQUEST['confirmation_type']>0)) // Проверяем тип оплаты
        {
            $error=True;
            $errors['confirmation_type']="Неверный тип";
        }

        $amount=(is_numeric($_POST['amount']))?$_POST['amount']:$_SETTINGS['amount'];

        if(!$error)
        {
            $pay_no=time();
            // Формируем запрос
            
            $xml=new DomDocument('1.0', 'utf-8');
            $xml->formatOutput=true;
            // Формируем запрос
            $merchant=$xml->appendChild($xml->createElement('merchant.request'));

            $merchant->appendChild($xml->createElement('wmid'))->appendChild($xml->createTextNode($_SETTINGS['wmid'])); // WMID
            $merchant->appendChild($xml->createElement('lmi_payee_purse'))->appendChild($xml->createTextNode($purse)); // Кошелек продавца
            $merchant->appendChild($xml->createElement('lmi_payment_no'))->appendChild($xml->createTextNode($pay_no)); // Номер платежа в системе учета продавца
            $merchant->appendChild($xml->createElement('lmi_payment_amount'))->appendChild($xml->createTextNode($amount));  // Сумма платежа в валюте указаного выше кошелька, разделитель - точка.
            $merchant->appendChild($xml->createElement('lmi_payment_desc'))->appendChild($xml->createTextNode($_SETTINGS['desc'])); // Описание платежа, 255 символов.
            $merchant->appendChild($xml->createElement('lmi_clientnumber'))->appendChild($xml->createTextNode($_REQUEST['number'])); // Мобильный телефон (в формате 79167777777,380527777777), WMID, email.
            $merchant->appendChild($xml->createElement('lmi_clientnumber_type'))->appendChild($xml->createTextNode($_REQUEST['number_type'])); // Тип данных, переданных в lmi_clientnumber (0-телефон, 1-WMID, 2-email)
            $merchant->appendChild($xml->createElement('lmi_sms_type'))->appendChild($xml->createTextNode($_REQUEST['confirmation_type'])); // Тип подтверждения транзакции (1-смс, 2-USSD, 3-автоматический выбор, из настроек сделанных самим клиентом и анализа предыдущих платежей и т.п., по умолчанию - смс, 4- только WM счет)
            $merchant->appendChild($xml->createElement('secret_key'))->appendChild($xml->createTextNode('')); // Тип подтверждения транзакции (1-смс, 2-USSD, 3-автоматический выбор, из настроек сделанных самим клиентом и анализа предыдущих платежей и т.п., по умолчанию - смс, 4- только WM счет)

            // Формируем запрос в соотвествии с выбранным методом подписи
            switch($_SETTINGS['sign_method'])
            {
                case "md5":
                    // md5, тут все понятно, склеиваем поля в нужном порядке и вычисляем md5 хэш от полученной строки
                    $sign = md5($_SETTINGS['wmid'].$purse.$pay_no.$_REQUEST['number'].$_REQUEST['number_type'].$_SETTINGS['secret_key']);
                    $merchant->appendChild($xml->createElement('md5'))->appendChild($xml->createTextNode($sign));
                    break;
                case "sign":
                    // Подпись с помощью WMSigner
                    // Склеиваем данные в нужном порядке, из которых далее будем вычислять хэш.
                    $sign = wmsign($_SETTINGS['wmid'].$purse.$pay_no.$_REQUEST['number'].$_REQUEST['number_type']);
                    $merchant->appendChild($xml->createElement('sign'))->appendChild($xml->createTextNode($sign));
                    break;
            }

            // Отсылаем запрос запрос к WebMoney, методом POST
            $result=post($xml->saveXML(),"https://merchant.webmoney.ru/conf/xml/XMLTransRequest.asp");
            
            // Используем библиотеку dom, т.к. на мы получили XML документ
            $dom=new domDocument;
            $dom->loadXML($result);
            if(!$dom)
                die("Ошибка парсинга XML");
            $data=simplexml_import_dom($dom);
            
            // Если произошла ошибка (retval - код ошибки, 0 в случае отсутствия ошибки), то выводим ее описание для клиента.
            if($data->retval!=0)
            {
                echo $data->retval." Error: ".$data->userdesc."\n\n";
                
            }
            else
            {
                ?>
                <html>
                    <head>
                        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
                    </head>
                    <body>
                        <form method="post" action="">
                            <input type="hidden" name="confirmation" value="1">
                            <?= ($data->realsmstype==1)?'Введите код, полученный в СМС<br />\n':'' ?><input type="<?= ($data->realsmstype==1)?'text':'hidden' ?>" name="code" value="<?= ($data->realsmstype==1)?'':0 ?>">
                            <input type="hidden" name="account" value="<?=$data->operation['wminvoiceid']?>">
                            <input type="hidden" name="purse" value="<?= $_REQUEST['purse'] ?>">
                            <input type="submit" value="Подтвердить оплату.">
                        </form>
                    </body>
                </html>
                <?
            }
        }

Клиент подтверждает оплату (если СМС — вводит код, пришедший ему, с остальными вариантами — достаточно просто нажать на кнопку), и мы должны уведомить о оплате WebMoney. Иначе денег нам не видать.

// Здесь валидация, можете посмотреть как реализовано в предыдущем примере.
                if(!$error)
        {
            $xml=new DomDocument('1.0','utf-8');
            $xml->formatOutput = true;
            // Формируем запрос
            $merchant = $xml->appendChild($xml->createElement('merchant.request'));
            
            $merchant->appendChild($xml->createElement('wmid'))->appendChild($xml->createTextNode($_SETTINGS['wmid'])); // WMID
            $merchant->appendChild($xml->createElement('lmi_payee_purse'))->appendChild($xml->createTextNode($purse));  // Кошелек продавца
            $merchant->appendChild($xml->createElement('lmi_clientnumber_code'))->appendChild($xml->createTextNode($_REQUEST['code'])); // Цифровой код, полученный от клиента (WM счет\USSD = 0, -1 отмена счета)
            $merchant->appendChild($xml->createElement('lmi_wminvoiceid'))->appendChild($xml->createTextNode($_REQUEST['account'])); // Номер счета, полученный из предыдущей операции
            
            switch($_SETTINGS['sign_method'])
            {
                case "md5":
                    // md5, тут все понятно, склеиваем поля в нужном порядке и вычисляем md5 хэш от полученной строки
                    $sign=md5($_SETTINGS['wmid'].$purse.$_REQUEST['account'].$_REQUEST['code'].$_SETTINGS['secret_key']);
                    $merchant->appendChild($xml->createElement('md5'))->appendChild($xml->createTextNode($sign));
                    break;
                case "sign":
                    // Подпись с помощью WMSigner
                    // Склеиваем данные в нужном порядке, из которых далее будем вычислять хэш.
                    $sign=wmsign($_SETTINGS['wmid'].$purse.$_REQUEST['account'].$_REQUEST['code']);
                    $merchant->appendChild($xml->createElement('sign'))->appendChild($xml->createTextNode($sign));
                    break;
            }

            // Отсылаем запрос запрос к WebMoney, методом POST
            $result=post($xml->saveXML(),"https://merchant.webmoney.ru/conf/xml/XMLTransConfirm.asp");
            // Используем библиотеку dom, т.к. на мы получили XML документ
            $dom=new domDocument;
            $dom->loadXML($result);
            if(!$dom)
                die("Ошибка парсинга XML");
            $data=simplexml_import_dom($dom);

            // Если произошла ошибка (retval - код ошибки, 0 в случае отсутствия ошибки), то выводим ее описание для клиента.
            if($data->retval!=0)
            {
                echo "Error№".$data->retval.": ".$data->userdesc."\n\n";
                                ?>
                <html>
                    <head>
                        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
                    </head>
                    <body>
                        <form method="post" action="">
                            <input type="hidden" name="confirmation" value="1">
                            <?=(!$_POST['code'])?'Введите код, полученный в СМС<br />':''?>
                            <input type="<?=(!$_POST['code'])?'text':'hidden'?>" name="code" value="<?=$_POST['code'];?>">
                            <input type="hidden" name="account" value="<?=$_POST['account']?>">
                            <input type="hidden" name="purse" value="<?= $_REQUEST['purse'] ?>">
                            <input type="submit" value="Подтвердить оплату.">
                        </form>
                    </body>
                </html>
                <?
            }
            else
            {
                // Оплата скорей всего была принята.
                echo "Оплата была принята";
            }
        }


Результаты тестирования

Собственно, получить платеж с использованием интерфейса X20 мне не удалось. Постоянно вылетали ошибки. Либо
Найден указанный Вами ВМ-идентификатор, но на его кошельке нет достаточного для оплаты количества средств

либо
Сообщение от WebMoney Transfer: в данный момент Ваш продавец приостановил прием платежей, попробуйте пожалуйста позднее.


Хотя деньги на счете есть, прием платежей не приостановлен. Сейчас жду комментария от WM по поводу данных ошибок, и по поводу комиссии при использовании X20.

Проблемы решены. Причина в фиксированной комиссии, невозможности оплачивать с кошельков, зарегистрированных в мерчанте и не совсем верной логике. Если выбрать способ оплаты — WM-счетом, то будет проверяться наличие на кошельке суммы платежа+0,8% комиссии+фиксированной комиссии. Хотя фиксированная комиссия на WM-счет не распространяется.

Ссылки по теме

Описание интерфейса X20
WMSigner
Ролик, демонстрирующий оплату через X20
Ролик, демонстрирующий оплату через X20 в оффлайн магазинах
Пример реализации на сайте мерчанта

UPD При подтверждении оплаты через USSD\SMS взимается дополнительная фиксированная комиссия — 0.9 WMR, 0.04 WMZ, 0.03 WME, 0.25 WMU.
Tags:
Hubs:
+28
Comments15

Articles