Magento 2 – Validate Items Before Placing an Order

checkoutmagento2redirectvalidation

I have a requirement to validate each quote item before an order is placed.

  • In our store, each product has a multi-select attribute called restricted_states.
  • During checkout, once the customer enters their shipping address state, the store should loop through each of the quote items and check to see if their product is restricted from shipping to that state.
  • IF the item's product is restricted, the customer should be redirected to the shopping cart page, and a message should be displayed informing them an item is restricted from shipping to that location and asking them to remove it
  • The customer should not be able to continue until the restricted item has been removed.

Okay, so I have tried several different approaches to get this accomplished and have had only partial success. So far I have created an observer that observes the sales_order_place_before event. The observer is able to identify the products that are restricted, however, it does not redirect the user to the cart page.

Does anyone know how I can get this to work? Is this the correct approach or is there another way I should go about this?

The code for my observer method is:

<?php

namespace NatureHills\ShippingRestriction\Observer;

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

class ShippingRestriction implements ObserverInterface
{
    /**
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;

    /**
     * @var \Magento\Catalog\Model\ProductRepository
     */
    protected $productRepo;

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

    /**
     * @var \Magento\Framework\App\ResponseFactory
     */
    protected $_responseFactory;

    /**
     * @var \Magento\Framework\UrlInterface
     */
    protected $_url;

    /**
     * @var \Magento\Checkout\Model\Session
     */
    protected $_checkoutSession;

    /**
     * @var \Magento\Framework\App\Response\RedirectInterface
     */
    protected $_redirect;

    /**
     * @var \Magento\Framework\Registry
     */
    protected $_registry;

    /**
     * ShippingRestriction constructor.
     * @param \Psr\Log\LoggerInterface $loggerInterface
     * @param \Magento\Catalog\Model\ProductRepository $productRepo
     */
    public function __construct(\Psr\Log\LoggerInterface $loggerInterface,
                                \Magento\Catalog\Model\ProductRepository $productRepo,
                                \Magento\Framework\Message\ManagerInterface $messageManager,
                                \Magento\Framework\App\ResponseFactory $responseFactory,
                                \Magento\Framework\UrlInterface $url,
                                \Magento\Checkout\Model\Session $checkoutSession,
                                \Magento\Framework\App\Response\RedirectInterface $redirect,
                                \Magento\Framework\Registry $registry)
    {
        $this->logger = $loggerInterface;
        $this->productRepo = $productRepo;
        $this->messageManager = $messageManager;
        $this->_responseFactory = $responseFactory;
        $this->_url = $url;
        $this->_checkoutSession = $checkoutSession;
        $this->_redirect = $redirect;
        $this->_registry = $registry;
    }

    /**
     * @param Observer $observer
     * @return $this
     */
    public function execute(Observer $observer)
    {
        $quote = $this->_checkoutSession->getQuote();
        if(!$quote){return $this;}

        $address = $quote->getShippingAddress();

        if($address)
        {
            $state = $address->getRegion();
            if(!$state){return $this;}

            $items = $quote->getAllItems();
            $restrictedItems = array();

            foreach($items as $item)
            {
                $product = $this->productRepo->getById($item->getProductId());

                if(!$product){continue;}

                $restrictedStates = explode(', ', $product->getResource()->getAttribute('restricted_states')->getFrontend()->getValue($product));

                if(is_array($restrictedStates))
                {
                    if(in_array($state, $restrictedStates))
                    {
                        array_push($restrictedItems, $product->getName());
                    }
                }
            }

            /* Check the restricted items array if it has values throw the error message and exit */
            if(count($restrictedItems) > 0)
            {
                $message = "We are sorry, due to state agricultural laws, we are unable to ship these items to ".$state.
                            " Please remove the product(s) below before proceeding, or call (888) 864-7663 for further assistance. ".
                            "Items: ".implode(", ", $restrictedItems);

                $this->messageManager->addErrorMessage($message);

                $cartUrl = $this->_url->getUrl('checkout/cart/index');

                $response = $observer->getControllerAction()->getResponse();
                $response->setRedirect($cartUrl);
                $response->SendResponse();
                exit;
            }
        }
        return $this;
    }
}

UPDATE

Using Sohel's suggestion below, I created the following plugin to achieve the desired results. I hope this helps if anyone else faces the same requirements.

<?php

namespace NatureHills\ProductShippingRestrictions\Plugin\Checkout\Model;

use Magento\Framework\Exception\InputException;

class ShippingInformationManagement
{
    /**
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;

    /**
     * @var \Magento\Catalog\Model\ProductRepository
     */
    protected $productRepo;


    /**
     * @var \Magento\Checkout\Model\Session
     */
    protected $_checkoutSession;

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

    /**
     * ShippingInformationManagement constructor.
     * @param \Psr\Log\LoggerInterface $loggerInterface
     * @param \Magento\Catalog\Model\ProductRepository $productRepo
     * @param \Magento\Checkout\Model\Session $checkoutSession
     */
    public function __construct(\Psr\Log\LoggerInterface $loggerInterface,
                                \Magento\Catalog\Model\ProductRepository $productRepo,
                                \Magento\Checkout\Model\Session $checkoutSession,
                                \Magento\Framework\Message\ManagerInterface $messageManager)
    {
        $this->logger = $loggerInterface;
        $this->productRepo = $productRepo;
        $this->_checkoutSession = $checkoutSession;
        $this->messageManager = $messageManager;
    }

    /**
     * @param \Magento\Checkout\Model\ShippingInformationManagement $subject
     * @param \Closure $proceed
     * @param $cartId
     * @param \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
     * @return mixed
     * @throws InputException
     */
    public function aroundSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagement $subject,
        \Closure $proceed,
        $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ) {

        $sessionQuote = $this->_checkoutSession->getQuote();
        if(!$sessionQuote){return $this;}

        $shippingAddress = $addressInformation->getShippingAddress();
        $state = $shippingAddress->getRegion();

        if($state){
            $items = $sessionQuote->getAllItems();
            $restrictedItems = array();

            foreach($items as $item)
            {
                $product = $this->productRepo->getById($item->getProductId());

                if(!$product){continue;}

                $restrictedStates = explode(', ', $product->getResource()->getAttribute('restricted_states')->getFrontend()->getValue($product));

                if(is_array($restrictedStates))
                {
                    if(in_array($state, $restrictedStates))
                    {
                        array_push($restrictedItems, $product->getName());
                    }
                }
            }

            /* Check the restricted items array if it has values throw the error message and exit */
            if(count($restrictedItems) > 0)
            {
                $message = "We are sorry, due to state agricultural laws, we are unable to ship these items to ".$state.
                    " Please remove the product(s) below before proceeding, or call (888) 864-7663 for further assistance. ".
                    "Items: ".implode(", ", $restrictedItems);

                throw new InputException(
                    __($message)
                );
            }
        }
        return $proceed($cartId, $addressInformation);
    }
}

Best Answer

According to your requirement, better to check in shipping address phage. So best approach to pluginize Magento\Checkout\Model\ShippingInformationManagement saveAddressInformation method.

Vendor/Module/etc/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\Checkout\Model\ShippingInformationManagement">
        <plugin name="checkout_shipping_address" type="Vendor\Module\Plugin\Checkout\Model\ShippingInformationManagement" sortOrder="1"/>
    </type>
</config>

Vendor/Module/Plugin/Checkout/Model/ShippingInformationManagement.php


namespace Vendor\Module\Plugin\Checkout\Model;

use Magento\Framework\Exception\InputException;

class ShippingInformationManagement
{

    /**
     * @param \Magento\Checkout\Model\ShippingInformationManagement $subject
     * @param \Closure $proceed
     * @param $cartId
     * @param \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
     * @return mixed
     * @throws InputException
     */
    public function aroundSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagement $subject,
        \Closure $proceed,
        $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ) {
        $shippingAddress = $addressInformation->getShippingAddress();
        $street = $shippingAddress->getStreet();
        if(!$validate) {
            throw new InputException(__('Invalid shipping information. Please check input data.'));
        }

        return $proceed($cartId, $addressInformation);
    }


}
Related Topic