Magento 2 - discount depend on Payment Method does not work
This rule doesn't work because Magento 2 doesn't save payment method to quote when you select one. And it also doesn't reload totals when selecting a payment method. And unfortunately, you have to write a custom module to solve the issue.
The new module needs only 4 files to be created:
app/code/Namespace/ModuleName/etc/frontend/routes.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router id="standard"> <route id="namespace_modulename" frontName="namespace_modulename"> <module name="Namespace_ModuleName"/> </route> </router> </config>
This will define a new controller for our module.
app/code/Namespace/ModuleName/Controller/Checkout/ApplyPaymentMethod.php
<?php namespace Namespace\ModuleName\Controller\Checkout; class ApplyPaymentMethod extends \Magento\Framework\App\Action\Action { /** * @var \Magento\Framework\Controller\Result\ForwardFactory */ protected $resultForwardFactory; /** * @var \Magento\Framework\View\LayoutFactory */ protected $layoutFactory; /** * @var \Magento\Checkout\Model\Cart */ protected $cart; /** * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory */ public function __construct( \Magento\Framework\App\Action\Context $context, \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, \Magento\Checkout\Model\Cart $cart ) { $this->resultForwardFactory = $resultForwardFactory; $this->layoutFactory = $layoutFactory; $this->cart = $cart; parent::__construct($context); } /** * @return \Magento\Framework\Controller\ResultInterface */ public function execute() { $pMethod = $this->getRequest()->getParam('payment_method'); $quote = $this->cart->getQuote(); $quote->getPayment()->setMethod($pMethod['method']); $quote->setTotalsCollectedFlag(false); $quote->collectTotals(); $quote->save(); } }
This file creates controller action to save the selected payment method to quote
app/code/Namespace/ModuleName/view/frontend/requirejs-config.js
var config = { map: { '*': { 'Magento_Checkout/js/action/select-payment-method': 'Namespace_ModuleName/js/action/select-payment-method' } } };
This file allows to override
Magento_Checkout/js/action/select-payment-method
fileapp/code/Namespace/ModuleName/view/frontend/web/js/action/select-payment-method.js
define( [ 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/full-screen-loader', 'jquery', 'Magento_Checkout/js/action/get-totals', ], function (quote, fullScreenLoader, jQuery, getTotalsAction) { 'use strict'; return function (paymentMethod) { quote.paymentMethod(paymentMethod); fullScreenLoader.startLoader(); jQuery.ajax('/namespace_modulename/checkout/applyPaymentMethod', { data: {payment_method: paymentMethod}, complete: function () { getTotalsAction([]); fullScreenLoader.stopLoader(); } }); } } );
Sends ajax request to save payment method and reload cart totals.
P.S. Parts of the code were taken from Payment Fee extension for Magento 2.
On Magento 2.2 i couldn't get MagestyApps answer to work. I needed to add some additional files. Because:
- Cart Price Rule for Payment Method was removed from admin (as pointed out by DaFunkyAlex);
- In Magento 2.2 the discount was not being applied on the quote, because the method
\Magento\AdvancedSalesRule\Model\Rule\Condition\FilterTextGenerator\Address\PaymentMethod::generateFilterText
(actually it falls back to\Magento\AdvancedSalesRule\Model\Rule\Condition\FilterTextGenerator\Address::generateFilterText
), was expecting the datapayment_method
to be set on the quote addresses; - Even changing the controller from MagestyApps answer to set
payment_method
data on the quote addresses, didn't work when the quote became an order, because it don't persistpayment_method
;
The following module worked out for me (thanks to MagestyApps answer, it was based on top of that):
registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Vendor_SalesRulesPaymentMethod',
__DIR__
);
etc/module.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Vendor_SalesRulesPaymentMethod" setup_version="1.0.0">
<sequence>
<module name="Magento_AdvancedSalesRule"/>
<module name="Magento_Checkout"/>
<module name="Magento_SalesRules"/>
<module name="Magento_Quote"/>
</sequence>
</module>
</config>
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">
<preference for="Magento\AdvancedSalesRule\Model\Rule\Condition\FilterTextGenerator\Address\PaymentMethod"
type="Vendor\SalesRulesPaymentMethod\Model\Rule\Condition\FilterTextGenerator\Address\PaymentMethod"/>
<type name="Magento\SalesRule\Model\Rule\Condition\Address">
<plugin name="addPaymentMethodOptionBack" type="Vendor\SalesRulesPaymentMethod\Plugin\AddPaymentMethodOptionBack" />
</type>
</config>
etc/frontend/routes.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="salesrulespaymentmethod" frontName="salesrulespaymentmethod">
<module name="Vendor_SalesRulesPaymentMethod"/>
</route>
</router>
</config>
Controller/Checkout/ApplyPaymentMethod.php
<?php
namespace Vendor\SalesRulesPaymentMethod\Controller\Checkout;
use Magento\Checkout\Model\Cart;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\ForwardFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\View\LayoutFactory;
use Magento\Quote\Model\Quote;
class ApplyPaymentMethod extends Action
{
/**
* @var ForwardFactory
*/
protected $resultForwardFactory;
/**
* @var LayoutFactory
*/
protected $layoutFactory;
/**
* @var Cart
*/
protected $cart;
/**
* @param Context $context
* @param LayoutFactory $layoutFactory
* @param ForwardFactory $resultForwardFactory
*/
public function __construct(
Context $context,
ForwardFactory $resultForwardFactory,
LayoutFactory $layoutFactory,
Cart $cart
) {
$this->resultForwardFactory = $resultForwardFactory;
$this->layoutFactory = $layoutFactory;
$this->cart = $cart;
parent::__construct($context);
}
/**
* @return ResponseInterface|ResultInterface|void
* @throws \Exception
*/
public function execute()
{
$pMethod = $this->getRequest()->getParam('payment_method');
/** @var Quote $quote */
$quote = $this->cart->getQuote();
$quote->getPayment()->setMethod($pMethod['method']);
$quote->setTotalsCollectedFlag(false);
$quote->collectTotals();
$quote->save();
}
}
Model/Rule/Condition/FilterTextGenerator/Address/PaymentMethod.php
<?php
namespace Vendor\SalesRulesPaymentMethod\Model\Rule\Condition\FilterTextGenerator\Address;
use Magento\AdvancedSalesRule\Model\Rule\Condition\FilterTextGenerator\Address\PaymentMethod as BasePaymentMethod;
class PaymentMethod extends BasePaymentMethod
{
/**
* @param \Magento\Framework\DataObject $quoteAddress
* @return string[]
*/
public function generateFilterText(\Magento\Framework\DataObject $quoteAddress)
{
$filterText = [];
if ($quoteAddress instanceof \Magento\Quote\Model\Quote\Address) {
$value = $quoteAddress->getQuote()->getPayment()->getMethod();
if (is_scalar($value)) {
$filterText[] = $this->getFilterTextPrefix() . $this->attribute . ':' . $value;
}
}
return $filterText;
}
}
Plugin/AddPaymentMethodOptionBack.php
<?php
namespace Vendor\SalesRulesPaymentMethod\Plugin;
use Magento\SalesRule\Model\Rule\Condition\Address;
class AddPaymentMethodOptionBack
{
/**
* @param Address $subject
* @param $result
* @return Address
*/
public function afterLoadAttributeOptions(Address $subject, $result)
{
$attributeOption = $subject->getAttributeOption();
$attributeOption['payment_method'] = __('Payment Method');
$subject->setAttributeOption($attributeOption);
return $subject;
}
}
view/frontend/requirejs-config.js
var config = {
map: {
'*': {
'Magento_Checkout/js/action/select-payment-method':
'Vendor_SalesRulesPaymentMethod/js/action/select-payment-method'
}
}
};
view/frontend/web/js/action/select-payment-method.js
define(
[
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/full-screen-loader',
'jquery',
'Magento_Checkout/js/action/get-totals',
],
function (quote, fullScreenLoader, jQuery, getTotalsAction) {
'use strict';
return function (paymentMethod) {
quote.paymentMethod(paymentMethod);
fullScreenLoader.startLoader();
jQuery.ajax('/salesrulespaymentmethod/checkout/applyPaymentMethod', {
data: {payment_method: paymentMethod},
complete: function () {
getTotalsAction([]);
fullScreenLoader.stopLoader();
}
});
}
}
);
We just checked the same rule and found that it doesn't work. Using the same conditions, no information about selected chosen method is sent and it is not recorded anywhere until the order is placed and the rule may not work.
Address has no payment method until validation and it gets the payment method from payment quote that doesn't exist because no information has been sent ($model->getQuote()->getPayment()->getMethod()
returns null
).
We suppose, that this is Magento bug. When you choose a payment method the information should be sent in advance.