UPS shipping method and products with no weight

Posted on 16. Mar, 2010 by Fido in Development, Magento

Magento, where art thou documentation? Where art thou support for community code and problem solutions?

Laments to the nature of Varien’s views “Open Source” aside, the UPS shipping module has one glaring bug: The UPS API doesn’t handle weights of zero (or, perhaps, as I havent verified this, Magento’s UPS module code doesn’t properly take into account that some products may have weight of zero).

(It would seem handy if we could set a shipping method per product. If only.)

Now, before I get into details on a work around to this issue, you should know that if you do not want products considered in shipping calculations, you should be using Virtual products. These act like simple products but are not calculated in the shipping. I have not tested how these interact with Bundled or Grouped products.

You should also consider using Promotions (Shopping Cart price rules) to give items meeting certain criteria free shipping (You can then give those products a weight to avoid the zero-weight issue).

Now, without further pontification (yay, big words),

here’s the fix:

(p.s. – this is done using Magento version 1.3.2.4)

1) We are COPYING: app/code/core/Mage/Usa/Model/Shipping/Carrier/Ups.php and pasting it here: code/local/Mage/Usa/Model/Shipping/Carrier/Ups.php We do this so we overwrite the core code, without actually changing the core code (best practices). Make sure any edits are done to the Ups.php found in your “local” folder rather than the “core” folder.

2) What we want to do is test if a product has a weight of zero. If it does, we flag it, and tell UPS that we have a weight of .05. Pretending our product has some weight prevents UPS from returning a response we can’t use. We then test for this flag later in the code to determine if we should reset the cost of the shipping to zero (since presumably, no weight = no cost – Apologies if this does not work with your business logic).

Code added to Ups.php:

//Line 50
protected $_isZeroWeight = false;

//Line 155ish
if($weight == 0) {
        $weight = .05;
        $this->_isZeroWeight = true;
}

//Line 295ish
else if($this->_isZeroWeight == true) {
        foreach ($priceArr as $method=>$price) {
        	 $rate = Mage::getModel('shipping/rate_result_method');
        	$rate->setCarrier('ups');
        	$rate->setCarrierTitle($this->getConfigData('title'));
        	$rate->setMethod($method);
        	$method_arr = $this->getCode('method', $method);
        	$rate->setMethodTitle(Mage::helper('usa')->__($method_arr));
        	$rate->setCost(0);
        	$rate->setPrice(0);
        	$result->append($rate);
        }
}

Now, here is the full code. I suggest you go by this to see how the changes relate to the rest of the code:

<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magentocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magentocommerce.com for more information.
 *
 * @category   Mage
 * @package    Mage_Usa
 * @copyright  Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * UPS shipping rates estimation
 *
 * @category   Mage
 * @package    Mage_Usa
 * @author      Magento Core Team <core@magentocommerce.com>
 */
class Mage_Usa_Model_Shipping_Carrier_Ups
    extends Mage_Usa_Model_Shipping_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{

    protected $_code = 'ups';

    protected $_request = null;

    protected $_result = null;

    protected $_xmlAccessRequest = null;

    protected $_defaultCgiGatewayUrl = 'http://www.ups.com:80/using/services/rave/qcostcgi.cgi';

    protected $_isZeroWeight = false;

    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {
        if (!$this->getConfigFlag('active')) {
            return false;
        }

        $this->setRequest($request);

        $this->_result = $this->_getQuotes();
        $this->_updateFreeMethodQuote($request);

        return $this->getResult();
    }

    public function setRequest(Mage_Shipping_Model_Rate_Request $request)
    {
        $this->_request = $request;

        $r = new Varien_Object();

        if ($request->getLimitMethod()) {
            $r->setAction($this->getCode('action', 'single'));
            $r->setProduct($request->getLimitMethod());
        } else {
            $r->setAction($this->getCode('action', 'all'));
            $r->setProduct('GND'.$this->getConfigData('dest_type'));
        }

        if ($request->getUpsPickup()) {
            $pickup = $request->getUpsPickup();
        } else {
            $pickup = $this->getConfigData('pickup');
        }
        $r->setPickup($this->getCode('pickup', $pickup));

        if ($request->getUpsContainer()) {
            $container = $request->getUpsContainer();
        } else {
            $container = $this->getConfigData('container');
        }
        $r->setContainer($this->getCode('container', $container));

        if ($request->getUpsDestType()) {
            $destType = $request->getUpsDestType();
        } else {
            $destType = $this->getConfigData('dest_type');
        }
        $r->setDestType($this->getCode('dest_type', $destType));

        if ($request->getOrigCountry()) {
            $origCountry = $request->getOrigCountry();
        } else {
            $origCountry = Mage::getStoreConfig('shipping/origin/country_id', $this->getStore());
        }

        $r->setOrigCountry(Mage::getModel('directory/country')->load($origCountry)->getIso2Code());

        if ($request->getOrigRegionCode()) {
            $origRegionCode = $request->getOrigRegionCode();
        } else {
            $origRegionCode = Mage::getStoreConfig('shipping/origin/region_id', $this->getStore());
            if (is_numeric($origRegionCode)) {
                $origRegionCode = Mage::getModel('directory/region')->load($origRegionCode)->getCode();
            }
        }
        $r->setOrigRegionCode($origRegionCode);

        if ($request->getOrigPostcode()) {
            $r->setOrigPostal($request->getOrigPostcode());
        } else {
            $r->setOrigPostal(Mage::getStoreConfig('shipping/origin/postcode', $this->getStore()));
        }

        if ($request->getOrigCity()) {
            $r->setOrigCity($request->getOrigCity());
        } else {
            $r->setOrigCity(Mage::getStoreConfig('shipping/origin/city', $this->getStore()));
        }

        if ($request->getDestCountryId()) {
            $destCountry = $request->getDestCountryId();
        } else {
            $destCountry = self::USA_COUNTRY_ID;
        }

        //for UPS, puero rico state for US will assume as puerto rico country
        if ($destCountry==self::USA_COUNTRY_ID && ($request->getDestPostcode()=='00912' || $request->getDestRegionCode()==self::PUERTORICO_COUNTRY_ID)) {
            $destCountry = self::PUERTORICO_COUNTRY_ID;
        }

        $r->setDestCountry(Mage::getModel('directory/country')->load($destCountry)->getIso2Code());

        $r->setDestRegionCode($request->getDestRegionCode());

        if ($request->getDestPostcode()) {
            $r->setDestPostal($request->getDestPostcode());
        } else {

        }

        $weight = $this->getTotalNumOfBoxes($request->getPackageWeight());

        if($weight == 0) {
        	$weight = .05;
        	$this->_isZeroWeight = true;
        }

        $r->setWeight($weight);
        if ($request->getFreeMethodWeight()!=$request->getPackageWeight()) {
            $r->setFreeMethodWeight($request->getFreeMethodWeight());
        }

        $r->setValue($request->getPackageValue());
        $r->setValueWithDiscount($request->getPackageValueWithDiscount());

        if ($request->getUpsUnitMeasure()) {
            $unit = $request->getUpsUnitMeasure();
        } else {
            $unit = $this->getConfigData('unit_of_measure');
        }
        $r->setUnitMeasure($unit);

        $this->_rawRequest = $r;

        return $this;
    }

    public function getResult()
    {
       return $this->_result;
    }

    protected function _getQuotes()
    {
        switch ($this->getConfigData('type')) {
            case 'UPS':
               return $this->_getCgiQuotes();

            case 'UPS_XML':
               return $this->_getXmlQuotes();
        }
        return null;
    }

    protected function _setFreeMethodRequest($freeMethod)
    {
        $r = $this->_rawRequest;

        $weight = $this->getTotalNumOfBoxes($r->getFreeMethodWeight());
        $r->setWeight($weight);
        $r->setAction($this->getCode('action', 'single'));
        $r->setProduct($freeMethod);
    }

    protected function _getCgiQuotes()
    {
        $r = $this->_rawRequest;

        $params = array(
            'accept_UPS_license_agreement' => 'yes',
            '10_action'      => $r->getAction(),
            '13_product'     => $r->getProduct(),
            '14_origCountry' => $r->getOrigCountry(),
            '15_origPostal'  => $r->getOrigPostal(),
            'origCity'       => $r->getOrigCity(),
            '19_destPostal'  => substr($r->getDestPostal(), 0, 5),
            '22_destCountry' => $r->getDestCountry(),
            '23_weight'      => $r->getWeight(),
            '47_rate_chart'  => $r->getPickup(),
            '48_container'   => $r->getContainer(),
            '49_residential' => $r->getDestType(),
            'weight_std'     => strtolower($r->getUnitMeasure()),
        );
        $params['47_rate_chart'] = $params['47_rate_chart']['label'];

        try {
            $url = $this->getConfigData('gateway_url');
            if (!$url) {
                $url = $this->_defaultCgiGatewayUrl;
            }
            $client = new Zend_Http_Client();
            $client->setUri($url);
            $client->setConfig(array('maxredirects'=>0, 'timeout'=>30));
            $client->setParameterGet($params);
            $response = $client->request();
            $responseBody = $response->getBody();
        } catch (Exception $e) {
            $responseBody = '';
        }

        return $this->_parseCgiResponse($responseBody);
    }

    public function getShipmentByCode($code,$origin = null){
        if($origin===null){
            $origin = $this->getConfigData('origin_shipment');
        }
        $arr = $this->getCode('originShipment',$origin);
        if(isset($arr[$code]))
            return $arr[$code];
        else
            return false;
    }

    protected function _parseCgiResponse($response)
    {
        $costArr = array();
        $priceArr = array();
        $errorTitle = Mage::helper('usa')->__('Unknown error');
        if (strlen(trim($response))>0) {
            $rRows = explode("\n", $response);
            $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
            foreach ($rRows as $rRow) {
                $r = explode('%', $rRow);
                switch (substr($r[0],-1)) {
                    case 3: case 4:
                        if (in_array($r[1], $allowedMethods)) {
                            $responsePrice = Mage::app()->getLocale()->getNumber($r[8]);
                            $costArr[$r[1]] = $responsePrice;
                            $priceArr[$r[1]] = $this->getMethodPrice($responsePrice, $r[1]);
                        }
                        break;
                    case 5:
                        $errorTitle = $r[1];
                        break;
                    case 6:
                        if (in_array($r[3], $allowedMethods)) {
                            $responsePrice = Mage::app()->getLocale()->getNumber($r[10]);
                            $costArr[$r[3]] = $responsePrice;
                            $priceArr[$r[3]] = $this->getMethodPrice($responsePrice, $r[3]);
                        }
                        break;
                }
            }
            asort($priceArr);
        }

        $result = Mage::getModel('shipping/rate_result');
        $defaults = $this->getDefaults();
        if (empty($priceArr)) {
            $error = Mage::getModel('shipping/rate_result_error');
            $error->setCarrier('ups');
            $error->setCarrierTitle($this->getConfigData('title'));
            //$error->setErrorMessage($errorTitle);
            $error->setErrorMessage($this->getConfigData('specificerrmsg'));
            $result->append($error);
        } else if($this->_isZeroWeight == true) {
        	foreach ($priceArr as $method=>$price) {
        	    $rate = Mage::getModel('shipping/rate_result_method');
        	    $rate->setCarrier('ups');
        	    $rate->setCarrierTitle($this->getConfigData('title'));
        	    $rate->setMethod($method);
        	    $method_arr = $this->getCode('method', $method);
        	    $rate->setMethodTitle(Mage::helper('usa')->__($method_arr));
        	    $rate->setCost(0);
        	    $rate->setPrice(0);
        	    $result->append($rate);
        	}
        } else {
            foreach ($priceArr as $method=>$price) {
                $rate = Mage::getModel('shipping/rate_result_method');
                $rate->setCarrier('ups');
                $rate->setCarrierTitle($this->getConfigData('title'));
                $rate->setMethod($method);
                $method_arr = $this->getCode('method', $method);
                $rate->setMethodTitle(Mage::helper('usa')->__($method_arr));
                $rate->setCost($costArr[$method]);
                $rate->setPrice($price);
                $result->append($rate);
            }
        }
//echo "<!--".print_r($result,1)."-->";
        return $result;
    }

//And so on..... I have truncated the rest of the file as no further changes are made. Don't delete the code here yourself.

What does this result in?

  1. Requesting a quote from the Cart page shows cost of zero for all shipping methods
  2. When checking out, users still must select a shipping method. They are all at zero cost, but users will still be able to check items such as 1-day shipping (And/or whatever methods you have enabled). Is this optimal? Probably not, however this issue is, unfortunately, outside of the scope of this article at this time.
  3. It appears that Magento will still calculate shopping carts with mixed products (products with and without weights) correctly. You should test this to make sure customers aren’t getting over or under charged.

Hope this helps someone!

Tags: , , ,

18 Comments

andrew

17. Mar, 2010

Thanks for this post!
I’m using Magento version1.3.2.4

First problem I was having with this code is an error stating unexpected T_PUBLIC. I added a “}” at the end of your code addition and got rid of the error. But now all I receive at checkout is the following:

Sorry, no quotes are available for this order at this time.

Any thoughts? Thanks.

Fido

17. Mar, 2010

My best suggestion would be to not copy and paste the entire code snippet above, but to add the code snippets into your Ups.php file by line number as noted (Compare to the long code snippet above to see exact placement).

There might be another error happening that is causing the UPS module to spit out its standard “not available at this time” error (Which shows regardless of the type of error the UPS module might be producing).

andrew

22. Mar, 2010

Thanks for the reply! I had gone through my own Ups.php and replaced just the snippets. It was, of course, my fault I ended up missing a “}”.

I have one other question. In number two above you mention all shipping methods showing at zero. Would it be possible to include an if statement after the initial calculation that checks to see if cost is at zero and if so only the Ground shipping method will show?

Thanks again. I am so close to having this solved for my client.

andrew

22. Mar, 2010

Please delete my previous post. Because what I actually need is what you say is out of the scope of this article. I need only the method of UPS Ground to show a cost of zero if weight is zero.

If you have time to help, I would very much appreciate it. Thanks!

Fido

22. Mar, 2010

Hi Andrew -
Unfortunately I don’t have a lot of time to help . There is an issue with what you are trying to do – If there is no weight, I don’t think UPS can calculate a shipping cost for any of the shipping methods.
What I did was “fake” a weight (by setting it to .05) – So technically, the UPS module is grabbing prices based on that weight. However, I am taking the prices and setting them to zero.
In the foreach loop, you could try to say if($method == “ground”) {… } and only set ground to Zero that way. However, the price you see for other shipping methods will always still be based on .05

manoj

01. May, 2010

hello fido
i want to debug magnto code with zend sudio
but unable to load how to do best practice to overlaod core function please help me i want to write my own logic into magento. some payment configration

Jordan Walker

04. Jun, 2010

This has been very informative and helpful to me as I build a website for a luxury watch seller. Thanks!

brian

30. Jun, 2010

Does anyone know how to send a shipping record to a third party vendor using Magento? Like an ODBC file or an XML file?

Web Dev

15. Jul, 2010

Nice, I was looking for the same. Its really a great pain when can’t figure out anything in magento after all. thank you!

Sigma Infosolutions

29. Jul, 2010

How 2 different sub-domain can have same shopping cart (i.e) Items purchased from one Sub-domain should reflect when went to another sub-domain and vice-verse in magento. Please suggest ……

Thanks,
Anil

Dann

05. Aug, 2010

Hey there, I am having a problem with my shipping, lets say I have 1 product added to cart that weights 2 lbs and shipping cost is $9.00 for 2 lbs, then I add 2 for qty so the cost should be $18.00 for 4 lbs, but instead the shipping cost is only like $9.70. Please any help is greatly appreciated.

mark23

09. Aug, 2010

Thanks for posting and sharing your ideas and thoughts. Very nice.

Nicolas

01. Oct, 2010

Cool. Magento is wonderfull. Tanks for the tips

Impulsis

30. Nov, 2010

Thank you! This post with detailed explanation helped to fix the bug! No problem with it from now )

Prashant

08. Dec, 2010

Nice article. It helped me a lot thanks….

Shannon

22. Dec, 2010

This is way over my head, but I am having similar problems with my recently launched Magento website http://www.madeinmuseum.com. My developer is in Italy and it is getting to difficult to work with him. Does anyone know of a good Magento consultant who could work on touching up my site for around $30 per hour?
It would be a great site for a portfolio and we plan to expand. Check it out….design@madeinmuseum.com

Magento Host

03. Jan, 2011

Very useful, thank you for this wonderful fix!

Marahrens

10. Jan, 2011

Thanks for the information. Very nice!

Leave a reply