Magento 2 Cart – How to Add Custom Discount in Cart Programmatically

cartdiscountmagento2

I want to apply custom discount amount to cart programmatically. I have tried doing it in following 2 ways, none of which work:

METHOD 1:

I tried following the default way in which Magento2 applies sales-rule discounts. I did it by creating following files in my module:

etc\sales.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd">
    <section name="quote">
        <group name="totals">
            <item name="discount" instance="Vendorname\Modulenmae\Model\CustomFolderName\Quote\Discount" sort_order="400"/>
        </group>
    </section>
</config>

frontend\layout\checkout_cart_index.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
     <body>
        <referenceBlock name="checkout.cart.totals">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="block-totals" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="before_grandtotal" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="discount" xsi:type="array">
                                            <item name="config" xsi:type="array">
                                                <item name="title" xsi:type="string" translate="true">My Custom Discount</item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Model File

class Discount extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
{
    public function collect(
            \Magento\Quote\Model\Quote $quote,
            \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
            \Magento\Quote\Model\Quote\Address\Total $total
        ) {

            parent::collect($quote, $shippingAssignment, $total);

            $label          = 'My Custom Discount';
            $discountAmount = -10;              
            $total->setDiscountDescription($label);
            $total->setDiscountAmount($discountAmount);
            $total->setBaseDiscountAmount($discountAmount);
            $total->setSubtotalWithDiscount($total->getSubtotal() + $discountAmount);
            $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $discountAmount);
            return $this;
        }
        //fetch function is also defined in this class. Since it is only used for display purposes I have not added its code in this question.
}

However, with this method, my custom discount gets applied but Magento's default sales rules like coupon code, etc. stop working. They throw errors like Invalid Coupon Code even when the coupon code is completely valid. If I revert my changes, the default coupon codes start working again.

METHOD 2:

Another method i tried is applying discount by observing the sales_quote_collect_totals_after event. It is working fine, but i think method 1 might be the best way to achieve it.

Any ideas as to how can I do it correctly ?

Best Answer

Thanks @R.S. . I found the solution by referring repo created by @R.S. here: https://github.com/magepal/stackexchange/tree/develop/104112 after some minor modifications.

sales.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd">
    <section name="quote">
        <group name="totals">
            <item name="testdiscount" instance="MagePal\TestDiscount\Model\Quote\Discount" sort_order="500"/>
        </group>
    </section>
</config>

Model file

public function collect(
        \Magento\Quote\Model\Quote $quote,
        \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
        \Magento\Quote\Model\Quote\Address\Total $total
    ) {
        //Fix for discount applied twice
        $items = $shippingAssignment->getItems();
        if (!count($items)) {
            return $this;
        }

        parent::collect($quote, $shippingAssignment, $total);
        //$address             = $shippingAssignment->getShipping()->getAddress();
        $label               = 'My Custom Discount';
        $discountAmount      = -10;   
        $appliedCartDiscount = 0;
        if($total->getDiscountDescription()) {
            // If a discount exists in cart and another discount is applied, the add both discounts.
            $appliedCartDiscount = $total->getDiscountAmount();
            $discountAmount      = $total->getDiscountAmount()+$discountAmount;
        $label               = $total->getDiscountDescription().', '.$label;
        }    

        $total->setDiscountDescription($label);
    $total->setDiscountAmount($discountAmount);
    $total->setBaseDiscountAmount($discountAmount);
        $total->setSubtotalWithDiscount($total->getSubtotal() + $discountAmount);
        $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $discountAmount);

       if(isset($appliedCartDiscount)) {
        $total->addTotalAmount($this->getCode(), $discountAmount - $appliedCartDiscount);
        $total->addBaseTotalAmount($this->getCode(), $discountAmount - $appliedCartDiscount);
    } else {
        $total->addTotalAmount($this->getCode(), $discountAmount);
        $total->addBaseTotalAmount($this->getCode(), $discountAmount);
    }

        return $this;
    }

checkout_cart_index.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.cart.totals">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="block-totals" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="before_grandtotal" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="testdiscount" xsi:type="array">
                                            <item name="config" xsi:type="array">
                                                <item name="title" xsi:type="string" translate="true">My Discount</item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

checkout_index_index.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="sidebar" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="summary" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="totals" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="testdiscount" xsi:type="array">
                                                            <item name="config" xsi:type="array">
                                                                <item name="title" xsi:type="string" translate="true">My Discount</item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Fetch is not showing the custom discount description. I have opened its ticket here: https://github.com/magento/magento2/issues/3594