Добрый день, Хабровчане!
Сегодня я хочу поделится опытом написания платежного метода в Magento. Платежный метод как пример был выбран не случайно, а как одна из самых важных и в тоже время сильно зависящих от страны пользователя частей интернет магазина. А так как в стандартной поставке Magento присутствуют методы в основном направленные на западного пользователя, возможно, эта статья пригодится кому то на просторах СНГ. Изначально в качестве подопытного хотелось написать поддержку QiWi, но тестовых учетных записей там создавать не разрешено, поэтому был выбран способ оплаты через Robokassa.
Прежде всего нам необходимо создать новый модуль, о том как это сделать более подробно можно почитать здесь — http://habrahabr.ru/blogs/php/80108/. Мы же попытаемся сконцентрироваться именно не платежном методе.
Итак, для начала мы должны объявить о своем существовании, положив соответствующий xml файл в директорию app/etc/modules/.
Sample_Robokassa.xml:
Эти строки подскажут Magento где искать наш модуль. Далее нам необходимо создать конфигурационный файл нашего модуля и положить его в соответствии с предыдущим фалом.
app/code/local/Sample/Robokassa/etc/config.xml:
И последний XML файл — это файл настроек, которые будут добавлены в админку.
После добавления всех этих файлов в админке по пути System->Configuration->Payment Methods->Robokassa должно появится что-то наподобие этого:

При написании модели платежного метода стоить обратить особое внимание на секцию атрибутов абстрактного класса Mage_Payment_Model_Method_Abstract, от которого мы и будем наследоваться. Из всех этих свойств нам понадобится только 3:
В процедуре инициализации заказу будет проставлен специальный статус — «pending_payment», который означает, что пользователь был перенаправлен на сторону платежной системы, но платеж все еще не произведен. В этом статусе из админки с заказом ничего делать нельзя.
Основной задачей нашей модели будет составление правильного запроса для робокассы, и проверка ответа от нее же. Для этого реализуем два метода, которые будем вызывать из контроллеров:
Тут я вырезал PHPDoc в целях экономии места. В первом методе у нас возвращается набор полей необходимых для передачи на робокассу в форме массива, во втором — заново генерируется сигнатура и сравнивается с переданным значением для того чтобы не дать злоумышленникам подделать ответ от робокассы. К слову, в робокассе используется 2 пароля: один для генерации ключа при комуникации magento->robokassa, второй — наоборот. Так же для того, чтобы система расшифровывала пароли при загрузке через стандартный метод getConfigData, нужно упомянуть, что эти поля являются зашифрованными в файле config.xml.
Кроме того здесь присутствует важный метод getOrderPlaceRedirectUrl, который позволит перенаправить пользователя после размещения заказа на нужную нам страницу. На этой странице мы нарисуем форму с данными из нашей модели и отправим ее яваскриптом на страницу робокассы. После этого пользователь увидит долгожданную форму оплаты.
На этом шаге наш платежный метод должен отображаться в списке методов на странице оплаты.
Контроллер у нас будет один и будет нацелен на 3 основных задачи:
1. Поставить заказу нужный статус после подтверждения покупки:
2. Обработки удачного возврата пользователя:
3. Обработка возврата с ошибкой:
Вот в принципе и все, данный модуль будет позволять размещать заказы и снимать деньги через робокассу. Еще в нем можно реализовать поддержку XML API робокассы, это позволит более гибко работать с валютами. Так же можно реализовать другие стандартные возможности платжных методов как: отладка, выбор статуса ордера при размещении и многое другое.
В статье опубликована бóльшая часть исходников. Отсутствует только уровень view и не совсем важные участки кода, которые вместе с остальными исходниками можно найти в diff файле. Дифф файл можно установить на существующую копию Magento командой patch в Linux, либо программой TortoiseSVN в Windows.
Сегодня я хочу поделится опытом написания платежного метода в Magento. Платежный метод как пример был выбран не случайно, а как одна из самых важных и в тоже время сильно зависящих от страны пользователя частей интернет магазина. А так как в стандартной поставке Magento присутствуют методы в основном направленные на западного пользователя, возможно, эта статья пригодится кому то на просторах СНГ. Изначально в качестве подопытного хотелось написать поддержку QiWi, но тестовых учетных записей там создавать не разрешено, поэтому был выбран способ оплаты через Robokassa.
Прежде всего нам необходимо создать новый модуль, о том как это сделать более подробно можно почитать здесь — http://habrahabr.ru/blogs/php/80108/. Мы же попытаемся сконцентрироваться именно не платежном методе.
1. Создание XML фалов
Итак, для начала мы должны объявить о своем существовании, положив соответствующий xml файл в директорию app/etc/modules/.
Sample_Robokassa.xml:
<?xml version="1.0"?>
<config>
<modules>
<Sample_Robokassa>
<active>true</active>
<codePool>local</codePool>
<depends>
<Mage_Sales/>
<Mage_Checkout/>
</depends>
</Sample_Robokassa>
</modules>
</config>
Эти строки подскажут Magento где искать наш модуль. Далее нам необходимо создать конфигурационный файл нашего модуля и положить его в соответствии с предыдущим фалом.
app/code/local/Sample/Robokassa/etc/config.xml:
<?xml version="1.0"?>
<config>
<modules>
<Sample_Robokassa>
<version>1.0.0.0</version>
</Sample_Robokassa>
</modules>
<global>
<models>
<robokassa>
<class>Sample_Robokassa_Model</class><!-- Тут у нас лежат модели -->
</robokassa>
</models>
<blocks>
<robokassa>
<class>Sample_Robokassa_Block</class><!-- Наши блоки -->
</robokassa>
</blocks>
<helpers>
<robokassa>
<class>Sample_Robokassa_Helper</class><!-- И helper для переводов -->
</robokassa>
</helpers>
</global>
<frontend>
<routers>
<robokassa>
<use>standard</use>
<args>
<module>Sample_Robokassa</module>
<frontName>robokassa</frontName>
</args>
</robokassa>
</routers>
</frontend>
</config>
И последний XML файл — это файл настроек, которые будут добавлены в админку.
<?xml version="1.0"?>
<config>
<sections>
<payment>
<groups>
<robokassa_redirect translate="label">
<label>Robokassa</label>
<frontend_type>text</frontend_type>
<sort_order>1</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<fields>
<active translate="label">
<label>Enabled</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</active>
<title translate="label">
<label>Title</label>
<frontend_type>text</frontend_type>
<sort_order>20</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</title>
<login translate="label">
<label>Merchant Login</label>
<frontend_type>obscure</frontend_type>
<backend_model>adminhtml/system_config_backend_encrypted</backend_model>
<sort_order>30</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</login>
<password1 translate="label">
<label>Merchant Password #1</label>
<!--Введенный пароль будет хранится в шифрованом виде -->
<frontend_type>obscure</frontend_type>
<backend_model>adminhtml/system_config_backend_encrypted</backend_model>
<sort_order>40</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</password1>
<password2 translate="label">
<label>Merchant Password #2</label>
<!--Введенный пароль будет хранится в шифрованом виде -->
<frontend_type>obscure</frontend_type>
<backend_model>adminhtml/system_config_backend_encrypted</backend_model>
<sort_order>45</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</password2>
<test translate="label">
<label>Test Mode</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>50</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</test>
<sort_order translate="label">
<label>Sort Order</label>
<frontend_type>text</frontend_type>
<sort_order>60</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</sort_order>
</fields>
</robokassa_redirect>
</groups>
</payment>
</sections>
</config>
После добавления всех этих файлов в админке по пути System->Configuration->Payment Methods->Robokassa должно появится что-то наподобие этого:

2. Модели
При написании модели платежного метода стоить обратить особое внимание на секцию атрибутов абстрактного класса Mage_Payment_Model_Method_Abstract, от которого мы и будем наследоваться. Из всех этих свойств нам понадобится только 3:
protected $_canUseForMultishipping = false;//Не использовать доставку на многие адреса
protected $_canUseInternal = false;//Не использовать в админке
protected $_isInitializeNeeded = true;//Вызвать процедуру инициализации до перенаправления пользователя
В процедуре инициализации заказу будет проставлен специальный статус — «pending_payment», который означает, что пользователь был перенаправлен на сторону платежной системы, но платеж все еще не произведен. В этом статусе из админки с заказом ничего делать нельзя.
Основной задачей нашей модели будет составление правильного запроса для робокассы, и проверка ответа от нее же. Для этого реализуем два метода, которые будем вызывать из контроллеров:
public function getRedirectFormFields()
{
$result = array();
$session = Mage::getSingleton('checkout/session');
$order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId());
if (!$order->getId()) {
return $result;
}
$result['MrchLogin'] = $this->getConfigData('login');
$result['OutSum'] = $order->getBaseGrandTotal();
$result['InvId'] = $order->getIncrementId();
$result['Desc'] = 'Shopping in' . Mage::getStoreConfig(Mage_Core_Model_Store::XML_PATH_STORE_STORE_NAME);
$result['IncCurrLabel'] = 'AlfaBankR';//можно вынести в настройки
$result['SignatureValue'] = md5(
$result['MrchLogin'] . ':'
. $result['OutSum'] . ':'
. $result['InvId'] . ':'
. $this->getConfigData('password1')
);
return $result;
}
public function validateRequest($request)
{
if (!isset($request['OutSum']) || !isset($request['InvId']) || !isset($request['SignatureValue'])) {
return false;
}
$crc = md5($request['OutSum'] . ':' . $request['InvId'] . ':' . $this->getConfigData('password2'));
return strtoupper($request['SignatureValue']) == strtoupper($crc);
}
Тут я вырезал PHPDoc в целях экономии места. В первом методе у нас возвращается набор полей необходимых для передачи на робокассу в форме массива, во втором — заново генерируется сигнатура и сравнивается с переданным значением для того чтобы не дать злоумышленникам подделать ответ от робокассы. К слову, в робокассе используется 2 пароля: один для генерации ключа при комуникации magento->robokassa, второй — наоборот. Так же для того, чтобы система расшифровывала пароли при загрузке через стандартный метод getConfigData, нужно упомянуть, что эти поля являются зашифрованными в файле config.xml.
Кроме того здесь присутствует важный метод getOrderPlaceRedirectUrl, который позволит перенаправить пользователя после размещения заказа на нужную нам страницу. На этой странице мы нарисуем форму с данными из нашей модели и отправим ее яваскриптом на страницу робокассы. После этого пользователь увидит долгожданную форму оплаты.
На этом шаге наш платежный метод должен отображаться в списке методов на странице оплаты.
3. Контроллеры
Контроллер у нас будет один и будет нацелен на 3 основных задачи:
1. Поставить заказу нужный статус после подтверждения покупки:
$request = Mage::app()->getRequest()->getPost();
$paymentMethod = Mage::getModel('robokassa/redirect');
if (!$paymentMethod->validateRequest($request)) {
return;
}
$order = Mage::getModel('sales/order')->loadByIncrementId($request['InvId']);
/*Переводим заказ в режим позволяющий дальнейшую работу с ним через админку*/
$order->setState(Mage_Sales_Model_Order::STATE_PROCESSING);
$order->setStatus('processing');
$order->setIsNotified(false);
$order->save();
echo 'OK' . $request['InvId']; //Говорим робокассе, что мы удачно приняли запрос
2. Обработки удачного возврата пользователя:
$session = Mage::getSingleton('checkout/session');
$session->setQuoteId($session->getRobokassaQuoteId());
/* Все прошло хорошо, можно деактивировать корзину */
Mage::getSingleton('checkout/session')->getQuote()->setIsActive(false)->save();
$this->_redirect('checkout/onepage/success', array('_secure'=>true));
3. Обработка возврата с ошибкой:
$session = Mage::getSingleton('checkout/session');
$session->setQuoteId($session->getRobokassaQuoteId());
if ($session->getLastRealOrderId()) {
$order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId());
if ($order->getId()) {
$order->cancel()->save();//Отменяем заказ, который имеет "pending_payment" статус
}
}
$quote = Mage::getModel('sales/quote')->load($session->getRobokassaQuoteId());
if ($quote->getId()) {
$quote->setActive(true);//Возвращаем корзину к жизни
$quote->save();
}
/* Выводим пользователю ошибку */
$session->addError(Mage::helper('robokassa')->__('Payment failed. Pleas try again later.'));
$this->_redirect('checkout/cart');
Вот в принципе и все, данный модуль будет позволять размещать заказы и снимать деньги через робокассу. Еще в нем можно реализовать поддержку XML API робокассы, это позволит более гибко работать с валютами. Так же можно реализовать другие стандартные возможности платжных методов как: отладка, выбор статуса ордера при размещении и многое другое.
В статье опубликована бóльшая часть исходников. Отсутствует только уровень view и не совсем важные участки кода, которые вместе с остальными исходниками можно найти в diff файле. Дифф файл можно установить на существующую копию Magento командой patch в Linux, либо программой TortoiseSVN в Windows.