Pull to refresh

Пишем платежный метод для Magento на примере Robokassa

Reading time8 min
Views9.2K
Добрый день, Хабровчане!

Сегодня я хочу поделится опытом написания платежного метода в 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 должно появится что-то наподобие этого:
image

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.
Tags:
Hubs:
+6
Comments1

Articles