Magento 2.2 Payment Method – Onepage Checkout Plugin

magento2.2onepage-checkoutphp-7plugin

How to disable the Cash on Delivery payment method in backend and frontend Programmatically?

Here the code I'm using:

etc/di.xml

<type name="Magento\OfflinePayments\Model\Cashondelivery">
    <plugin name="cashondeliveryplugin" type="Xxx\Yyy\Plugin\Model\Cashondelivery" sortOrder="10" disabled="false" />
</type>

Plugin:

public function aroundIsAvailable(
    \Magento\OfflinePayments\Model\Cashondelivery $subject,
    callable $proceed,
    $quote = null
) {
    $result          = $proceed($quote);
    $codAvailability = $this->_pincodeModel->isCODAvailable($quote->getShippingAddress()->getPostcode()) ? "yes" : "no";
    if ($codAvailability == 'no') {
        return false;
    }

    return $result;
}

The actual problem is: the quote is missed when plugin called from the frontend (checkout page).

Best Answer

You can use a simple plugin on the method isAvailable of the model Magento\OfflinePayments\Model\Cashondelivery:

<?php
/**
 * Copyright © 2018 MageWorx. All rights reserved.
 * See LICENSE.txt for license details.
 */

namespace MageWorx\DisableCOD\Plugin;

use Magento\Customer\Model\Session as CustomerSession;
use Magento\Backend\Model\Auth\Session as BackendSession;
use Magento\OfflinePayments\Model\Cashondelivery;

class DisableCashOnDelivery
{
    /**
     * @var CustomerSession
     */
    protected $customerSession;

    /**
     * @var BackendSession
     */
    protected $backendSession;

    /**
     * @param CustomerSession $customerSession
     * @param BackendSession $backendSession
     */
    public function __construct(
        CustomerSession $customerSession,
        BackendSession $backendSession
    ) {
        $this->customerSession = $customerSession;
        $this->backendSession = $backendSession;
    }

    /**
     *
     * @param Cashondelivery $subject
     * @param                $result
     * @return bool
     */
    public function afterIsAvailable(Cashondelivery $subject, $result)
    {
        $condition = true; // Replace with your condition

        if ($condition) {
            return false;
        }

        return $result;
    }
}

Just change the conditions to desired one.

The result on the frontend (checkout page):

The result on the frontend

The result on the backend (create new order page):

The result on the backend

Full plugin could be found on github

Updated code:

<?php
/**
 * Copyright © 2018 MageWorx. All rights reserved.
 * See LICENSE.txt for license details.
 */

namespace MageWorx\DisableCOD\Plugin;

use Magento\Customer\Model\Session as CustomerSession;
use Magento\Backend\Model\Auth\Session as BackendSession;
use Magento\Framework\App\State;
use Magento\OfflinePayments\Model\Cashondelivery;
use Magento\Checkout\Model\Session as CheckoutFrontendSession;
use Magento\Backend\Model\Session\Quote as CheckoutBackendSession;

class DisableCashOnDelivery
{
    /**
     * @var CustomerSession
     */
    protected $customerSession;

    /**
     * @var BackendSession
     */
    protected $backendSession;

    /**
     * @var CheckoutFrontendSession|CheckoutBackendSession
     */
    protected $session;

    /**
     * @param CustomerSession         $customerSession
     * @param BackendSession          $backendSession
     * @param CheckoutFrontendSession $checkoutFrontendSession
     * @param CheckoutBackendSession  $checkoutBackendSession
     * @param State                   $state
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(
        CustomerSession $customerSession,
        BackendSession $backendSession,
        CheckoutFrontendSession $checkoutFrontendSession,
        CheckoutBackendSession $checkoutBackendSession,
        State $state
    ) {
        $this->customerSession = $customerSession;
        $this->backendSession  = $backendSession;
        if ($state->getAreaCode() == \Magento\Framework\App\Area::AREA_ADMINHTML) {
            $this->session = $checkoutBackendSession;
        } else {
            $this->session = $checkoutFrontendSession;
        }
    }

    public function aroundIsAvailable(
        \Magento\OfflinePayments\Model\Cashondelivery $subject,
        callable $proceed,
        \Magento\Quote\Api\Data\CartInterface $quote = null
    ) {
        $result = $proceed($quote);
        if ($quote === null) {
            $quote = $this->session->getQuote();
        }
        $codAvailability = $this->_pincodeModel->isCODAvailable($quote->getShippingAddress()->getPostcode()) ? 1 : 0;
        if (!$codAvailability) {
            return false;
        }

        return $result;
    }
}

Do not forget add your classes to the di (_pincodeModel).

Update 2:

On the frontend the $quote argument is null because it was called from the method \Magento\OfflinePayments\Model\InstructionsConfigProvider::getConfig() where the method isAvailable() of the each payment method was called without providing arguments, what means the $quote will be null:

public function getConfig()
{
    $config = [];
    foreach ($this->methodCodes as $code) {
        if ($this->methods[$code]->isAvailable()) {
            $config['payment']['instructions'][$code] = $this->getInstructions($code);
        }
    }
    return $config;
}

But in the checkout session, it exists, and we can access it from the checkout session object.

Update 3:

For the backend the method isAvailable() of the all payment methods was called from the Magento\Payment\Block\Form\Container::getMethods() method which provides a quote in the arguments list:

/**
 * Retrieve available payment methods
 *
 * @return array
 */
public function getMethods()
{
    $methods = $this->getData('methods');
    if ($methods === null) {
        $quote = $this->getQuote();
        $store = $quote ? $quote->getStoreId() : null;
        $methods = [];
        foreach ($this->getPaymentMethodList()->getActiveList($store) as $method) {
            $methodInstance = $this->getPaymentMethodInstanceFactory()->create($method);
            if ($methodInstance->isAvailable($quote) && $this->_canUseMethod($methodInstance)) {
                $this->_assignMethod($methodInstance);
                $methods[] = $methodInstance;
            }
        }
        $this->setData('methods', $methods);
    }
    return $methods;
}

Here is why you can access the quote object from the backend directly, and can not do that on the frontend.

Related Topic