Добрый день, Хабровчане!
Сегодня я хочу поделится опытом написания платежного метода в 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.
