Библиотека для работы с QIWI через SOAP

в 11:45, , рубрики: composer, packagist, php, Qiwi, soap, метки: , , , ,

Так уж получилось, что мы решили у себя подключить прием платежей через 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js