I want to add Y free product to cart if user buy X product.

I have changed code of Mage_Sales_Model_Quote

public function addProductAdvanced(Mage_Catalog_Model_Product $product, $request = null, $processMode = null){
$specialPrice = 0;

it's work fine.

but when user add Y product from product page it's add with price 0. but i want to add only one qty. with 0 and other with regular price. Please help me.

1 Answer 1


first of all: prefer events to rewrites, prefer rewrites to monkey patches and never touch the core unless you are a Magento core developer :)

So said, let's decompose the problem into two sub problems:

  1. Let's identify the event we can observe to add the free Y product if user buy X product.
  2. Let's Make sure that an automatically added free Y product can coexist with a manually added (and non free) Y product; in other words: differentiate quote items referring to the same product.

Identify the event

The most appropriate event is sales_quote_collect_totals_before because it is called not only when an item is manually added by the user to the cart but also when the item is added in a programmatic manner, for example when the user logs in and the quote merge occurs.

We should pay attention to the fact that this event is called not only when adding a product to cart, so we should keep track of the fact that we already added the gift.

Differentiate quote items

How Magento determines whether two quote items are the same?

  • They have the same product_id
  • They have the same parent_id (if any)
  • They have the same custom options

As far as I understand from your description, the free Y and the non-free Y have the same product_id and are simple products (so they don't have a parent_id).

Thus we will differentiate them by adding dynamic custom options to the quote item representing the gift.

Implementation steps

Here are the steps needed to implement our solution.

Activate a custom module

Create the following {magento_install_dir}/app/etc/modules/Yourname_Yourmodule.xml file:

<?xml version="1.0"?>

Note: substitute Yourname with your vendor name and Yourmodule with your module name.

Note 2: if you intend to distribute the extension prefer the community code pool.

Define the module configuration

Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/etc/config.xml file:

<?xml version="1.0"?>
        <!-- Default configuration values -->

Note 1: I'm observing the event only in frontend area; if the store manager creates an order in the Admin Panel, our logic won't be applied.

Note 2: I'm using Magento 2 convention for observers: a class associated to each event whose name ends with Observer suffix and with a single responsibility performed by the execute() method.

Note 3: even if I'm not providing system.xml for module configuration I set module configuration default values through the <default> XML node.

Implement the helper

Our helper is a simple gateway towards system configuration.

Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/Helper/Data.php file:


class Yourname_Yourmodule_Helper_Data extends Mage_Core_Helper_Abstract
     * XML Paths for General configuration constants
    const XML_PATH_IS_ACTIVE = 'yourname_yourmodule/general/active';
    const XML_PATH_REWARDING_PRODUCT_ID = 'yourname_yourmodule/general/rewarding_product_id';
    const XML_PATH_FREE_PRODUCT_ID = 'yourname_yourmodule/general/free_product_id';

    public function isActive()
        return Mage::getStoreConfigFlag(self::XML_PATH_IS_ACTIVE);

    public function getRewardingProductId()
        return Mage::getStoreConfig(self::XML_PATH_REWARDING_PRODUCT_ID);

    public function getFreeProductId()
        return Mage::getStoreConfig(self::XML_PATH_FREE_PRODUCT_ID);

Implement the observer

Here are the logical steps of our observer:

  • Check whether the module is activated, otherwise do nothing
  • If X is not in cart remove Y (if present)
  • If both X and free Y are in cart, update Y's quantity according to X's
  • If the X product is in cart but the free Y is not, add free Y

Create the following {magento_install_dir}/app/code/local/Yourname/Yourmodule/Model/Observer/AddFreeProductObserver.php file:


class Yourname_Yourmodule_Model_Observer_AddFreeProductObserver
    public function execute(Varien_Event_Observer $observer)
        // Check whether the module is activated, otherwise do nothing

        /** @var Yourname_Yourmodule_Helper_Data $helper */
        $helper = Mage::helper('yourname_yourmodule');

        if (!$helper->isActive()) {

        // Check whether rewarding X product is in cart, otherwise do nothing
        $rewardingProductId = $helper->getRewardingProductId();
        $rewardingProductName = '';
        $rewardingProductItemQty = 0;

        $freeProductId = $helper->getFreeProductId();
        $freeProductItemId = null;

        /** @var Mage_Sales_Model_Quote $quote */
        $quote = $observer
            ->setIsSuperMode(true); // note: this ignores stock checks

        /** @var Mage_Sales_Model_Quote_Item $item */
        foreach ($quote->getAllVisibleItems() as $item) {
            if ($item->getProductId() == $rewardingProductId) {
                $rewardingProductName = $item->getName();
                $rewardingProductItemQty = $item->getQty();

            if ($item->getProductId() == $freeProductId) {
                /** @var Mage_Sales_Model_Quote_Item_Option $customOptionValue */
                if ($customOptionValue = $item->getOptionByCode('additional_options')) {
                    $value = $customOptionValue->getValue() ? unserialize($customOptionValue->getValue()) : array();
                    if (isset($value['free_message'])) {
                        $freeProductItemId = $item->getId();

        if (!$rewardingProductItemQty) {
            // If the free Y product is in cart, remove it
            if ($freeProductItemId) {

        // If free Y is already in cart update Y's quantity according to X's
        if ($freeProductItemId) {

        // The X product is in cart but the free Y is not: add free Y
        $freeProduct = Mage::getModel('catalog/product')->load($freeProductId);
        if ($freeProduct->getId()) {
            $customOptionValue = $freeProduct->getCustomOption('additional_option');
            if ($customOptionValue) {
                $customOptionValue = unserialize($customOptionValue);
            } else {
                $customOptionValue = array();

            if (isset($customOptionValue['free_message'])) {

            $customOptionValue = array_merge(
                    'free_message' => array(
                        'label' => $helper->__('Free'),
                        'value' => $helper->__('One free each %s', $rewardingProductName),

            $freeProduct->addCustomOption('additional_options', serialize($customOptionValue));

                ->addProductAdvanced($freeProduct, 1)


I've not tested this solution too much, consider it a guiding example and do your tests to be sure it works the way you desire.

Hope it helps.

  • It goes to infinite loop once addtocart.Reached maximum nesting level .also can't we use same added product ID to add it again with 0 price and different custom option if added product fulfill some condition?
    – Zahirabbas
    Jul 10, 2018 at 11:27

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.