Так уж получилось, что мы решили у себя подключить прием платежей через QIWI. Сказано — сделано! Вот только в процессе разработки пришлось столкнуться с убогостью примеров кода от разработчиков киви:
<?php
/**
* На этот скрипт приходят уведомления от QIWI Кошелька.
* SoapServer парсит входящий SOAP-запрос, извлекает значения тегов login, password, txn, status,
* помещает их в объект класса Param и вызывает функцию updateBill объекта класса TestServer.
*
* Логика обработки магазином уведомления должна быть в updateBill.
*/
$s = new SoapServer('IShopClientWS.wsdl', array('classmap' => array('tns:updateBill' => 'Param', 'tns:updateBillResponse' => 'Response')));
// $s = new SoapServer('IShopClientWS.wsdl');
$s->setClass('TestServer');
$s->handle();
class Response {
public $updateBillResult;
}
class Param {
public $login;
public $password;
public $txn;
public $status;
}
class TestServer {
function updateBill($param) {
// Выводим все принятые параметры в качестве примера и для отладки
$f = fopen('c:\phpdump.txt', 'w');
fwrite($f, $param->login);
fwrite($f, ', ');
fwrite($f, $param->password);
fwrite($f, ', ');
fwrite($f, $param->txn);
fwrite($f, ', ');
fwrite($f, $param->status);
fclose($f);
// проверить password, login
// В зависимости от статуса счета $param->status меняем статус заказа в магазине
if ($param->status == 60) {
// заказ оплачен
// найти заказ по номеру счета ($param->txn), пометить как оплаченный
} else if ($param->status > 100) {
// заказ не оплачен (отменен пользователем, недостаточно средств на балансе и т.п.)
// найти заказ по номеру счета ($param->txn), пометить как неоплаченный
} else if ($param->status >= 50 && $param->status < 60) {
// счет в процессе проведения
} else {
// неизвестный статус заказа
}
// формируем ответ на уведомление
// если все операции по обновлению статуса заказа в магазине прошли успешно, отвечаем кодом 0
// $temp->updateBillResult = 0
// если произошли временные ошибки (например, недоступность БД), отвечаем ненулевым кодом
// в этом случае QIWI Кошелёк будет периодически посылать повторные уведомления пока не получит код 0
// или не пройдет 24 часа
$temp = new Response();
$temp->updateBillResult = 0;
return $temp;
}
}
?>
Я конечно понимаю, пример исчерпывающий, но можно ведь было что-нибудь «поготовее» выложить? Поскольку система популярна, как и язык PHP — я решил сразу вынести библиотеку в публичный репозитарий, дабы упростить жизнь тем, кому только предстоит подключать эту систему. Так как в недавнем моем вопросе никто против поста не возражал — выкладываю ее тут.
В библиотеке есть как клиентская, так и серверная часть. Подключается элементарно — выгружаем через git или добавляем зависимость в composer (nick4fake/ishop). Основная работа идет через экземпляр класса IShopClient. Например, можно от него наследоваться, и в конструкторе указать имя/пароль магазина, а класс оформить как сервис Symfony2 (так у нас сделано):
<?php
namespace MyOwnMegaPrefixQiwi;
use MyOwnMegaPrefixSettings;
use IShop;
class Qiwi extends IShopClient {
protected $settings;
public function __construct(
Settings $settings // Нечто, что выдает нам настройки
) {
$this->settings = $settings;
parent::__construct(
$this->settings->get('qiwi.login'),
$this->settings->get('qiwi.pass')
);
}
}
Работа клиентом (это тот, кто запросы на SOAP-сервер шлет) элементарна — берем экземпляр IShopClient и вызываем соответствующие методы. Они — лишь простые обертки над классом для работы с php-soap, преобразовывают параметры, да код возврата меняют на более подробный статус. Поля даты — в DateTime, все остальное — соответствует api qiwi (только логин/пароль дублировать не нужно).
Работа с сервером чуть сложнее. Поскольку методы сервера вызываются весьма хитро (SOAP же), было решено использовать замыкания для обработки запроса. Да, не забываем отключить сертификат x509 в настройках. Я так и не нашел способа прикрутить WSSE к php-soap (насколько я понял, никто не нашел). Обидно то, что в примере кода для Java проверка сертификата есть.
use IShopServerMethodsCheckBillResponse as QiwiBill;
$callback = function ($bill) use (&$myMegaService) {
/** @var QiwiBill $bill */
$row = $myMegaService->findByKey( // Ищем чек в нашей базе
$bill->id
);
if (!$row) {
throw new Exception('Неправильный код чека');
}
$myMegaService->process($row); // Что-то делаем с этим
return $myMegaService->status(); // Код возврата для сервера QIWI. 0 - все нормально
};
$theIShopObject->processRequest($callback); // Вызываем метод обработки запроса
header('Content-Type: text/xml; charset=utf-8'); // Если мы отдадим text/html, qiwi не пропустит платеж (да и вообще, надо протоколу следовать)
Удобно? Мне кажется — да. Вы можете просто сравнить с альтернативным путем. Итак, преимущества библиотеки:
- Можно вообще не задумываться о SOAP, WSDL и т.д. Все просто работает.
- Для сервера есть проверка подписи, после обновления статуса, чек повторно подгружается с сервера (как рекомендуют доки киви).
- PHPDoc и все-все-все, так сложнее ошибиться. Кое-где есть дополнительные уточнения (где грабли лежат).
- Статусы расшифровываются (код -> текст).
- Есть в packagist, подключение займет 2 минуты.
- Нормально работает с автолоадером.
Само-собой, есть минусы:
Какой-то стрёмный тип предлагает вам выписывать чеки через его библиотеку.- PHP 5.3. Замыкания + неймспейсы. Если вас это не устраивает, повырезать новое не составит труда.
Ссылки: описание протокола, пример кода, репозитарий.
Уже после того, как система была прикручена, я узнал о готовой библиотеке для работы с киви — от пользователя JekaRu. Значит, будет больший выбор. :)
Спасибо за внимание. Если будут замечания касательно чистоты кода — с радостью внемлю (только не говорите, что не PRS, я к нему еще не готов).
Автор: nick4fake