Pull to refresh

Как я подключался к QIWI

Payment systems

Зачем мне это было нужно?


Проект настойчиво требовал подключения удобных платежных систем. Да, есть webmoney, но не у всех. Да, есть moneybookers для карточек, но слишком долог бюрократический процесс.

Было принято решение принимать платежи через QIWI, во-первых потому что их автоматы есть практически везде, а во-вторых (тссс, большой секрет!) они готовят запуск системы прямых платежей со счета сотового оператора, без всяких дурацких СМС и девяностодевятипроцентных комиссий.

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

Сказано – сделано!


Для сайта был взят вполне заурядный VDS, на котором собран вполне заурядный же серверный набор – nginx спереди, Apache позади.

Основа в виде некоей CMS у меня уже была, в том числе в ней был реализован и модуль личных счетов пользователей с подключаемыми модулями платежных систем.

Для работы с SOAP проще всего взять с гуглокода класс nuSOAP (http://code.google.com/p/nusoap-for-php5/).

Затем нам надо сделать две вещи:
  1. Отправку на сервер QIWI информации о попытке платежа
  2. Прием от QIWI на наш сервер пакета с информацией об изменении статуса платежа.

Документация, жаль, что не очень подробная, есть здесь: https://ishop.qiwi.ru/docs/OnlineStores_Protocols_SOAP.pdf

Привожу свой код, который каждый может взять за основу

$client = new nusoap_client("https://mobw.ru/services/ishop", false); // создаем клиента для отправки запроса на QIWI
$error = $client->getError();
if ( !empty($error) ) {
TPAccount::cancelTransaction($transaction); // обрабатываем возможные ошибки и в случае их возникновения откатываем транзакцию в своей системе
echo -1;
exit();
}
$client->useHTTPPersistentConnection();

// Параметры для передачи данных о платеже:
// Ваш ID в системе QIWI
// Ваш пароль
// Телефон пользователя
// Сумма платежа в рублях
// Комментарий, который пользователь увидит в своем личном кабинете или платежном автомате
// Наш внутренний уникальный номер транзакции
// Время жизни платежа до его автоматической отмены
// Оповещать ли клиента через СМС или звонком о выписанном счете
// 0 – только для зарегистрированных пользователей QIWI, 1 – для всех
$params = array(
'login' => $payment->getVar('id'),
'password' => $payment->getVar('password'),
'user' => $phone,
'amount' => $transaction['amount'],
'comment' => ‘Платеж за наш замечательный товар’,
'txn' => $transaction['id'],
'lifetime' => date("d.m.Y H:i:s", strtotime("+2 weeks")),
'alarm' => 1,
'create' => 1
);
// собствено запрос:
$result = $client->call('createBill', $params, "http://server.ishop.mw.ru/");
if ($client->fault) {
TPAccount::cancelTransaction($transaction);
echo -1;
exit();
} else {
$err = $client->getError();
if ($err) {
TPAccount::cancelTransaction($transaction);
echo -1;
exit();
} else {
echo $result;
exit();
}
}


У меня данный скрипт вызывается через AJAX, и, как видно из кода, он возвращает код завершения: -1 – ошибка, любое другое значение – ответ сервера QIWI.

Немногим сложнее и код для приема от платежной системы информации об изменении статуса (привожу сразу в окончательном виде):

$server = new nusoap_server;
$server->register('updateBill');
$server->service($HTTP_RAW_POST_DATA);

function updateBill($login, $password, $txn, $status) {

// создаю экземпляр класса qiwiPayment
$paymentclass = "qiwi" . "Payment";
$payment = new $paymentclass;

//обработка возможных ошибок авторизации
if ( $login != $payment->getVar('id') )
return 150;

if ( !empty($password) && $password != md5($payment->getVar('password')) )
return 150;

// восстанавливаю информацию о внутренней транзакции
$transaction = TPTransactions::getTransactionByID(intval($txn));

if ( empty($transaction) )
return 300;

switch ( intval($status) ) {

case 50:
// Bill created
return 0;
break;

case 60:
// Счет оплачен пользователем – завершаем транзакцию
TPAccount::proceedTransaction($transaction);
return 0;
break;

default:
// Что-то случилось, счет не оплачен, транзакцию нужно отменить
TPAccount::cancelTransaction($transaction);
return 0;
break;

}

return 300;

}


Сюрприз первый. nuSOAP.


Вот вроде бы код написан, проверен на локальном сервере, пора познакомить мой PHP с благородной JAVA со стороны QIWI.

Первая неожиданность возникает мгновенно – nuSOAP при первой же попытке создать тестовый платеж озадачивает сообщением «Response not of type text/xml». Да, никак не ожидали создатели nuSOAP, что одна российская платежная система решит, что SOAP – это «application/soap+xml». Поправим их – достаточно несложно найти и закомментировать соответствующие проверки внутри кода nuSOAP.

И вот торжество – тестовый платеж создан, его видно в QIWI, настала пора принять от него ответ об успешной оплате.

Иду к автомату…

Настройка nginx. Ошибка 411.


Однако меня ждет неприятный сюрприз. При попытке проверить работу веб-сервиса магазина, мой сервер неожиданно отвечает ошибкой 411 – Length required.

Ответ поддержки QIWI все прояснил:
«Здравствуйте. Наш сервис посылает chunked-запросы без указания content-length. Думаю у вашего веб-сервера должны быть настройки для обработки таких запросов.»

Для меня так и осталось навсегда загадкой, что мешает QIWI все-таки указывать Content-length, однако теперь многое стало понято. nginx не воспринимает chuncked запросы, сразу отвечая на них ошибкой…

Идем на сервер и за работу!

Скачиваем модуль NginxHttpChunkinModule, кладем его, распаковав, к примеру в /root/chunkin. Взять его можно здесь: http://wiki.nginx.org/NginxHttpChunkinModule, там же есть и инструкции по установке

Ничего сложного в установке нет, покажу на примере FreeBSD и nginx 0.7.64

#cd /usr/ports/www/nginx
# cd ./work/nginx-0.7.64
# ./configure --add-module=/root/chunkin
# cd ../../
#make rmconfig && make config && make deinstall && make install


Затем вносим в конфиг nginx (обычно это /usr/local/etc/nginx/nginx.conf) в секцию http параметр

chunkin on;

и перезапускаем nginx

Еще один сюрприз – ошибка 501


И что вы думаете? Все заработало? Нет, конечно же.

Следующей напастью стала ошибка 501 Method Not Implemented. Ее причину удалось локализовать достаточно быстро, посмотрев логи сервера. Несмотря на то, что я прописал в интерфейсе QIWI адрес своего вебсервиса /account/result/qiwi.html, клиент QIWI упорно бился в закрытую дверь – он пытался делать запрос на несуществующий адрес /account/result/qiwi/

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

Последняя и самая страшная неожиданность


Ну и теперь, когда вроде бы уже все готово, QIWI подготовил мне еще один сюрприз. При попытке тестирования веб-сервиса магазина из интерфейса QIWI я неизменно получал сообщение «При запросе произошла ошибка: error in msg parsing: xml was empty, didn't parse!»

На самом деле это хороший знак – значит nuSOAP работает, и даже отдает правильный, с точки зрения QIWI SOAP-ответ на запрос. Вот только никаких данных от QIWI он не видит…

Отладка показала, что массив $_POST пуст, от QIWI не приходит никаких данных.

Вот тут я впал в полный ступор. Очевидной причины такого поведения нет, но оно все-таки есть!
Сборка nginx с параметром --with-debug, двухдневный анализ логов, консультации со спецами по nginx (огромная благодарность товарищу MiksIr) показали довольно-таки неприглядную картину. Nginx с установленным модулем chunkin действительно добросовестно собирал chuncked данные в единое целое и готовил для дальнейшей передачи к Apache-у запрос с правильно указанным заголовком Content-length. Однако вместо того, чтобы полностью удалить из исходного запроса заголовок Transfer-encoding: chuncked он (модуль chunkin) лишь обрезал его значение, оставляя примерно в таком виде «Transfer-encoding:». Apache на такое безобразие вполне логично ругался.

Сказать, что проблему удалось быстро – не могу. Однако она все-таки решилась. Итак:

Идем в исходники модуля NginxHttpChunkinModule, файл ngx_http_chunckin_filter_module.c и комментируем следующие строчки:

r->headers_in.transfer_encoding->value.len = 0;
r->headers_in.transfer_encoding->value.data = (u_char*) "";
ngx_http_chunkin_clear_transfer_encoding(r);


Глубоко разбираться в коде не было времени, так что можно считать это грубым, но действенным хаком.
Снова пересобираем nginx, и – вуаля!

Неожиданность совсем последняя


Создаю у себя транзакцию, вижу ее номер. Иду в личный кабинет QIWI и пытаюсь ей вручную задать статус «Оплачено». Тестовый скрипт QIWI добросовестно показывает мне 0 (нуль), но ничего не происходит…
Не буду утомлять читателей описание процесса поиска этого глюка. Скажу лишь, что тестовый скрипт всегда выдает нуль, независимо от ответа Вашего сервера, это «особенность системы (с) поддержка QIWI», а вот учесть тот факт, что написанная на Java-е клиентская часть платежной системы почему-то считает своим долгом передать хэш Вашего пароля в верхнем регистре – нужно обязательно.

Итог.


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

Заранее спасибо, надеюсь, что моя статья поможет кому-то сэкономить хотя бы несколько минут драгоценного времени!

P.S. Ссылку на проект мог бы дать — да боюсь хабраэффекта. Может быть потом, в соответствующем блоге, когда буду уверен, что сервер выдержит…

UPD. Перенес в блог «Платежные системы»
Tags:phpqiwiплатежные системы
Hubs: Payment systems
Total votes 71: ↑67 and ↓4 +63
Views33.1K

Popular right now

PHP-разработчик fullstack (Linux, Apache, MySQL, PHP)
from 250,000 ₽КАУССанкт-ПетербургRemote job
PHP Developer (fullstack)
from 120,000 to 150,000 ₽Группа проектов М1-shopRemote job
PHP developer (symfony, highload service)
to 150,000 ₽ВсеИнструменты.руRemote job
PHP Developer
from 150,000 to 250,000 ₽BotHelpRemote job
Senior/Middle PHP Developer
from 140,000 to 230,000 ₽РобоФинансЕкатеринбургRemote job