Magento – How to add same product with different price in cart

cartfree-giftprice

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;
$item->setCustomPrice($specialPrice);
$item->setOriginalCustomPrice($specialPrice);
$item->getProduct()->setIsSuperMode(true);
...............
..............
}

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.

Best Answer

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"?>
<config>
    <modules>
        <Yourname_Yourmodule>
            <active>true</active>
            <codePool>local</codePool>
        </Yourname_Yourmodule>
    </modules>
</config>

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"?>
<config>
    <modules>
        <Yourname_Yourmodule>
            <version>0.1.0</version>
        </Yourname_Yourmodule>
    </modules>
    <global>
        <models>
            <yourname_yourmodule>
                <class>Yourname_Yourmodule_Model</class>
                <resourceModel>yourname_yourmodule_resource</resourceModel>
            </yourname_yourmodule>
            <yourname_yourmodule_resource>
                <class>Yourname_Yourmodule_Model_Resource</class>
            </yourname_yourmodule_resource>
        </models>
        <helpers>
            <yourname_yourmodule>
                <class>Yourname_Yourmodule_Helper</class>
            </yourname_yourmodule>
        </helpers>
    </global>
    <frontend>
        <events>
            <sales_quote_collect_totals_before>
                <observers>
                    <yourname_yourmodule>
                        <type>singleton</type>
                        <class>yourname_yourmodule/observer_addFreeProductObserver</class>
                        <method>execute</method>
                    </yourname_yourmodule>
                </observers>
            </sales_quote_collect_totals_before>
        </events>
    </frontend>
    <default>
        <!-- Default configuration values -->
        <yourname_yourmodule>
            <general>
                <active>1</active>
                <rewarding_product_id>418</rewarding_product_id>
                <rewarded_product_id>906</rewarded_product_id>
            </general>
        </yourname_yourmodule>
    </default>
</config>

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:

<?php

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:

<?php

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()) {
            return;
        }

        // 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
            ->getQuote()
            ->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();
                continue;
            }

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

        if (!$rewardingProductItemQty) {
            // If the free Y product is in cart, remove it
            if ($freeProductItemId) {
                $quote->removeItem($freeProductItemId);
            }
            return;
        }

        // If free Y is already in cart update Y's quantity according to X's
        if ($freeProductItemId) {
            $quote
                ->getItemById($freeProductItemId)
                ->setQty($rewardingProductItemQty);
            return;
        }

        // 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'])) {
                return;
            }

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

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

            $quote
                ->addProductAdvanced($freeProduct, 1)
                ->setCustomPrice(0)
                ->setOriginalCustomPrice(0);
        }
    }
}

Conclusion

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.

Related Topic