Warning: THIS POST IS DEPRECATED! I gave up trying to hack OrderShip and wrote my own extension (http://www.illapps.com/shipsync.html)..    I'm leaving the post here for reference, but I strongly suggest going with a commercial solution.

For some time now FedEx has been phasing out their legacy DC API, and encouraging developers to migrate to the new Web Services API. I had already starting dabbling in the new API to help automate shipping, but the fire was put under my tail yesterday when I received this Magento error report ->

Warning: Simplexml_load_string() function.simplexml-load-string] Entity: line 1 parser error string not closed expecting “ or “ in /app/code/core/Mage/Usa/Model/Shipping/Carrier/Fedex.php on line 432

Uh oh. It looks like a truncated or corrupted xml response. So I did a bit of googling, and discovered that others are currently having the same issues with FedEx. But, as much as I'd like to point the finger at the big guy, it turns out that Magento is using FedEx's legacy API, and this service will likely be decommissioned in the near future. So, it's not surprising that we're having trouble with this old crap. FedEx now offers a new web services API, and it is much much cooler. Thus, the solution here is to update Magento to support the new FedEx web services. Additionally, Magento needs failover shipping support! Here's the scoop ->

Disclaimers

This solution is just a hack, but you may find it useful, or as a starting point for a more elegant solution. If enough people take interest I may do more with it.

Warning : Do not attempt this process on a live site. Try it on a staging site first. If you must do it on a live site, backup everything before proceeding.

Step 1 - Install Zenprint XAXJAX Integration

Install Zenprint XAXJAX Integration (Zenprint_Xajax)

I'm not really interested in another AJAX library, but it looks like this is only getting called during the shipping process, so it's not a ton of overheard. Either way, it's required for Zenprint's Shipping module, so for now we need it. Install via Magento Connect.

Note: this says 1.2 in the package manager, but seems to be working fine in 1.3.x

Step 2 - Install Zenprint API Shipping Module

Install Zenprint API Shipping module (Zenprint_Ordership)

This module adds auto shipping (with tracking numbers and PDF shipping labels). Install via Magento Connect.

Note: this one also says 1.2, but installs fine in 1.3.x

Help!

On a shared host? >>> Having permissions problems with Magento Connect? Click here

On a shared host? >>> You may need to ask your hosting provider to install needed dependencies

Step 3 - Signup for FedEx Developer Resources, and setup Zenprint

At this point you need a FedEx account, key, password, and meter. Speak to your FedEx representative or check out FedEx Developer Resources to get started.

Once you get your keys and such, fill in the data in the Magento admin backend (System->Configuration->Shipping Methods->FedEx). You must also enter your address in the shipping settings configuration (System->Configuration->Shipping Settings). Now take a moment to read the Zenprint_Ordership Documentation, and apply any relevant tweaks to your system.

Save everything, and clear your cache for good measure.

Step 4 - Update WSDL to v6

Now we get our hands dirty. First, download the shipping WSDL (ShipService_v6) and the rate WSDL (RateService_v6) from FedEx Developer Resources. Put both of these files in the following directory :

/magento/app/code/community/Zenprint/Ordership/Model/Shipping/Carrier/wsdl/

Note: When you first receive these files, the WSDL URL is set to "https://gatewaybeta.fedex.com:443/web-services." You will want to change this to the live URL ("https://gateway.fedex.com:443/web-services") once you begin using the production keys.

Step 5 - Load Fedex.php

Now load up the following file in your favorite editor :

/magento/app/code/community/Zenprint/Ordership/Model/Shipping/Carrier/Fedex.php

# You made that backup already, right???

Step 6 - Find and Replace

find↓

protected $_gatewayUrl = 'https://gateway.fedex.com/GatewayDC';

replace ↓

protected $_gatewayUrl = 'https://gateway.fedex.com/web-services';

find↓

$wsdlpath = dirname(__FILE__).'/wsdl/ShipService_v5.wsdl';

replace↓

$wsdlpath = dirname(__FILE__).'/wsdl/ShipService_v6.wsdl';

find↓

# 07/19/09 - Oops! Forgot this part--Added

$request['Version'] = array('ServiceId' => 'ship', 'Major' => 5, 'Intermediate' => 0, 'Minor' => 0);

replace↓

$request['Version'] = array('ServiceId' => 'ship', 'Major' => 6, 'Intermediate' => 0, 'Minor' => 0);

find↓

protected function _getXmlQuotes()
{
..
..
..
}

replace↓

# The following function makes the API call. The SOAP request is slow. Caching may help.

protected /************************************************************\
*
\************************************************************/
function _getXmlQuotes()
 
{
 
	$r = $this->_rawRequest;
	# Retrieve Magento data
 
	$newline = "";
	# Define HTML break
 
	$path_to_wsdl = dirname(__FILE__).'/wsdl/RateService_v6.wsdl';
	# WSDL path
 
	try
 
	{
		ini_set("soap.wsdl_cache_enabled", "0");
 
		$client = new SoapClient($path_to_wsdl);
 
		# Authenticate FedEx Key, and Password
		$request['WebAuthenticationDetail'] = array(
		'UserCredential' =>
		array(
		'Key' =>
		Mage::getStoreConfig('carriers/fedex/key'),
		'Password' =>
		Mage::getStoreConfig('carriers/fedex/password')));
 
		# Fedex Account and Meter
		$request['ClientDetail'] = array(
		'AccountNumber' =>
		Mage::getStoreConfig('carriers/fedex/account'),
		'MeterNumber' =>
		Mage::getStoreConfig('carriers/fedex/meter'));
 
		$request['TransactionDetail'] = array(
		'CustomerTransactionId' =>
		' *** Rate Available Services Request v6 using PHP ***');
 
		$request['Version'] = array('ServiceId' =>
		'crs', 'Major' =>
		'6', 'Intermediate' =>
		'0', 'Minor' =>
		'0');
 
		# DropoffType, ShipTimestamp, and PackagingType
		$request['RequestedShipment']['DropoffType'] = $this->
		getUnderscoreCodeFromCode(Mage::getStoreConfig('carriers/fedex/dropoff'));
 
		$request['RequestedShipment']['ShipTimestamp'] = date('c');
 
		$request['RequestedShipment']['PackagingType'] = $this->
		getUnderscoreCodeFromCode(Mage::getStoreConfig('carriers/fedex/packaging'));
 
		if(Mage::getStoreConfig('carriers/fedex/third_party') == 1)
		{
 
			$shipping_charges_payment = array('PaymentType' =>
			'THIRD_PARTY', 'Payor' =>
			array('AccountNumber' =>
			Mage::getStoreConfig('carriers/fedex/third_party_fedex_account'), 'CountryCode' =>
			Mage::getStoreConfig('carriers/fedex/third_party_fedex_account_country')));
 
		}
 
		else
		{
 
			$shipping_charges_payment = array('PaymentType' =>
			'SENDER', 'Payor' =>
			array('AccountNumber' =>
			Mage::getStoreConfig('carriers/fedex/account'), 'CountryCode' =>
			Mage::getStoreConfig('carriers/fedex/account_country')));
 
		}
 
		# Generate shipping street
		$shipperstreetlines = array(Mage::getStoreConfig('shipping/origin/address1'));
 
		if(Mage::getStoreConfig('shipping/origin/address2') != '')
		{
 
			$shipperstreetlines[] = Mage::getStoreConfig('shipping/origin/address2');
 
		}
 
		if(Mage::getStoreConfig('shipping/origin/address3') != '')
		{
 
			$shipperstreetlines[] = Mage::getStoreConfig('shipping/origin/address3');
 
		}
 
		# Set Shipper and Recipient
		$request['RequestedShipment']['Shipper'] = array('Address' =>
		array(
		'StreetLines' =>
		$shipperstreetlines, #Origin details
		'City' =>
		Mage::getStoreConfig('shipping/origin/city'),
		'StateOrProvinceCode' =>
		Mage::getModel('directory/region')->
		load(Mage::getStoreConfig('shipping/origin/region_id'))->
		getCode(),
		'PostalCode' =>
		Mage::getStoreConfig('shipping/origin/postcode'),
		'CountryCode' =>
		Mage::getStoreConfig('shipping/origin/country_id')));
 
		$request['RequestedShipment']['Recipient'] = array('Address' =>
		array(
		'PostalCode' =>
		$r->
		getDestPostal(),
		'CountryCode' =>
		$r->
		getDestCountry()));
 
		$request['RequestedShipment']['ShippingChargesPayment'] = $shipping_charges_payment;
 
		$request['RequestedShipment']['RateRequestTypes'] = 'LIST';
 
		$request['RequestedShipment']['PackageCount'] = '1';
 
		$request['RequestedShipment']['PackageDetail'] = 'INDIVIDUAL_PACKAGES';
 
		$request['RequestedShipment']['RequestedPackages'] = array('0' =>
		array('SequenceNumber' =>
		'1',
		'InsuredValue' =>
		array(
		'Amount' =>
		$r->
		getValue(),
		'Currency' =>
		Mage::app()->
		getBaseCurrencyCode()),
		'Weight' =>
		array('Value' =>
		$r->
		getWeight(), 'Units' =>
		'LB')));
 
		$response = $client->getRates($request);
	}
 
	catch (SoapFault $fault)
	{
		return $this->_parseXmlResponse(NULL);
	}
 
	if ($response->HighestSeverity == 'FAILURE')
		{ return $this->_parseXmlResponse(NULL); }
	else { return $this->_parseXmlResponse($response); }
}

replace↓

# This parses the response, and sets up a failsafe flat rate if FedEx is unavailable.

protected /************************************************************\
*
\************************************************************/
function _parseXmlResponse($response)
 
{	
 
	# Retrieve Magento data
	# Check for allowed shipping methods
	$allowedMethods = explode(",", $this->
	getConfigData('allowed_methods'));
 
	foreach ($allowedMethods as $method)
 
	{
 
		$parsedAllowedMethods[] = $this->
		getUnderscoreCodeFromCode($method);
 
	}
 
	$result = Mage::getModel('shipping/rate_result');
 
	$defaults = $this->
	getDefaults();
 
	if ($response == NULL)
	{
		# Set flat rate
		$rate = Mage::getModel('shipping/rate_result_method');
 
		$rate->
		setCarrier('fedex');
 
		$rate->
		setCarrierTitle($this->
		getConfigData('title'));
 
		$rate->
		setMethod('FEDEX_GROUND');
		# Shipping method
		$rate->
		setMethodTitle('FedEx Ground (Flat)');
		# Customer view
		$rate->
		setCost(7);
		# $7.00
		$rate->
		setPrice(7);
		# $7.00
		$result->
		append($rate);
 
		return $result;
	}
 
	elseif ($response->HighestSeverity == 'ERROR')
	{
		$msg = '';
		if(is_array($response->Notifications))
		{
			foreach ($response->Notifications as $notification)
				{ $msg .= $notification->Severity.': '.$notification->Message.$newline; }
		}
		else
		{
			$msg .= $response->Notifications->Severity.': '.$response->Notifications->Message.$newline;
		}
		$error = Mage::getModel('shipping/rate_result_error');
		$error->setCarrier('fedex');
		$error->setCarrierTitle($this->getConfigData('title'));
		$error->setErrorMessage($msg);
		$result->append($error);
		return $result;
	}
 
	else
	{
		# Iterate through retrieved FedEx responses
		foreach ($response->RateReplyDetails as $rateReply)
		{
			# Now update shipping rates for allowed methods
			if (in_array($rateReply->
			ServiceType, $parsedAllowedMethods))
 
			{
				$_serviceType = str_replace('_', '', $rateReply->ServiceType);
 
				$rate = Mage::getModel('shipping/rate_result_method');
 
				$rate->
				setCarrier('fedex');
 
				$rate->
				setCarrierTitle($this->
				getConfigData('title'));
 
				$rate->
				setMethod($_serviceType);
 
				$rate->
				setMethodTitle($this->
				getCode('method', $rateReply->
				ServiceType, true));
				$rate->
				setCost(
				$rateReply->
				RatedShipmentDetails[0]->
				ShipmentRateDetail->
				TotalNetCharge->
				Amount);
 
				$rate->
				setPrice($rateReply->
				RatedShipmentDetails[0]->
				ShipmentRateDetail->
				TotalNetCharge->
				Amount);
 
				$result->
				append($rate);
 
			}
		}
		return $result;
	}
}

Step 7 - Cleanup

I had to insert a definition for 2nd day air to get the text label to show up ->

public function getCode($type, $code='', $underscore=false)
    {
..
..
..
        //Needed since XML requests have no underscore, while SOAP interface includes them (nice one Fedex)
        $codes_underscore = array(
            'method'=>array(
                'PRIORITY_OVERNIGHT'                => Mage::helper('usa')->__('Priority Overnight'),
                'STANDARD_OVERNIGHT'                => Mage::helper('usa')->__('Standard Overnight'),
                'FIRST_OVERNIGHT'                   => Mage::helper('usa')->__('First Overnight'),
                'FEDEX_2DAY'                        => Mage::helper('usa')->__('2Day'),
  		'FEDEX_2_DAY'                        => Mage::helper('usa')->__('2Day'),  # Added
                'FEDEX_EXPRESS_SAVER'                => Mage::helper('usa')->__('Express Saver'),
                'INTERNATIONAL_PRIORITY'            => Mage::helper('usa')->__('International Priority'),
                'INTERNATIONAL_ECONOMY'             => Mage::helper('usa')->__('International Economy'),
                'INTERNATIONAL_FIRST'               => Mage::helper('usa')->__('International First'),
                'FEDEX_1DAY_FREIGHT'                 => Mage::helper('usa')->__('1 Day Freight'),
                'FEDEX_2DAY_FREIGHT'                 => Mage::helper('usa')->__('2 Day Freight'),
                'FEDEX_3DAY_FREIGHT'                 => Mage::helper('usa')->__('3 Day Freight'),
                'FEDEX_GROUND'                      => Mage::helper('usa')->__('Ground'),
                'GROUND_HOME_DELIVERY'               => Mage::helper('usa')->__('Home Delivery'),
                'INTERNATIONAL_PRIORITY_FREIGHT'    => Mage::helper('usa')->__('Intl Priority Freight'),
                'INTERNATIONAL_ECONOMY_FREIGHT'     => Mage::helper('usa')->__('Intl Economy Freight'),
                'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => Mage::helper('usa')->__('Europe First Priority'),
            ),
..
..
..
}

# 07/20/09 - Added better error handling and fixed a few bugs, updated code snippets above

Step 8 - Test

-> Test rate requests
-> Test auto ship functionality
-> Test PDF label printing
-> Test tracking number generation

Once you have everything working, FedEx will give you a production key and you can start shipping orders.

Conclusion

It's a hack, but it works. Enjoy!

..
..
..

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Mixx
  • Google
  • Furl
  • NewsVine
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • TwitThis