1

Been searching high and low for a question similar to this both here and in Google and I'm surprised that I haven't been able to find anything similar.

I'm familiar with Customer Groups and Tiered Pricing but it doesn't fit the objective that my client has set.

What we want is for a user to come to our Magento store and see the regular homepage with regular prices. At that point we want a prominent text field for the user to add a Coupon Code upon which the site will refresh and display the new discounted prices with the regular price struck out (or "slashed" by some other visual method.

Customer Groups / Tiered Pricing aren't the solution because they require that the customer log in. The NOT LOGGED IN group wouldn't help either since all users would see the discount.

This can't happen in the Shopping Cart either because by then it's too late, this needs to happen on the Catalog level.

We are currently using OSCommerce and transitioning over to Magento shortly. Right now what we're doing to emulate this behavior is to have a text field on our regular website on the Store Access page where a user can click on a region or enter a coupon code. IF they enter a code they are redirected to a custom store that has special pricing.

I know that it's easy to recreate our current method in Magento by creating a store view and then using the same functionality but it seems a shame to do this when the idea of moving to a new platform that so much more powerful.

I haven't seen any extensions that do this. Does anyone have any insight as to whether something like this can be accomplish and if so, how?

nero
  • 521
  • 1
  • 7
  • 19

2 Answers2

2

I am curious Jonathon how you did it, I didn't take your approach, mine is a little more complex. Mine does allow someone to post the coupon code in the url as well though and I set a cookie and all that for it. I basically setup my own form in the header that a user can put in a coupon code and have that applied as well as putting the coupon in the url for email campaigns.

It would take me a while to go back over it in detail, so I will post some code snippets that maybe can help you get going, a long with trying the way Jonathan says as well.

Override the cart controller and add your own action.

 public function couponExternalPostAction()
{
        $quote =  $this->_getQuote();
        $couponCode = (string) $this->getRequest()->getParam('coupon_code');
        $validateCoupon = Mage::getModel('package_module/coupon');
        $json = $validateCoupon->addCouponCode($couponCode, $quote, $this->getRequest());

        echo $json;
        return;
}

I also had to override the couponPostAction() to get things to work the normal way correctly.

I have an addCoupon method in my own model

 public function addCouponCode($code, $quote, $request){
    $couponCode = (string) $code;
    $removed = false;

    if ($request->getParam('remove') == 1) {
        $couponCode = '';
        $removed = true;
    }

    $oldCouponCode = $quote->getCouponCode();

    /* No point in applying the rule again if it is the same coupon code that is in the quote */
    if ($couponCode === $oldCouponCode) {
        $json = $this->_getResponseJson($removed, $couponCode, $quote, false, true);
        return $json;
    }
    // Set the code get the rule base on validation even if it doesn't validate (false), which will also add it to the session,  then get our response
    $quote->setCouponCode(strlen($couponCode) ? $couponCode : '');
    $rule = $this->_validateCoupon($quote,$couponCode);
    // add coupon code to cookie, so we can delete from quote if the user closes their browser and comes back
    if($rule && !$removed){
        Mage::getModel('core/cookie')->set('coupon_code', $couponCode, 0, '/', null, null, null, false);
    }else{
       Mage::getModel('core/cookie')->delete('coupon_code');
    }
    $json = $this->_getResponseJson($removed, $couponCode, $quote, $rule);

    //See if the quote id is set  before saving
    $quoteId = $quote->getQuoteId();

    //Save the quote since everything has been set if not the data wont be set on page refresh
    $quote->save();

    //Set the quote id if it wasn't set before saving the quote. This makes sure we work off the same quote and a new one isn't created.
    if(empty($quoteId)){
        $this->_setQuoteId($quote);
    }

    return $json;
}

Validating the coupon

 protected function _validateCoupon($quote,$couponCode){
    $store = Mage::app()->getStore($quote->getStoreId());
    $validator = Mage::getModel('package_module/validator');
    $validator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());

    return $validator->isValidExternalCode($couponCode, $quote->getShippingAddress(),false);
}

I extended Mage_SalesRule_Model_Validator with my own validator function

 public function isValidExternalCode($couponCode, $address, $setCoupon = true){
    foreach ($this->_getRules() as $rule) {
        if ($rule->getCode() && (in_array(strtolower($couponCode),explode(',',strtolower($rule->getCode()))))) {
            if($setCoupon){
                $address->setCouponCode($couponCode);
            }
            return $rule;
        }
    }
    return false;
}

Here I generate the json response

rotected function _getResponseJson($removed, $couponCode, $quote, $rule = false, $isDup = false){
    $json = '{"Response":{';
    if($removed){
        $json .= '"success":"Promotional code was cancelled successfully."';
        Mage::getSingleton('checkout/session')->setData('coupon_rule',null);
    }
    if(!$removed && $isDup){
        $json .= '"error":"' . $couponCode . ' is already applied"';
    }else if(!$removed && $rule){
        $json .= '"success":"Promotional code ' . $couponCode . ' has been applied",';
        $json .= '"couponMessage":"<span>' . $rule->getName() . '</span>"';
        Mage::getSingleton('checkout/session')->setData('coupon_rule','<span>' . $rule->getName() .'</span>');
    }else if(!$removed){
        $json .= '"error":"' . $couponCode . ' is not valid"';
        $quote->setCouponCode('');
    }
    $json .= '}}';
    return $json;
}

I also had to override the collect method in Mage_SalesRule_Model_Quote_Discount

public function collect(Mage_Sales_Model_Quote_Address $address)
{
    Mage_Sales_Model_Quote_Address_Total_Abstract::collect($address);
    $quote = $address->getQuote();
    $store = Mage::app()->getStore($quote->getStoreId());


    $eventArgs = array(
        'website_id'        => $store->getWebsiteId(),
        'customer_group_id' => $quote->getCustomerGroupId(),
        'coupon_code'       => $quote->getCouponCode(),
    );

    $this->_calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode());

    $items = $address->getAllItems();
    /* EDITS
     * Moved the if statement for no items in cart down past these previous methods and then if the address type is shipping and the coupon is set
     * add the coupon code to the address to allow the validation to still pick up the coupon code
     */
    if($quote->getCouponCode() && ($address->getAddressType() == Mage_Sales_Model_Quote_Address::TYPE_SHIPPING)){
        $address->setCouponCode($quote->getCouponCode());
    }
    if (!count($items)) {
        return $this;
    }

    $address->setDiscountDescription(array());

    foreach ($items as $item) {
        if ($item->getNoDiscount()) {
            $item->setDiscountAmount(0);
            $item->setBaseDiscountAmount(0);
        }
        else {
            /**
             * Child item discount we calculate for parent
             */
            if ($item->getParentItemId()) {
                continue;
            }

            $eventArgs['item'] = $item;
            Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs);

            if ($item->getHasChildren() && $item->isChildrenCalculated()) {
                foreach ($item->getChildren() as $child) {
                    $this->_calculator->process($child);
                    $eventArgs['item'] = $child;
                    Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs);
                    $this->_aggregateItemDiscount($child);
                }
            } else {
                $this->_calculator->process($item);
                $this->_aggregateItemDiscount($item);
            }
        }
    }

    /**
     * Process shipping amount discount
     */
    $address->setShippingDiscountAmount(0);
    $address->setBaseShippingDiscountAmount(0);
    if ($address->getShippingAmount()) {
        $this->_calculator->processShippingAmount($address);
        $this->_addAmount(-$address->getShippingDiscountAmount());
        $this->_addBaseAmount(-$address->getBaseShippingDiscountAmount());
    }

    $this->_calculator->prepareDescription($address);
    return $this;
}
dan.codes
  • 3,523
  • 9
  • 45
  • 59
  • Good one for posting all that code @Dan. I would be quite concerned about overriding the Cart controller and the couponPostAction method. It means that any future Magento upgrades or patches could potentially break the site because of your customizations. I would recommend never overriding a controller if you can avoid it (Event Observers are much preferable). I'll try to post more on my solution to demonstrate how to avoid overriding key controllers. – Jonathan Day Feb 01 '11 at 03:07
  • 1
    I did this a while back when I first got into Magento, so I would probably do it differently now. As for the this situation it is for a specific client and it is understood that if we are to do an upgrade it was going to have to be thoroughly QA tested before launched, I guess that goes without saying though. – dan.codes Feb 01 '11 at 11:03
  • all good, sounds like you have it under control, I just like to point those issues out for people who might be new to Magento reading the solution :) – Jonathan Day Feb 01 '11 at 22:25
1

This can definitely be achieved. It involves writing a custom module (start here and here) with a controller that accepts the value of your coupon field, initiates a checkout session for that user ($session = Mage::getSingleton('checkout/session')) and stores the coupon code in the checkout session ($session->setData('coupon_code',$coupon).

You would then extend the price model to check for coupon code in the session. You can override Mage_Catalog_Model_Product_Type_Price in your own module using the <rewrite> syntax. Retrieve the coupon code ($couponCode = Mage::getSingleton("checkout/session")->getData("coupon_code");). Note that the Price object is different for Bundle and other non-Simple product types.

If you need more info, I can post code samples.

Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
  • How complex is something like this? I'm very green with Magento (40 hours) and while I understand the MVC paradigm, (a day playing with CodeIgniter) I'm at beginner level. As a programmer, meh. However, I am very much willing to learn and make every effort possible whether I succeed or fail but given my "profile" do you think it's even feasible for me to try and bite this off? I'm by no means asking someone to do it for me. I'm just trying to figure out if I have a legit chance to make this work if I have some guidance along the way. Code samples would be great! – nero Feb 01 '11 at 00:55
  • It's moderately complex, but I'd suggest that if you're planning to use Magento in the medium-long term, that it's a good project to start with. It will introduce you to Magento's code structures while using a concept that you're familiar with. Magento does have a learning curve (lots of people whine about that), but the architecture is actually elegant and highly repeatable once you're familiar with it. – Jonathan Day Feb 01 '11 at 01:50
  • I recently did a small project with different requirements but I used a similar approach. For this I would definitely follow Jonathan's suggestions. Let me also say that if you need to make several custom enhancements (such as this one), your trip with Magento will be a long and turbulent one. Magento is enormously configurable, and is extensible for "easy" customization. The only problem is that some of those easy customizations end up being a frikin' pain in the butt and take a long time. This is why Jonathan said you really need to commit to Magento for the long term. – shaune Feb 01 '11 at 22:05