📜 ⬆️ ⬇️

We write the payment method for Magento on the example of Robokassa

Good afternoon, Habrovchane!

Today I want to share the experience of writing a payment method in Magento. The payment method as an example was not chosen by chance, but as one of the most important and at the same time strongly dependent on the country of the user of parts of the online store. And since the standard delivery of Magento contains methods mainly aimed at Western users, this article may be useful to someone in the CIS. Initially, I wanted to write support for QiWi as an experimental one, but it was not allowed to create test accounts there, so the payment method through Robokassa was chosen.


')
First of all, we need to create a new module, how to do this in more detail can be read here - http://habrahabr.ru/blogs/php/80108/ . We will try to concentrate on the non-payment method.

1. Creating XML Files



So, first we have to declare our existence by putting the corresponding xml file in the app / etc / modules / directory.

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> 


These lines will tell Magento where to look for our module. Next, we need to create a configuration file for our module and put it in accordance with the previous file.

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> 


And the last XML file is a file of settings that will be added to the admin area.

 <?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> 


After adding all these files to the admin panel on the System-> Configuration-> Payment Methods-> Robokassa path, something like this should appear:
image

2. Models



When writing a payment method model, it is worth paying special attention to the attributes section of the abstract class Mage_Payment_Model_Method_Abstract, from which we will inherit. Of all these properties, we need only 3:
 protected $_canUseForMultishipping = false;//      protected $_canUseInternal = false;//    protected $_isInitializeNeeded = true;//      

In the initialization procedure, the order will be marked with a special status - “pending_payment”, which means that the user has been redirected to the side of the payment system, but the payment has not yet been made. In this status from the admin with the order nothing can be done.

The main task of our model will be to compile the correct query for the robokassa, and check the response from it. To do this, we implement two methods that we will call from controllers:
 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); } 


Here I cut out PHPDoc in order to save space. In the first method, we return a set of fields required for transmission to the robocassa in the form of an array, in the second method, the signature is re-generated and compared with the transmitted value in order to prevent attackers from forging a response from the robocash ticket. By the way, in the robocasher 2 passwords are used: one to generate the key when communicating magento-> robokassa, the second - vice versa. Also, in order for the system to decrypt passwords when loading via the standard getConfigData method, it is necessary to mention that these fields are encrypted in the config.xml file.

In addition, there is an important getOrderPlaceRedirectUrl method that will allow you to redirect the user after placing an order on the page we need. On this page we will draw a form with the data from our model and send it by javascript to the robocash desk. After that, the user will see the long-awaited form of payment.

In this step, our payment method should appear in the list of methods on the payment page.

3. Controllers



We will have one controller and will focus on 3 main tasks:

1. Put the order the desired status after confirmation of purchase:
 $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. Handling a successful user return:
 $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. Return processing with an error:
 $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'); 


That's basically all, this module will allow you to place orders and withdraw money through the robokassa. You can also implement support for the XML API of the robocash desk, it will allow you to work with currencies more flexibly. It is also possible to implement other standard features of payment methods such as: debugging, order status selection during placement, and much more.

The article published most of the source code. Only the view level is missing, and not quite important parts of the code, which, together with the other sources, can be found in the diff file . The diff file can be installed on an existing copy of Magento using the patch command in Linux, or using the TortoiseSVN program in Windows.

Source: https://habr.com/ru/post/127916/


All Articles