1

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);
    }
}

1 Answer 1

2

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

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);
    }


}
3
  • Thank you Sohel! You have saved me a lot more heartache! I will update my question with what I came up with based on your answer. Commented Jun 16, 2017 at 20:53
  • I have the same functionality where I need to update quote items before vendor/magento/module-checkout/Model/ShippingInformationManagement.php line: 124. Can you please tell me how can I call my class method there using around method? Commented Jan 19, 2018 at 15:40
  • I have created di.xml and ShippingInformationManagement.php but no luck. Did I miss something? I have to create an observer too for this action Commented Jan 24, 2019 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.