Pass Magento 2 Certified Professional Developer Plus Exam

It’s been a long time, No new post publish because I was so busy with Magento 2 exams. I have passed 5 Magento 2 certifications. My latest certification was Magento 2 Certified Professional Developer Plus. Today I share my experience and how I prepared Magento 2 Certified Professional Developer Plus exam.

When I took the exam?
I took my Magento 2 Certified Professional Developer Plus exam on 31st December 2018.

How I start preparation?
Magento 2 Certified Professional Developer Plus exam related resource is not available so much. Also, this exam is so young. When Magento announce this exam, I start preparation. My Magento 2 Certified Professional Developer exam preparation help me a lot. I start digging Magento 2 commerce feature. At this point, Magento 2 Certified Solution specialist exam preparation helps me to Magento 2 commerce default functionality. I start practicing Staging, Segmentation, RMA, MessageQueue, Database Sharding etc. feature. I digging how a feature works by coding.

Firstly I find out that area, which I am weak. Then I start that area. It was so much dedication because after full-time work, taking preparation is not easy. But I did that.

How can take the exam?
Anyone who have 2 years working experience with Magento 2 commerce. Actually you need hands on experience with it. This exam is builds on Magento 2 commerce only. So it is necessary to work with Magento 2 commerce and Magento 2 commerce knowledge.

How hard this exam compare to others exam, especially professional?
Well, I would say this is hard more than all exam. I see many great magento developer fail their first attempt. You need so deep knowledge about Magento 2 commerce. So before taking exam you must revise all area (according to study guide) deeply.

How deep we need to study?
Well, it is sooo deep 😀 . For example: for console command, you need to learn in detail. How you declare in di.xml which area you need to declare (global or frontend, or adminhtml) that, how you declare required/optional option, what is the common error and how to fixed that, how interact with commerce feature etc. Same for all. It is must to check how interact with Magento 2 commerce which feature that is common for community edition.

What is the passing score?

  • 60 multiple choice questions
  • 90 minutes to complete the exam
  • 62% or higher needed to pass

For more details check official site

Where can I find good resource?
Still devdocs is good resource that I have found. Also check every topic from study guide. Also follow my blog 😀 , I have a plan to discuss important topic one by one. Read use guide for default feature.

One important feature I have found for this exam is Security. This feature should include for Professional exam too. Security is so important when you build something for merchant. So you must know how magento 2 handle this stuff.

Feel free to reach me if you have any question regarding certification. I am active in Magento Slack, Magento StackExchange or through my blog.

How to overwrite Swatch Renderer JS in Magento 2

In Magento 2, you can easily overwrite any js using mixin. So today I will show, how you can overwrite core Swatch Renderer Js.

Step 1: Create app/code/VendorName/ModuleName/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Swatches/js/swatch-renderer': {
                'VendorName_ModuleName/js/swatch-renderer-mixin': true
            }
        }
    }
};

Step 2: Create app/code/VendorName/ModuleName/view/frontend/web/js/swatch-renderer-mixin.js

define(['jquery'], function ($) {
    'use strict';

    return function (SwatchRenderer) {
        $.widget('mage.SwatchRenderer', $['mage']['SwatchRenderer'], {
            _init: function () {
                console.log('getProductSwatchRenderer');
                this._super();
            }
        });
        return $['mage']['SwatchRenderer'];
    };
});

That’s it.

Now you need to remove static content and deploy static content.

How to get customer id from block when full page cache enable in magento 2

Today I discuss about getting customer_id from block when full page cache(FPC) is enable. If FPC is enable then you can’t get customer id from block just because magento2 reset all customer session. You can get customer session data if your layout set cacheable=”false” in that case magento2 just ignore cache whole page. How? lets dive.

If you open vendor/magento/module-customer/Model/Layout/DepersonalizePlugin.php


/**
 * After generate Xml
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @param \Magento\Framework\View\LayoutInterface $result
 * @return \Magento\Framework\View\LayoutInterface
 */
public function afterGenerateXml(\Magento\Framework\View\LayoutInterface $subject, $result)
{
    if ($this->depersonalizeChecker->checkIfDepersonalize($subject)) {
        $this->visitor->setSkipRequestLogging(true);
        $this->visitor->unsetData();
        $this->session->clearStorage();
        $this->customerSession->clearStorage();
        $this->session->setData(\Magento\Framework\Data\Form\FormKey::FORM_KEY, $this->formKey);
        $this->customerSession->setCustomerGroupId($this->customerGroupId);
        $this->customerSession->setCustomer($this->customerFactory->create()->setGroupId($this->customerGroupId));
    }
    return $result;
}

Now check the if condition, basically this return true/false from following function
vendor/magento/module-page-cache/Model/DepersonalizeChecker.php


/**
 * Check if depersonalize or not
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @return bool
 * @api
 */
public function checkIfDepersonalize(\Magento\Framework\View\LayoutInterface $subject)
{
    return ($this->moduleManager->isEnabled('Magento_PageCache')
        && $this->cacheConfig->isEnabled()
        && !$this->request->isAjax()
        && ($this->request->isGet() || $this->request->isHead())
        && $subject->isCacheable());
}

Here $subject->isCacheable() return false if your layout has ‘cacheable=”false”‘ attribute, lets check
vendor/magento/framework/View/Layout.php


/**
 * Check is exists non-cacheable layout elements
 *
 * @return bool
 */
public function isCacheable()
{
    $this->build();
    $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]'));
    return $this->cacheable && $cacheableXml;
}

So if full page cahce enable and cacheable=”false” is not exist then magento 2 reset customer session by


$this->session->clearStorage();
$this->customerSession->clearStorage();

That’s why if you try to get customer session data from block, it’s return always empty basically only group_id is present in customer session data.

So how to get customer information then.

You can follow Magento2 official tutorial.

Now Today I discuss how you can get customer_id if cache enable. Lets dive.

Suppose vendor name ‘SR‘ and module name ‘CustomerSession

Create a contex class [SR/CustomerSession/Model/Customer/Context.php]


namespace SR\CustomerSession\Model\Customer;

class Context
{
    /**
     * Customer authorization cache context
     */
    const CONTEXT_CUSTOMER_ID = 'logged_in_customer_id';
}

Now pluginize Magento\Framework\App\Action\AbstractAction dispatch method.
So create a di.xml [SR/CustomerSession/etc/frontend/di.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Action\AbstractAction">
        <plugin name="sr-customer-app-action-dispatchController-context-plugin" type="SR\CustomerSession\Plugin\App\Action\Context" sortOrder="15"/>
    </type>
</config>

Now create SR/CustomerSession/Plugin/App/Action/Context.php


namespace SR\CustomerSession\Plugin\App\Action;

use SR\CustomerSession\Model\Customer\Context as CustomerSessionContext;

class Context
{
    /**
     * @var \Magento\Customer\Model\Session
     */
    protected $customerSession;

    /**
     * @var \Magento\Framework\App\Http\Context
     */
    protected $httpContext;

    /**
     * @param \Magento\Customer\Model\Session $customerSession
     * @param \Magento\Framework\App\Http\Context $httpContext
     */
    public function __construct(
        \Magento\Customer\Model\Session $customerSession,
        \Magento\Framework\App\Http\Context $httpContext
    ) {
        $this->customerSession = $customerSession;
        $this->httpContext = $httpContext;
    }

    /**
     * @param \Magento\Framework\App\ActionInterface $subject
     * @param callable $proceed
     * @param \Magento\Framework\App\RequestInterface $request
     * @return mixed
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function aroundDispatch(
        \Magento\Framework\App\ActionInterface $subject,
        \Closure $proceed,
        \Magento\Framework\App\RequestInterface $request
    ) {
        $customerId = $this->customerSession->getCustomerId();
        if(!$customerId) {
            $customerId = 0;
        }

        $this->httpContext->setValue(
            CustomerSessionContext::CONTEXT_CUSTOMER_ID,
            $customerId,
            false
        );

        return $proceed($request);
    }
}

That’s it. Now you can able to get customer id following way even cache enable:


/**
* @var \Magento\Framework\App\Http\Context
*/
$this->httpContext->getValue(SR\CustomerSession\Model\Customer\Context::CONTEXT_CUSTOMER_ID);

Download full module from here

Generate invoice and shipment automatically when a new order is placed in Magento 2

Many merchants wants to create invoice / shipment and complete the order all at once when place order. So today I will discuss about how achieve their need. Although it’s custom needs/requirement so we need to create a custom module for that.

In that case, you need to do is to observe the checkout_submit_all_after event. So lets start.

Here Vendor is ‘SR’ and Module name ‘AutoInvoiceShipment’.

So create a module you can follow this post

Create events.xml [SR/AutoInvoiceShipment/etc/events.xml]

<?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="checkout_submit_all_after">
        <observer name="sr_auto_invoice_shipment_checkout_submit_all_after" instance="SR\AutoInvoiceShipment\Observer\CheckoutAllSubmitAfterObserver"/>
    </event>
</config>

Create observer class for that [SR/AutoInvoiceShipment/Observer/CheckoutAllSubmitAfterObserver.php]

namespace SR\AutoInvoiceShipment\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class CheckoutAllSubmitAfterObserver implements ObserverInterface
{
    /**
     *
     * @var \SR\AutoInvoiceShipment\Helper\Data
     */
    protected $helper;

    /**
     * @param \SR\AutoInvoiceShipment\Helper\Data $helper
     */
    public function __construct(
        \SR\AutoInvoiceShipment\Helper\Data $helper
    ) {
        $this->helper = $helper;
    }

    /**
     *
     * @param Observer $observer
     * @return $this
     */
    public function execute(Observer $observer)
    {
        if(!$this->helper->isEnabled()) {
            return $this;
        }

        $order = $observer->getEvent()->getOrder();
        if(!$order->getId()) {
            return $this;
        }

        $invoice = $this->helper->createInvoice($order);
        if($invoice) {
            $this->helper->createShipment($order, $invoice);
        }

        return $this;
    }
}

Create a helper[SR/AutoInvoiceShipment/Helper/Data.php]

namespace SR\AutoInvoiceShipment\Helper;

use Magento\Framework\App\Helper\AbstractHelper;

class Data extends AbstractHelper
{
    const MODULE_ENABLED = 'sr_auto_invoice_shipment/settings/enabled';

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     *
     * @var \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory
     */
    protected $invoiceCollectionFactory;

    /**
     *
     * @var \Magento\Sales\Model\Service\InvoiceService
     */
    protected $invoiceService;

    /**
     *
     * @var \Magento\Sales\Model\Order\ShipmentFactory
     */
    protected $shipmentFactory;

    /**
     *
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $transactionFactory;

    /**
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory
     * @param \Magento\Sales\Model\Service\InvoiceService $invoiceService
     * @param \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory,
        \Magento\Sales\Model\Service\InvoiceService $invoiceService,
        \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory,
        \Magento\Framework\DB\TransactionFactory $transactionFactory
    ) {
        parent::__construct($context);
        $this->scopeConfig = $scopeConfig;
        $this->invoiceCollectionFactory = $invoiceCollectionFactory;
        $this->invoiceService = $invoiceService;
        $this->shipmentFactory = $shipmentFactory;
        $this->transactionFactory = $transactionFactory;
    }

    public function createInvoice($order)
    {
        try {
            $invoices = $this->invoiceCollectionFactory->create()
                ->addAttributeToFilter('order_id', array('eq' => $order->getId()));

            $invoices->getSelect()->limit(1);

            if ((int)$invoices->count() !== 0) {
                return null;
            }

            if(!$order->canInvoice()) {
                return null;
            }

            $invoice = $this->invoiceService->prepareInvoice($order);
            $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
            $invoice->register();
            $invoice->getOrder()->setCustomerNoteNotify(false);
            $invoice->getOrder()->setIsInProcess(true);
            $order->addStatusHistoryComment('Automatically INVOICED', false);
            $transactionSave = $this->transactionFactory->create()->addObject($invoice)->addObject($invoice->getOrder());
            $transactionSave->save();
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
            return null;
        }

        return $invoice;
    }

    public function createShipment($order, $invoice)
    {
        try {
            $shipment = $this->prepareShipment($invoice);
            if ($shipment) {
                $order->setIsInProcess(true);
                $order->addStatusHistoryComment('Automatically SHIPPED', false);
                $this->transactionFactory->create()->addObject($shipment)->addObject($shipment->getOrder())->save();
            }
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
        }
    }

    public function prepareShipment($invoice)
    {
        $shipment = $this->shipmentFactory->create(
            $invoice->getOrder(),
            []
        );

        return $shipment->getTotalQty() ? $shipment->register() : false;
    }

    /**
     * Is the module enabled in configuration.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return $this->scopeConfig->getValue(self::MODULE_ENABLED);
    }
}

That’s it.

If you want an option that you can handle this module on/off. So create a configuration
[SR/AutoInvoiceShipment/etc/adminhtml/system.xml]

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="sr_auto_invoice_shipment" translate="label" type="text" sortOrder="1300" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Auto Invoice Shipment</label>
            <tab>sales</tab>
            <resource>SR_AutoInvoiceShipment::sr_auto_invoice_shipment</resource>
            <group id="settings" translate="label" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Extension Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

Download Full Module From Here

How to create shipment programmatically in magento2

If we need to programmatically create shipment for an order then you can create by following way.


// Load the order
$order = $this->_objectManager->create('Magento\Sales\Model\Order')
    ->loadByAttribute('increment_id', '000000001');
OR
$order = $this->_objectManager->create('Magento\Sales\Model\Order')
    ->load('1');

// Check if order can be shipped or has already shipped
if (! $order->canShip()) {
    throw new \Magento\Framework\Exception\LocalizedException(
                    __('You can\'t create an shipment.')
                );
}

// Initialize the order shipment object
$convertOrder = $this->_objectManager->create('Magento\Sales\Model\Convert\Order');
$shipment = $convertOrder->toShipment($order);

// Loop through order items
foreach ($order->getAllItems() AS $orderItem) {
    // Check if order item has qty to ship or is virtual
    if (! $orderItem->getQtyToShip() || $orderItem->getIsVirtual()) {
        continue;
    }

    $qtyShipped = $orderItem->getQtyToShip();

    // Create shipment item with qty
    $shipmentItem = $convertOrder->itemToShipmentItem($orderItem)->setQty($qtyShipped);

    // Add shipment item to shipment
    $shipment->addItem($shipmentItem);
}

// Register shipment
$shipment->register();

$shipment->getOrder()->setIsInProcess(true);

try {
    // Save created shipment and order
    $shipment->save();
    $shipment->getOrder()->save();

    // Send email
    $this->_objectManager->create('Magento\Shipping\Model\ShipmentNotifier')
        ->notify($shipment);

    $shipment->save();
} catch (\Exception $e) {
    throw new \Magento\Framework\Exception\LocalizedException(
                    __($e->getMessage())
                );
}

How to integrate recaptcha in magento 2 contact page

Today I discuss, How to integrate google reCaptcha into contact page. So you need to view captcha and validation captcha.

Step 1: Create Module.xml [SR/ReCaptcha/etc/module.xml]


<?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="SR_ReCaptcha" setup_version="2.1.0">
    </module>
</config>

Step 2: Create registration.php [SR/ReCaptcha/registration.php]


\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'SR_ReCaptcha',
    __DIR__
);

Step 3: Create system configuration [SR/ReCaptcha/etc/adminhtml/system.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="sr_reCaptcha">
            Re Captcha
        </tab>
        <section id="reCaptcha" type="text" showInDefault="1">
            <label>Re Captcha Configuration</label>
            <tab>sr_reCaptcha</tab>
            <resource>SR_ReCaptcha::reCaptcha</resource>
            <group id="settings" type="text"  sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                <label>Extension Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="site_key" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Site Key</label>
                </field>
                <field id="secret_key" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Secret Key</label>
                </field>
            </group>
        </section>
    </system>
</config>


Step 4: Assign default value for system configuration [SR/ReCaptcha/etc/config.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <reCaptcha>
            <settings>
                <enabled>0</enabled>
                <site_key></site_key>
                <secret_key></secret_key>
            </settings>
        </reCaptcha>
    </default>
</config>


Step 5: Create helper [SR/ReCaptcha/Helper/Data.php]


namespace SR\ReCaptcha\Helper;

use Magento\Store\Model\ScopeInterface;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    const MODULE_ENABLED = 'reCaptcha/settings/enabled';
    const SITE_KEY = 'reCaptcha/settings/site_key';
    const SECRET_KEY = 'reCaptcha/settings/secret_key';

    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * Data constructor.
     *
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
    ) {
        parent::__construct($context);
        $this->storeManager = $storeManager;
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * Is the module enabled in configuration.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return $this->getStoreConfig(self::MODULE_ENABLED);
    }

    /**
     * The recaptcha site key.
     *
     *
     * @return string
     */
    public function getSiteKey()
    {
        return $this->getStoreConfig(self::SITE_KEY);
    }

    /**
     * The recaptcha secret key.
     *
     *
     * @return string
     */
    public function getSecretKey()
    {
        return $this->getStoreConfig(self::SECRET_KEY);
    }

    public function getStoreConfig($path)
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE, $store);
    }

    public function getStoreId()
    {
        return $this->storeManager->getStore()->getStoreId();
    }
}

Step 6: Layout configuration [SR/ReCaptcha/view/frontend/layout/contact_index_index.xml]


<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="form.additional.info">
            <block class="SR\ReCaptcha\Block\ReCaptcha" name="recaptchaForm" template="SR_ReCaptcha::captcha.phtml" after="-" cacheable="false" />
        </referenceContainer>
    </body>
</page>


Step 7: Create phtml file [SR/ReCaptcha/view/frontend/templates/captcha.phtml]


<?php /** @var $block \SR\ReCaptcha\Block\ReCaptcha */ ?>
<?php if ($block->isEnabled()): ?>
    https://www.google.com/recaptcha/api.js
    
getSiteKey();?>==">
<?php endif; ?>

Step 7: Create a plugin [SR/ReCaptcha/etc/frontend/di.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Contact\Controller\Index\Post">
        <plugin name="reCaptcha_Post_Action" type="SR\ReCaptcha\Plugin\Contact\Controller\Index\Post" sortOrder="1"/>
    </type>
</config>


Step 8: Create a plugin class [SR/ReCaptcha/Plugin/Contact/Controller/Index/Post.php]


namespace SR\ReCaptcha\Plugin\Contact\Controller\Index;

class Post
{
    /**
     * @var \Magento\Framework\Controller\Result\RedirectFactory
     */
    protected $resultRedirectFactory;

    /**
     * @var \Magento\Framework\Message\ManagerInterface
     */
    protected $messageManager;

    /**
     * @var \SR\ReCaptcha\Helper\Data $dataHelper
     */
    protected $dataHelper;

    /**
     * @param \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory
     * @param \Magento\Framework\Message\ManagerInterface $messageManager
     * @param \SR\ReCaptcha\Helper\Data $dataHelper
     */
    public function __construct(
        \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory,
        \Magento\Framework\Message\ManagerInterface $messageManager,
        \SR\ReCaptcha\Helper\Data $dataHelper
    ) {
        $this->resultRedirectFactory = $resultRedirectFactory;
        $this->messageManager = $messageManager;
        $this->dataHelper = $dataHelper;
    }

    public function aroundExecute(
        \Magento\Contact\Controller\Index\Post $subject,
        \Closure $proceed
    ) {
        if($this->dataHelper->isEnabled()) {
            $recaptcha_response_field = $subject->getRequest()->getPost('g-recaptcha-response');
            if($recaptcha_response_field) {
                $secretKey = $this->dataHelper->getSecretKey();
                $response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".$secretKey."&response=".$recaptcha_response_field."&remoteip=".$_SERVER['REMOTE_ADDR']);
                $result = json_decode($response, true);
                if(isset($result['success']) && ($result['success'])) {
                    return $proceed();
                } else {
                    $resultRedirect = $this->resultRedirectFactory->create();
                    $this->messageManager->addError(
                        __('There was an error with the recaptcha code, please try again.')
                    );
                    $resultRedirect->setPath('contact/index');
                    return $resultRedirect;
                }
            } else {
                $this->messageManager->addError(
                    __('There was an error with the recaptcha code, please try again.')
                );
                $resultRedirect = $this->resultRedirectFactory->create();
                $resultRedirect->setPath('contact/index/index');
                return $resultRedirect;
            }
        }

        return $proceed();
    }
}

Download full module from here

After installation for configuration go to Store -> configuration
re-captcha config