In this article, we will take a look at developing the integration of Magento 2 with any external ERP system. Magento 2.2.4 compatible syntax will be used to show the examples of code. The protocol of the interaction with ERP will be REST / JSON API, a two-way data exchange.
Before starting to work on the integration, it is necessary to determine the functionality and set requirements for the system.
Let us define what we need:
- always up-to-date data from both the website and ERP;
- reliability of the exchange (data must be validated; in case of failure, the exchange must be repeated);
- monitoring system — logging and notification in case of errors.
In the context of this article, we omit the explanations to the components that are not directly related to the topic at hand.
Let’s start implementing the integration of Magento 2 with ERP. The first step is to create a new module. To do this, create the following directory structure:
app/code/BelVG/ERPIntegration/
app/code/BelVG/ERPIntegration/etc
app/code/BelVG/ERPIntegration/Setup
app/code/BelVG/ERPIntegration/Helper
app/code/BelVG/ERPIntegration/Controller
app/code/BelVG/ERPIntegration/Observer
Create an app/code/BelVG/ERPIntegration/registration.php file:
1 2 3 4 5 6 |
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'BelVG_ERPIntegration', __DIR__ ); |
Create an app/code/BelVG/ERPIntegration/etc/module.xml file:
1 2 3 4 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="BelVG_ERPIntegration" setup_version="1.0.0" /> </config> |
Implement the installer of the module. It must add new attributes to entities, such as category, product, customer, order, and others that are exchanged. This attribute must be unique, as it will play the role of a common identifier for the site and ERP. We make it a string type, so as not to depend on the implementation of ERP.
Create an app/code/BelVG/ERPIntegration/Setup/InstallData.php file (here is an example of creating an attribute for a product).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php namespace BelVG\ERPIntegration\Setup; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Eav\Setup\EavSetupFactory; class InstallData implements InstallDataInterface { private $eavSetupFactory; public function __construct(EavSetupFactory $eavSetupFactory) { $this->eavSetupFactory = $eavSetupFactory; } public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $eavSetup->removeAttribute(ProductAttributeInterface::ENTITY_TYPE_CODE,'product_erp_id'); $eavSetup->addAttribute( ProductAttributeInterface::ENTITY_TYPE_CODE, 'product_erp_id', [ 'type' => 'varchar', 'frontend' => '', 'label' => 'Product ERP ID', 'input' => 'multiselect', 'class' => '', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'visible' => 1, 'required' => false, 'user_defined' => 1, 'default' => '', 'searchable' => true, 'filterable' => true, 'comparable' => false, 'visible_on_front' => false, 'used_in_product_listing' => false, 'unique' => true ]); } } |
We implement the component responsible for exchanging data with ERP. It should have a closed logic of sending requests to simplify working with the code. In the example below, we will use the CURL component for REST requests to the ERP API.
To do this, create an app/code/BelVG/ERPIntegration/Helper/Requester.php file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?php namespace BelVG\ERPIntegration\Helper; use \Magento\Framework\App\Helper\AbstractHelper; use \Magento\Framework\App\Helper\Context; use Magento\Framework\Exception\LocalizedException; class Requester extends AbstractHelper { protected $curl; const ERP_API_BASE_URL = 'https://127.0.0.1/erp/api/'; public function __construct( Context $context, \Magento\Framework\HTTP\Client\Curl $curl ) { $this->curl = $curl; parent::__construct($context); } public function createProduct(array $product) { $url = self::ERP_API_BASE_URL . 'product/create'; $this->curl->post($url, $product); if ($this->curl->getStatus() !== 200) { throw new LocalizedException(__('ERP query fail!')); } $response = json_decode($this->curl->getBody(), true); if ($response['success']) { return $response['product_erp_id']; } else { throw new LocalizedException(__($response['error_message'])); } } public function updateProduct(array $product) { $url = self::ERP_API_BASE_URL . 'product/create'; if (!$product['product_erp_id']) { throw new LocalizedException(__('Invalid product ERP ID!')); } $this->curl->post($url, $product); if ($this->curl->getStatus() !== 200) { throw new LocalizedException(__('ERP query fail!')); } $response = json_decode($this->curl->getBody(), true); if ($response['success']) { return $response['product_erp_id']; } else { throw new LocalizedException(__($response['error_message'])); } } } |
In order to send to ERP the data that has been created or changed on the site, you must set the capture of the save events on the entities that the Site ➜ ERP exchange is carried out.
To implement this functionality, create an observer for the product_save_after event.
Create an app/code/BelVG/ERPIntegration/Observers/ProductSaveAfterObserver.php file. It also has logging implemented in it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<?php namespace BelVG\ERPIntegration\Observer; use Magento\Catalog\Model\Product; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Catalog\Model\ProductRepository; class ProductSaveAfterObserver implements ObserverInterface { protected $requester; protected $productRepository; protected $logger; public function __construct( \BelVG\ERPIntegration\Helper\Requester $requester, ProductRepository $productRepository, \Psr\Log\LoggerInterface $logger ) { $this->requester = $requester; $this->productRepository = $productRepository; $this->logger = $logger; } public function execute(\Magento\Framework\Event\Observer $observer) { $product = $observer['product']; if (!$this->isProductChanged($product) && $product->getData('product_erp_id')) { return; } try { if ($product->getData('product_erp_id')) { $erpId = $this->requester->updateProduct([ 'name' => $product->getName(), 'category_erp_id' => $product->getData('category_erp_id'), ]); } else { $erpId = $this->requester->createProduct([ 'name' => $product->getName(), 'category_erp_id' => $product->getData('category_erp_id'), ]); } } catch (LocalizedException $e) { $this->logger->alert($e->getMessage()); return; } $product->setData('category_erp_id'. $erpId); $this->productRepository->save($product); $this->logger->info('Product ' . $erpId . ' has been synchronized'); } private function isProductChanged(Product $product) { return $product->getOrigData('name') != $product->getName(); } } |
Also, create an app/code/BelVG/ERPIntegration/etc/events.xml file, where we assign this observer to the desired event.
1 2 3 4 5 6 7 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="product_save_after"> <observer name="setcustomerupdatedat" instance="Belvg\ERPIntegration\Observer\ProductSaveAfterObserver"/> </event> </config> |
To implement a relevance check and a scheduled data exchange, you can use CRON similarly to the method of event handling.
You can also implement external API integrations for direct calls from ERP if the data has been changed on the ERP side and requires immediate synchronization. The API of both the ERP and the Magento 2 module should be closed by authorization, or in some other way (for example, by an IP filter).
It remains to implement the error notifications. Let us enable email notifications. This component is an example, it can be used in a catch block along with logging:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?php namespace BelVG\ERPIntegration\Helper; use \Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\Mail\Template\TransportBuilder; class Notify extends AbstractHelper { /** @var TransportBuilder $transportBuilder */ private $transportBuilder; public function __construct( \Magento\Framework\App\Helper\Context $context, TransportBuilder $transportBuilder ) { $this->transportBuilder = $transportBuilder; parent::__construct($context); } public function reportingError($process, $message { try { $subject = 'ERP synchronize error'; $this->sendEmail($subject, $process, $message); } catch (\Exception $e) {} } private function sendEmail($subject, $process, $message) { $transport = $this->transportBuilder ->setTemplateIdentifier('erp_integration_error_notify_template') ->setTemplateOptions( ['area' => 'frontend', 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID] ) ->setTemplateVars([ 'process' => $process, 'message' => $message, ]) ->setFrom('general') ->getTransport(); $transport->sendMessage(); } } |
When completed, the module should be installed. To do this, execute the following commands in the Magento console (from the Magento root directory):
php bin/magento setup:upgrade
php bin/magento setup:di:compile
That is how you integrate Magento with ERP systems. Thank you for reading, hope everything was clear. Leave your comments and questions below and I will answer them ASAP.