*/
class Mage_Usa_Model_Shipping_Carrier_Fedex
extends Mage_Usa_Model_Shipping_Carrier_Abstract
implements Mage_Shipping_Model_Carrier_Interface
{
/**
* Code of the carrier
*
* @var string
*/
const CODE = 'fedex';
/**
* Purpose of rate request
*
* @var string
*/
const RATE_REQUEST_GENERAL = 'general';
/**
* Purpose of rate request
*
* @var string
*/
const RATE_REQUEST_SMARTPOST = 'SMART_POST';
/**
* Code of the carrier
*
* @var string
*/
protected $_code = self::CODE;
/**
* Rate request data
*
* @var Mage_Shipping_Model_Rate_Request|null
*/
protected $_request = null;
/**
* Raw rate request data
*
* @var Varien_Object|null
*/
protected $_rawRequest = null;
/**
* Rate result data
*
* @var Mage_Shipping_Model_Rate_Result|null
*/
protected $_result = null;
/**
* Path to wsdl file of rate service
*
* @var string
*/
protected $_rateServiceWsdl;
/**
* Path to wsdl file of ship service
*
* @var string
*/
protected $_shipServiceWsdl = null;
/**
* Path to wsdl file of track service
*
* @var string
*/
protected $_trackServiceWsdl = null;
/**
* Container types that could be customized for FedEx carrier
*
* @var array
*/
protected $_customizableContainerTypes = array('YOUR_PACKAGING');
public function __construct()
{
parent::__construct();
$wsdlBasePath = Mage::getModuleDir('etc', 'Mage_Usa') . DS . 'wsdl' . DS . 'FedEx' . DS;
$this->_shipServiceWsdl = $wsdlBasePath . 'ShipService_v10.wsdl';
$this->_rateServiceWsdl = $wsdlBasePath . 'RateService_v10.wsdl';
$this->_trackServiceWsdl = $wsdlBasePath . 'TrackService_v5.wsdl';
}
/**
* Create soap client with selected wsdl
*
* @param string $wsdl
* @param bool|int $trace
* @return SoapClient
*/
protected function _createSoapClient($wsdl, $trace = false)
{
$client = new SoapClient($wsdl, array('trace' => $trace));
$client->__setLocation($this->getConfigFlag('sandbox_mode')
? 'https://wsbeta.fedex.com:443/web-services/rate'
: 'https://ws.fedex.com:443/web-services/rate'
);
return $client;
}
/**
* Create rate soap client
*
* @return SoapClient
*/
protected function _createRateSoapClient()
{
return $this->_createSoapClient($this->_rateServiceWsdl);
}
/**
* Create ship soap client
*
* @return SoapClient
*/
protected function _createShipSoapClient()
{
return $this->_createSoapClient($this->_shipServiceWsdl, 1);
}
/**
* Create track soap client
*
* @return SoapClient
*/
protected function _createTrackSoapClient()
{
return $this->_createSoapClient($this->_trackServiceWsdl, 1);
}
/**
* Collect and get rates
*
* @param Mage_Shipping_Model_Rate_Request $request
* @return Mage_Shipping_Model_Rate_Result|bool|null
*/
public function collectRates(Mage_Shipping_Model_Rate_Request $request)
{
if (!$this->getConfigFlag($this->_activeFlag)) {
return false;
}
$this->setRequest($request);
$this->_getQuotes();
$this->_updateFreeMethodQuote($request);
return $this->getResult();
}
/**
* Prepare and set request to this instance
*
* @param Mage_Shipping_Model_Rate_Request $request
* @return Mage_Usa_Model_Shipping_Carrier_Fedex
*/
public function setRequest(Mage_Shipping_Model_Rate_Request $request)
{
$this->_request = $request;
$r = new Varien_Object();
if ($request->getLimitMethod()) {
$r->setService($request->getLimitMethod());
}
if ($request->getFedexAccount()) {
$account = $request->getFedexAccount();
} else {
$account = $this->getConfigData('account');
}
$r->setAccount($account);
if ($request->getFedexDropoff()) {
$dropoff = $request->getFedexDropoff();
} else {
$dropoff = $this->getConfigData('dropoff');
}
$r->setDropoffType($dropoff);
if ($request->getFedexPackaging()) {
$packaging = $request->getFedexPackaging();
} else {
$packaging = $this->getConfigData('packaging');
}
$r->setPackaging($packaging);
if ($request->getOrigCountry()) {
$origCountry = $request->getOrigCountry();
} else {
$origCountry = Mage::getStoreConfig(
Mage_Shipping_Model_Shipping::XML_PATH_STORE_COUNTRY_ID,
$request->getStoreId()
);
}
$r->setOrigCountry(Mage::getModel('directory/country')->load($origCountry)->getIso2Code());
if ($request->getOrigPostcode()) {
$r->setOrigPostal($request->getOrigPostcode());
} else {
$r->setOrigPostal(Mage::getStoreConfig(
Mage_Shipping_Model_Shipping::XML_PATH_STORE_ZIP,
$request->getStoreId()
));
}
if ($request->getDestCountryId()) {
$destCountry = $request->getDestCountryId();
} else {
$destCountry = self::USA_COUNTRY_ID;
}
$r->setDestCountry(Mage::getModel('directory/country')->load($destCountry)->getIso2Code());
if ($request->getDestPostcode()) {
$r->setDestPostal($request->getDestPostcode());
} else {
}
$weight = $this->getTotalNumOfBoxes($request->getPackageWeight());
$r->setWeight($weight);
if ($request->getFreeMethodWeight()!= $request->getPackageWeight()) {
$r->setFreeMethodWeight($request->getFreeMethodWeight());
}
$r->setValue($request->getPackagePhysicalValue());
$r->setValueWithDiscount($request->getPackageValueWithDiscount());
$r->setMeterNumber($this->getConfigData('meter_number'));
$r->setKey($this->getConfigData('key'));
$r->setPassword($this->getConfigData('password'));
$r->setIsReturn($request->getIsReturn());
$r->setBaseSubtotalInclTax($request->getBaseSubtotalInclTax());
$this->_rawRequest = $r;
return $this;
}
/**
* Get result of request
*
* @return mixed
*/
public function getResult()
{
return $this->_result;
}
/**
* Get version of rates request
*
* @return array
*/
public function getVersionInfo()
{
return array(
'ServiceId' => 'crs',
'Major' => '10',
'Intermediate' => '0',
'Minor' => '0'
);
}
/**
* Forming request for rate estimation depending to the purpose
*
* @param string $purpose
* @return array
*/
protected function _formRateRequest($purpose)
{
$r = $this->_rawRequest;
$ratesRequest = array(
'WebAuthenticationDetail' => array(
'UserCredential' => array(
'Key' => $r->getKey(),
'Password' => $r->getPassword()
)
),
'ClientDetail' => array(
'AccountNumber' => $r->getAccount(),
'MeterNumber' => $r->getMeterNumber()
),
'Version' => $this->getVersionInfo(),
'RequestedShipment' => array(
'DropoffType' => $r->getDropoffType(),
'ShipTimestamp' => date('c'),
'PackagingType' => $r->getPackaging(),
'TotalInsuredValue' => array(
'Amount' => $r->getValue(),
'Currency' => $this->getCurrencyCode()
),
'Shipper' => array(
'Address' => array(
'PostalCode' => $r->getOrigPostal(),
'CountryCode' => $r->getOrigCountry()
)
),
'Recipient' => array(
'Address' => array(
'PostalCode' => $r->getDestPostal(),
'CountryCode' => $r->getDestCountry(),
'Residential' => (bool)$this->getConfigData('residence_delivery')
)
),
'ShippingChargesPayment' => array(
'PaymentType' => 'SENDER',
'Payor' => array(
'AccountNumber' => $r->getAccount(),
'CountryCode' => $r->getOrigCountry()
)
),
'CustomsClearanceDetail' => array(
'CustomsValue' => array(
'Amount' => $r->getValue(),
'Currency' => $this->getCurrencyCode()
)
),
'RateRequestTypes' => 'LIST',
'PackageCount' => '1',
'PackageDetail' => 'INDIVIDUAL_PACKAGES',
'RequestedPackageLineItems' => array(
'0' => array(
'Weight' => array(
'Value' => (float)$r->getWeight(),
'Units' => 'LB'
),
'GroupPackageCount' => 1,
)
)
)
);
if ($purpose == self::RATE_REQUEST_GENERAL) {
$ratesRequest['RequestedShipment']['RequestedPackageLineItems'][0]['InsuredValue'] = array(
'Amount' => $r->getValue(),
'Currency' => $this->getCurrencyCode()
);
} else if ($purpose == self::RATE_REQUEST_SMARTPOST) {
$ratesRequest['RequestedShipment']['ServiceType'] = self::RATE_REQUEST_SMARTPOST;
$ratesRequest['RequestedShipment']['SmartPostDetail'] = array(
'Indicia' => ((float)$r->getWeight() >= 1) ? 'PARCEL_SELECT' : 'PRESORTED_STANDARD',
'HubId' => $this->getConfigData('smartpost_hubid')
);
}
return $ratesRequest;
}
/**
* Makes remote request to the carrier and returns a response
*
* @param string $purpose
* @return mixed
*/
protected function _doRatesRequest($purpose)
{
$ratesRequest = $this->_formRateRequest($purpose);
$requestString = serialize($ratesRequest);
$response = $this->_getCachedQuotes($requestString);
$debugData = array('request' => $ratesRequest);
if ($response === null) {
try {
$client = $this->_createRateSoapClient();
$response = $client->getRates($ratesRequest);
$this->_setCachedQuotes($requestString, serialize($response));
$debugData['result'] = $response;
} catch (Exception $e) {
$debugData['result'] = array('error' => $e->getMessage(), 'code' => $e->getCode());
Mage::logException($e);
}
} else {
$response = unserialize($response);
$debugData['result'] = $response;
}
$this->_debug($debugData);
return $response;
}
/**
* Do remote request for and handle errors
*
* @return Mage_Shipping_Model_Rate_Result
*/
protected function _getQuotes()
{
$this->_result = Mage::getModel('shipping/rate_result');
// make separate request for Smart Post method
$allowedMethods = explode(',', $this->getConfigData('allowed_methods'));
if (in_array(self::RATE_REQUEST_SMARTPOST, $allowedMethods)) {
$response = $this->_doRatesRequest(self::RATE_REQUEST_SMARTPOST);
$preparedSmartpost = $this->_prepareRateResponse($response);
if (!$preparedSmartpost->getError()) {
$this->_result->append($preparedSmartpost);
}
}
// make general request for all methods
$response = $this->_doRatesRequest(self::RATE_REQUEST_GENERAL);
$preparedGeneral = $this->_prepareRateResponse($response);
if (!$preparedGeneral->getError() || ($this->_result->getError() && $preparedGeneral->getError())) {
$this->_result->append($preparedGeneral);
}
return $this->_result;
}
/**
* Prepare shipping rate result based on response
*
* @param mixed $response
* @return Mage_Shipping_Model_Rate_Result
*/
protected function _prepareRateResponse($response)
{
$costArr = array();
$priceArr = array();
$errorTitle = 'Unable to retrieve tracking';
if (is_object($response)) {
if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
$errorTitle = (string)$response->Notifications->Message;
} elseif (isset($response->RateReplyDetails)) {
$allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
if (is_array($response->RateReplyDetails)) {
foreach ($response->RateReplyDetails as $rate) {
$serviceName = (string)$rate->ServiceType;
if (in_array($serviceName, $allowedMethods)) {
$amount = $this->_getRateAmountOriginBased($rate);
$costArr[$serviceName] = $amount;
$priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
}
}
asort($priceArr);
} else {
$rate = $response->RateReplyDetails;
$serviceName = (string)$rate->ServiceType;
if (in_array($serviceName, $allowedMethods)) {
$amount = $this->_getRateAmountOriginBased($rate);
$costArr[$serviceName] = $amount;
$priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
}
}
}
}
$result = Mage::getModel('shipping/rate_result');
if (empty($priceArr)) {
$error = Mage::getModel('shipping/rate_result_error');
$error->setCarrier($this->_code);
$error->setCarrierTitle($this->getConfigData('title'));
$error->setErrorMessage($errorTitle);
$error->setErrorMessage($this->getConfigData('specificerrmsg'));
$result->append($error);
} else {
foreach ($priceArr as $method=>$price) {
$rate = Mage::getModel('shipping/rate_result_method');
$rate->setCarrier($this->_code);
$rate->setCarrierTitle($this->getConfigData('title'));
$rate->setMethod($method);
$rate->setMethodTitle($this->getCode('method', $method));
$rate->setCost($costArr[$method]);
$rate->setPrice($price);
$result->append($rate);
}
}
return $result;
}
/**
* Get origin based amount form response of rate estimation
*
* @param stdClass $rate
* @return null|float
*/
protected function _getRateAmountOriginBased($rate)
{
$amount = null;
$rateTypeAmounts = array();
if (is_object($rate)) {
// The "RATED..." rates are expressed in the currency of the origin country
foreach ($rate->RatedShipmentDetails as $ratedShipmentDetail) {
$netAmount = (string)$ratedShipmentDetail->ShipmentRateDetail->TotalNetCharge->Amount;
$rateType = (string)$ratedShipmentDetail->ShipmentRateDetail->RateType;
$rateTypeAmounts[$rateType] = $netAmount;
}
// Order is important
foreach (array('RATED_ACCOUNT_SHIPMENT', 'RATED_LIST_SHIPMENT', 'RATED_LIST_PACKAGE') as $rateType) {
if (!empty($rateTypeAmounts[$rateType])) {
$amount = $rateTypeAmounts[$rateType];
break;
}
}
if (is_null($amount)) {
$amount = (string)$rate->RatedShipmentDetails[0]->ShipmentRateDetail->TotalNetCharge->Amount;
}
}
return $amount;
}
/**
* Set free method request
*
* @param $freeMethod
* @return void
*/
protected function _setFreeMethodRequest($freeMethod)
{
$r = $this->_rawRequest;
$weight = $this->getTotalNumOfBoxes($r->getFreeMethodWeight());
$r->setWeight($weight);
$r->setService($freeMethod);
}
/**
* Get xml quotes
*
* @return Mage_Shipping_Model_Rate_Result
*/
protected function _getXmlQuotes()
{
$r = $this->_rawRequest;
$xml = new SimpleXMLElement('');
$xml->addAttribute('xmlns:api', 'http://www.fedex.com/fsmapi');
$xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$xml->addAttribute('xsi:noNamespaceSchemaLocation', 'FDXRateAvailableServicesRequest.xsd');
$requestHeader = $xml->addChild('RequestHeader');
$requestHeader->addChild('AccountNumber', $r->getAccount());
$requestHeader->addChild('MeterNumber', '0');
$xml->addChild('ShipDate', date('Y-m-d'));
$xml->addChild('DropoffType', $r->getDropoffType());
if ($r->hasService()) {
$xml->addChild('Service', $r->getService());
}
$xml->addChild('Packaging', $r->getPackaging());
$xml->addChild('WeightUnits', 'LBS');
$xml->addChild('Weight', $r->getWeight());
$originAddress = $xml->addChild('OriginAddress');
$originAddress->addChild('PostalCode', $r->getOrigPostal());
$originAddress->addChild('CountryCode', $r->getOrigCountry());
$destinationAddress = $xml->addChild('DestinationAddress');
$destinationAddress->addChild('PostalCode', $r->getDestPostal());
$destinationAddress->addChild('CountryCode', $r->getDestCountry());
$payment = $xml->addChild('Payment');
$payment->addChild('PayorType', 'SENDER');
$declaredValue = $xml->addChild('DeclaredValue');
$declaredValue->addChild('Value', $r->getValue());
$declaredValue->addChild('CurrencyCode', $this->getCurrencyCode());
if ($this->getConfigData('residence_delivery')) {
$specialServices = $xml->addChild('SpecialServices');
$specialServices->addChild('ResidentialDelivery', 'true');
}
$xml->addChild('PackageCount', '1');
$request = $xml->asXML();
$responseBody = $this->_getCachedQuotes($request);
if ($responseBody === null) {
$debugData = array('request' => $request);
try {
$url = $this->getConfigData('gateway_url');
if (!$url) {
$url = $this->_defaultGatewayUrl;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
$responseBody = curl_exec($ch);
curl_close ($ch);
$debugData['result'] = $responseBody;
$this->_setCachedQuotes($request, $responseBody);
}
catch (Exception $e) {
$debugData['result'] = array('error' => $e->getMessage(), 'code' => $e->getCode());
$responseBody = '';
}
$this->_debug($debugData);
}
return $this->_parseXmlResponse($responseBody);
}
/**
* Prepare shipping rate result based on response
*
* @param mixed $response
* @return Mage_Shipping_Model_Rate_Result
*/
protected function _parseXmlResponse($response)
{
$costArr = array();
$priceArr = array();
if (strlen(trim($response))>0) {
if ($xml = $this->_parseXml($response)) {
if (is_object($xml->Error) && is_object($xml->Error->Message)) {
$errorTitle = (string)$xml->Error->Message;
} elseif (is_object($xml->SoftError) && is_object($xml->SoftError->Message)) {
$errorTitle = (string)$xml->SoftError->Message;
} else {
$errorTitle = 'Unknown error';
}
$allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
foreach ($xml->Entry as $entry) {
if (in_array((string)$entry->Service, $allowedMethods)) {
$costArr[(string)$entry->Service] =
(string)$entry->EstimatedCharges->DiscountedCharges->NetCharge;
$priceArr[(string)$entry->Service] = $this->getMethodPrice(
(string)$entry->EstimatedCharges->DiscountedCharges->NetCharge,
(string)$entry->Service
);
}
}
asort($priceArr);
} else {
$errorTitle = 'Response is in the wrong format.';
}
} else {
$errorTitle = 'Unable to retrieve tracking';
}
$result = Mage::getModel('shipping/rate_result');
if (empty($priceArr)) {
$error = Mage::getModel('shipping/rate_result_error');
$error->setCarrier('fedex');
$error->setCarrierTitle($this->getConfigData('title'));
$error->setErrorMessage($this->getConfigData('specificerrmsg'));
$result->append($error);
} else {
foreach ($priceArr as $method=>$price) {
$rate = Mage::getModel('shipping/rate_result_method');
$rate->setCarrier('fedex');
$rate->setCarrierTitle($this->getConfigData('title'));
$rate->setMethod($method);
$rate->setMethodTitle($this->getCode('method', $method));
$rate->setCost($costArr[$method]);
$rate->setPrice($price);
$result->append($rate);
}
}
return $result;
}
/**
* Parse XML string and return XML document object or false
*
* @param string $xmlContent
* @return SimpleXMLElement|bool
*/
protected function _parseXml($xmlContent)
{
try {
try {
return simplexml_load_string($xmlContent);
} catch (Exception $e) {
throw new Exception(Mage::helper('usa')->__('Failed to parse xml document: %s', $xmlContent));
}
} catch (Exception $e) {
Mage::logException($e);
return false;
}
}
/**
* Get configuration data of carrier
*
* @param string $type
* @param string $code
* @return array|bool
*/
public function getCode($type, $code='')
{
$codes = array(
'method' => array(
'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => Mage::helper('usa')->__('Europe First Priority'),
'FEDEX_1_DAY_FREIGHT' => Mage::helper('usa')->__('1 Day Freight'),
'FEDEX_2_DAY_FREIGHT' => Mage::helper('usa')->__('2 Day Freight'),
'FEDEX_2_DAY' => Mage::helper('usa')->__('2 Day'),
'FEDEX_2_DAY_AM' => Mage::helper('usa')->__('2 Day AM'),
'FEDEX_3_DAY_FREIGHT' => Mage::helper('usa')->__('3 Day Freight'),
'FEDEX_EXPRESS_SAVER' => Mage::helper('usa')->__('Express Saver'),
'FEDEX_GROUND' => Mage::helper('usa')->__('Ground'),
'FIRST_OVERNIGHT' => Mage::helper('usa')->__('First Overnight'),
'GROUND_HOME_DELIVERY' => Mage::helper('usa')->__('Home Delivery'),
'INTERNATIONAL_ECONOMY' => Mage::helper('usa')->__('International Economy'),
'INTERNATIONAL_ECONOMY_FREIGHT' => Mage::helper('usa')->__('Intl Economy Freight'),
'INTERNATIONAL_FIRST' => Mage::helper('usa')->__('International First'),
'INTERNATIONAL_GROUND' => Mage::helper('usa')->__('International Ground'),
'INTERNATIONAL_PRIORITY' => Mage::helper('usa')->__('International Priority'),
'INTERNATIONAL_PRIORITY_FREIGHT' => Mage::helper('usa')->__('Intl Priority Freight'),
'PRIORITY_OVERNIGHT' => Mage::helper('usa')->__('Priority Overnight'),
'SMART_POST' => Mage::helper('usa')->__('Smart Post'),
'STANDARD_OVERNIGHT' => Mage::helper('usa')->__('Standard Overnight'),
'FEDEX_FREIGHT' => Mage::helper('usa')->__('Freight'),
'FEDEX_NATIONAL_FREIGHT' => Mage::helper('usa')->__('National Freight'),
),
'dropoff' => array(
'REGULAR_PICKUP' => Mage::helper('usa')->__('Regular Pickup'),
'REQUEST_COURIER' => Mage::helper('usa')->__('Request Courier'),
'DROP_BOX' => Mage::helper('usa')->__('Drop Box'),
'BUSINESS_SERVICE_CENTER' => Mage::helper('usa')->__('Business Service Center'),
'STATION' => Mage::helper('usa')->__('Station')
),
'packaging' => array(
'FEDEX_ENVELOPE' => Mage::helper('usa')->__('FedEx Envelope'),
'FEDEX_PAK' => Mage::helper('usa')->__('FedEx Pak'),
'FEDEX_BOX' => Mage::helper('usa')->__('FedEx Box'),
'FEDEX_TUBE' => Mage::helper('usa')->__('FedEx Tube'),
'FEDEX_10KG_BOX' => Mage::helper('usa')->__('FedEx 10kg Box'),
'FEDEX_25KG_BOX' => Mage::helper('usa')->__('FedEx 25kg Box'),
'YOUR_PACKAGING' => Mage::helper('usa')->__('Your Packaging')
),
'containers_filter' => array(
array(
'containers' => array('FEDEX_ENVELOPE', 'FEDEX_PAK'),
'filters' => array(
'within_us' => array(
'method' => array(
'FEDEX_EXPRESS_SAVER',
'FEDEX_2_DAY',
'FEDEX_2_DAY_AM',
'STANDARD_OVERNIGHT',
'PRIORITY_OVERNIGHT',
'FIRST_OVERNIGHT',
)
),
'from_us' => array(
'method' => array(
'INTERNATIONAL_FIRST',
'INTERNATIONAL_ECONOMY',
'INTERNATIONAL_PRIORITY',
)
)
)
),
array(
'containers' => array('FEDEX_BOX', 'FEDEX_TUBE'),
'filters' => array(
'within_us' => array(
'method' => array(
'FEDEX_2_DAY',
'FEDEX_2_DAY_AM',
'STANDARD_OVERNIGHT',
'PRIORITY_OVERNIGHT',
'FIRST_OVERNIGHT',
'FEDEX_FREIGHT',
'FEDEX_1_DAY_FREIGHT',
'FEDEX_2_DAY_FREIGHT',
'FEDEX_3_DAY_FREIGHT',
'FEDEX_NATIONAL_FREIGHT',
)
),
'from_us' => array(
'method' => array(
'INTERNATIONAL_FIRST',
'INTERNATIONAL_ECONOMY',
'INTERNATIONAL_PRIORITY',
)
)
)
),
array(
'containers' => array('FEDEX_10KG_BOX', 'FEDEX_25KG_BOX'),
'filters' => array(
'within_us' => array(),
'from_us' => array('method' => array('INTERNATIONAL_PRIORITY'))
)
),
array(
'containers' => array('YOUR_PACKAGING'),
'filters' => array(
'within_us' => array(
'method' =>array(
'FEDEX_GROUND',
'GROUND_HOME_DELIVERY',
'SMART_POST',
'FEDEX_EXPRESS_SAVER',
'FEDEX_2_DAY',
'FEDEX_2_DAY_AM',
'STANDARD_OVERNIGHT',
'PRIORITY_OVERNIGHT',
'FIRST_OVERNIGHT',
'FEDEX_FREIGHT',
'FEDEX_1_DAY_FREIGHT',
'FEDEX_2_DAY_FREIGHT',
'FEDEX_3_DAY_FREIGHT',
'FEDEX_NATIONAL_FREIGHT',
)
),
'from_us' => array(
'method' =>array(
'INTERNATIONAL_FIRST',
'INTERNATIONAL_ECONOMY',
'INTERNATIONAL_PRIORITY',
'INTERNATIONAL_GROUND',
'FEDEX_FREIGHT',
'FEDEX_1_DAY_FREIGHT',
'FEDEX_2_DAY_FREIGHT',
'FEDEX_3_DAY_FREIGHT',
'FEDEX_NATIONAL_FREIGHT',
'INTERNATIONAL_ECONOMY_FREIGHT',
'INTERNATIONAL_PRIORITY_FREIGHT',
)
)
)
)
),
'delivery_confirmation_types' => array(
'NO_SIGNATURE_REQUIRED' => Mage::helper('usa')->__('Not Required'),
'ADULT' => Mage::helper('usa')->__('Adult'),
'DIRECT' => Mage::helper('usa')->__('Direct'),
'INDIRECT' => Mage::helper('usa')->__('Indirect'),
),
);
if (!isset($codes[$type])) {
return false;
} elseif ('' === $code) {
return $codes[$type];
}
if (!isset($codes[$type][$code])) {
return false;
} else {
return $codes[$type][$code];
}
}
/**
* Return FeDex currency ISO code by Magento Base Currency Code
*
* @return string 3-digit currency code
*/
public function getCurrencyCode ()
{
$codes = array(
'DOP' => 'RDD', // Dominican Peso
'XCD' => 'ECD', // Caribbean Dollars
'ARS' => 'ARN', // Argentina Peso
'SGD' => 'SID', // Singapore Dollars
'KRW' => 'WON', // South Korea Won
'JMD' => 'JAD', // Jamaican Dollars
'CHF' => 'SFR', // Swiss Francs
'JPY' => 'JYE', // Japanese Yen
'KWD' => 'KUD', // Kuwaiti Dinars
'GBP' => 'UKL', // British Pounds
'AED' => 'DHS', // UAE Dirhams
'MXN' => 'NMP', // Mexican Pesos
'UYU' => 'UYP', // Uruguay New Pesos
'CLP' => 'CHP', // Chilean Pesos
'TWD' => 'NTD', // New Taiwan Dollars
);
$currencyCode = Mage::app()->getStore()->getBaseCurrencyCode();
return isset($codes[$currencyCode]) ? $codes[$currencyCode] : $currencyCode;
}
/**
* Get tracking
*
* @param mixed $trackings
* @return mixed
*/
public function getTracking($trackings)
{
$this->setTrackingReqeust();
if (!is_array($trackings)) {
$trackings=array($trackings);
}
foreach($trackings as $tracking){
$this->_getXMLTracking($tracking);
}
return $this->_result;
}
/**
* Set tracking request
*
* @return void
*/
protected function setTrackingReqeust()
{
$r = new Varien_Object();
$account = $this->getConfigData('account');
$r->setAccount($account);
$this->_rawTrackingRequest = $r;
}
/**
* Send request for tracking
*
* @param array $tracking
* @return void
*/
protected function _getXMLTracking($tracking)
{
$trackRequest = array(
'WebAuthenticationDetail' => array(
'UserCredential' => array(
'Key' => $this->getConfigData('key'),
'Password' => $this->getConfigData('password')
)
),
'ClientDetail' => array(
'AccountNumber' => $this->getConfigData('account'),
'MeterNumber' => $this->getConfigData('meter_number')
),
'Version' => array(
'ServiceId' => 'trck',
'Major' => '5',
'Intermediate' => '0',
'Minor' => '0'
),
'PackageIdentifier' => array(
'Type' => 'TRACKING_NUMBER_OR_DOORTAG',
'Value' => $tracking,
),
/*
* 0 = summary data, one signle scan structure with the most recent scan
* 1 = multiple sacn activity for each package
*/
'IncludeDetailedScans' => 1,
);
$requestString = serialize($trackRequest);
$response = $this->_getCachedQuotes($requestString);
$debugData = array('request' => $trackRequest);
if ($response === null) {
try {
$client = $this->_createTrackSoapClient();
$response = $client->track($trackRequest);
$this->_setCachedQuotes($requestString, serialize($response));
$debugData['result'] = $response;
} catch (Exception $e) {
$debugData['result'] = array('error' => $e->getMessage(), 'code' => $e->getCode());
Mage::logException($e);
}
} else {
$response = unserialize($response);
$debugData['result'] = $response;
}
$this->_debug($debugData);
$this->_parseTrackingResponse($tracking, $response);
}
/**
* Parse tracking response
*
* @param array $trackingValue
* @param stdClass $response
*/
protected function _parseTrackingResponse($trackingValue, $response)
{
if (is_object($response)) {
if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
$errorTitle = (string)$response->Notifications->Message;
} elseif (isset($response->TrackDetails)) {
$trackInfo = $response->TrackDetails;
$resultArray['status'] = (string)$trackInfo->StatusDescription;
$resultArray['service'] = (string)$trackInfo->ServiceInfo;
$timestamp = isset($trackInfo->EstimatedDeliveryTimestamp) ?
$trackInfo->EstimatedDeliveryTimestamp : $trackInfo->ActualDeliveryTimestamp;
$timestamp = strtotime((string)$timestamp);
if ($timestamp) {
$resultArray['deliverydate'] = date('Y-m-d', $timestamp);
$resultArray['deliverytime'] = date('H:i:s', $timestamp);
}
$deliveryLocation = isset($trackInfo->EstimatedDeliveryAddress) ?
$trackInfo->EstimatedDeliveryAddress : $trackInfo->ActualDeliveryAddress;
$deliveryLocationArray = array();
if (isset($deliveryLocation->City)) {
$deliveryLocationArray[] = (string)$deliveryLocation->City;
}
if (isset($deliveryLocation->StateOrProvinceCode)) {
$deliveryLocationArray[] = (string)$deliveryLocation->StateOrProvinceCode;
}
if (isset($deliveryLocation->CountryCode)) {
$deliveryLocationArray[] = (string)$deliveryLocation->CountryCode;
}
if ($deliveryLocationArray) {
$resultArray['deliverylocation'] = implode(', ', $deliveryLocationArray);
}
$resultArray['signedby'] = (string)$trackInfo->DeliverySignatureName;
$resultArray['shippeddate'] = date('Y-m-d', (int)$trackInfo->ShipTimestamp);
if (isset($trackInfo->PackageWeight) && isset($trackInfo->Units)) {
$weight = (string)$trackInfo->PackageWeight;
$unit = (string)$trackInfo->Units;
$resultArray['weight'] = "{$weight} {$unit}";
}
$packageProgress = array();
if (isset($trackInfo->Events)) {
$events = $trackInfo->Events;
if (isset($events->Address)) {
$events = array($events);
}
foreach ($events as $event) {
$tempArray = array();
$tempArray['activity'] = (string)$event->EventDescription;
$timestamp = strtotime((string)$event->Timestamp);
if ($timestamp) {
$tempArray['deliverydate'] = date('Y-m-d', $timestamp);
$tempArray['deliverytime'] = date('H:i:s', $timestamp);
}
if (isset($event->Address)) {
$addressArray = array();
$address = $event->Address;
if (isset($address->City)) {
$addressArray[] = (string)$address->City;
}
if (isset($address->StateOrProvinceCode)) {
$addressArray[] = (string)$address->StateOrProvinceCode;
}
if (isset($address->CountryCode)) {
$addressArray[] = (string)$address->CountryCode;
}
if ($addressArray) {
$tempArray['deliverylocation'] = implode(', ', $addressArray);
}
}
$packageProgress[] = $tempArray;
}
}
$resultArray['progressdetail'] = $packageProgress;
}
}
if (!$this->_result) {
$this->_result = Mage::getModel('shipping/tracking_result');
}
if (isset($resultArray)) {
$tracking = Mage::getModel('shipping/tracking_result_status');
$tracking->setCarrier('fedex');
$tracking->setCarrierTitle($this->getConfigData('title'));
$tracking->setTracking($trackingValue);
$tracking->addData($resultArray);
$this->_result->append($tracking);
} else {
$error = Mage::getModel('shipping/tracking_result_error');
$error->setCarrier('fedex');
$error->setCarrierTitle($this->getConfigData('title'));
$error->setTracking($trackingValue);
$error->setErrorMessage($errorTitle ? $errorTitle : Mage::helper('usa')->__('Unable to retrieve tracking'));
$this->_result->append($error);
}
}
/**
* Parse xml tracking response
*
* @deprecated after 1.6.0.0 see _parseTrackingResponse()
* @param array $trackingvalue
* @param string $response
* @return void
*/
protected function _parseXmlTrackingResponse($trackingvalue, $response)
{
$resultArr=array();
if (strlen(trim($response))>0) {
if ($xml = $this->_parseXml($response)) {
if (is_object($xml->Error) && is_object($xml->Error->Message)) {
$errorTitle = (string)$xml->Error->Message;
} elseif (is_object($xml->SoftError) && is_object($xml->SoftError->Message)) {
$errorTitle = (string)$xml->SoftError->Message;
}
if (!isset($errorTitle)) {
$resultArr['status'] = (string)$xml->Package->StatusDescription;
$resultArr['service'] = (string)$xml->Package->Service;
$resultArr['deliverydate'] = (string)$xml->Package->DeliveredDate;
$resultArr['deliverytime'] = (string)$xml->Package->DeliveredTime;
$resultArr['deliverylocation'] = (string)$xml->TrackProfile->DeliveredLocationDescription;
$resultArr['signedby'] = (string)$xml->Package->SignedForBy;
$resultArr['shippeddate'] = (string)$xml->Package->ShipDate;
$weight = (string)$xml->Package->Weight;
$unit = (string)$xml->Package->WeightUnits;
$resultArr['weight'] = "{$weight} {$unit}";
$packageProgress = array();
if (isset($xml->Package->Event)) {
foreach ($xml->Package->Event as $event) {
$tempArr=array();
$tempArr['activity'] = (string)$event->Description;
$tempArr['deliverydate'] = (string)$event->Date;//YYYY-MM-DD
$tempArr['deliverytime'] = (string)$event->Time;//HH:MM:ss
$addArr=array();
if (isset($event->Address->City)) {
$addArr[] = (string)$event->Address->City;
}
if (isset($event->Address->StateProvinceCode)) {
$addArr[] = (string)$event->Address->StateProvinceCode;
}
if (isset($event->Address->CountryCode)) {
$addArr[] = (string)$event->Address->CountryCode;
}
if ($addArr) {
$tempArr['deliverylocation']=implode(', ',$addArr);
}
$packageProgress[] = $tempArr;
}
}
$resultArr['progressdetail'] = $packageProgress;
}
} else {
$errorTitle = 'Response is in the wrong format';
}
} else {
$errorTitle = false;
}
if (!$this->_result) {
$this->_result = Mage::getModel('shipping/tracking_result');
}
if ($resultArr) {
$tracking = Mage::getModel('shipping/tracking_result_status');
$tracking->setCarrier('fedex');
$tracking->setCarrierTitle($this->getConfigData('title'));
$tracking->setTracking($trackingvalue);
$tracking->addData($resultArr);
$this->_result->append($tracking);
} else {
$error = Mage::getModel('shipping/tracking_result_error');
$error->setCarrier('fedex');
$error->setCarrierTitle($this->getConfigData('title'));
$error->setTracking($trackingvalue);
$error->setErrorMessage($errorTitle ? $errorTitle : Mage::helper('usa')->__('Unable to retrieve tracking'));
$this->_result->append($error);
}
}
/**
* Get tracking response
*
* @return string
*/
public function getResponse()
{
$statuses = '';
if ($this->_result instanceof Mage_Shipping_Model_Tracking_Result) {
if ($trackings = $this->_result->getAllTrackings()) {
foreach ($trackings as $tracking){
if($data = $tracking->getAllData()){
if (!empty($data['status'])) {
$statuses .= Mage::helper('usa')->__($data['status']) . "\n
";
} else {
$statuses .= Mage::helper('usa')->__('Empty response') . "\n
";
}
}
}
}
}
if (empty($statuses)) {
$statuses = Mage::helper('usa')->__('Empty response');
}
return $statuses;
}
/**
* Get allowed shipping methods
*
* @return array
*/
public function getAllowedMethods()
{
$allowed = explode(',', $this->getConfigData('allowed_methods'));
$arr = array();
foreach ($allowed as $k) {
$arr[$k] = $this->getCode('method', $k);
}
return $arr;
}
/**
* Return array of authenticated information
*
* @return array
*/
protected function _getAuthDetails()
{
return array(
'WebAuthenticationDetail' => array(
'UserCredential' => array(
'Key' => $this->getConfigData('key'),
'Password' => $this->getConfigData('password')
)
),
'ClientDetail' => array(
'AccountNumber' => $this->getConfigData('account'),
'MeterNumber' => $this->getConfigData('meter_number')
),
'TransactionDetail' => array(
'CustomerTransactionId' => '*** Express Domestic Shipping Request v9 using PHP ***'
),
'Version' => array(
'ServiceId' => 'ship',
'Major' => '10',
'Intermediate' => '0',
'Minor' => '0'
)
);
}
/**
* Form array with appropriate structure for shipment request
*
* @param Varien_Object $request
* @return array
*/
protected function _formShipmentRequest(Varien_Object $request)
{
if ($request->getReferenceData()) {
$referenceData = $request->getReferenceData() . $request->getPackageId();
} else {
$referenceData = 'Order #'
. $request->getOrderShipment()->getOrder()->getIncrementId()
. ' P'
. $request->getPackageId();
}
$packageParams = $request->getPackageParams();
$customsValue = $packageParams->getCustomsValue();
$height = $packageParams->getHeight();
$width = $packageParams->getWidth();
$length = $packageParams->getLength();
$weightUnits = $packageParams->getWeightUnits() == Zend_Measure_Weight::POUND ? 'LB' : 'KG';
$dimensionsUnits = $packageParams->getDimensionUnits() == Zend_Measure_Length::INCH ? 'IN' : 'CM';
$unitPrice = 0;
$itemsQty = 0;
$itemsDesc = array();
$countriesOfManufacture = array();
$productIds = array();
$packageItems = $request->getPackageItems();
foreach ($packageItems as $itemShipment) {
$item = new Varien_Object();
$item->setData($itemShipment);
$unitPrice += $item->getPrice();
$itemsQty += $item->getQty();
$itemsDesc[] = $item->getName();
$productIds[] = $item->getProductId();
}
// get countries of manufacture
$productCollection = Mage::getResourceModel('catalog/product_collection')
->addStoreFilter($request->getStoreId())
->addFieldToFilter('entity_id', array('in' => $productIds))
->addAttributeToSelect('country_of_manufacture');
foreach ($productCollection as $product) {
$countriesOfManufacture[] = $product->getCountryOfManufacture();
}
$paymentType = $request->getIsReturn() ? 'RECIPIENT' : 'SENDER';
$requestClient = array(
'RequestedShipment' => array(
'ShipTimestamp' => time(),
'DropoffType' => $this->getConfigData('dropoff'),
'PackagingType' => $request->getPackagingType(),
'ServiceType' => $request->getShippingMethod(),
'Shipper' => array(
'Contact' => array(
'PersonName' => $request->getShipperContactPersonName(),
'CompanyName' => $request->getShipperContactCompanyName(),
'PhoneNumber' => $request->getShipperContactPhoneNumber()
),
'Address' => array(
'StreetLines' => array($request->getShipperAddressStreet()),
'City' => $request->getShipperAddressCity(),
'StateOrProvinceCode' => $request->getShipperAddressStateOrProvinceCode(),
'PostalCode' => $request->getShipperAddressPostalCode(),
'CountryCode' => $request->getShipperAddressCountryCode()
)
),
'Recipient' => array(
'Contact' => array(
'PersonName' => $request->getRecipientContactPersonName(),
'CompanyName' => $request->getRecipientContactCompanyName(),
'PhoneNumber' => $request->getRecipientContactPhoneNumber()
),
'Address' => array(
'StreetLines' => array($request->getRecipientAddressStreet()),
'City' => $request->getRecipientAddressCity(),
'StateOrProvinceCode' => $request->getRecipientAddressStateOrProvinceCode(),
'PostalCode' => $request->getRecipientAddressPostalCode(),
'CountryCode' => $request->getRecipientAddressCountryCode(),
'Residential' => (bool)$this->getConfigData('residence_delivery')
),
),
'ShippingChargesPayment' => array(
'PaymentType' => $paymentType,
'Payor' => array(
'AccountNumber' => $this->getConfigData('account'),
'CountryCode' => Mage::getStoreConfig(
Mage_Shipping_Model_Shipping::XML_PATH_STORE_COUNTRY_ID,
$request->getStoreId()
)
)
),
'LabelSpecification' =>array(
'LabelFormatType' => 'COMMON2D',
'ImageType' => 'PNG',
'LabelStockType' => 'PAPER_8.5X11_TOP_HALF_LABEL',
),
'RateRequestTypes' => array('ACCOUNT'),
'PackageCount' => 1,
'RequestedPackageLineItems' => array(
'SequenceNumber' => '1',
'Weight' => array(
'Units' => $weightUnits,
'Value' => $request->getPackageWeight()
),
'CustomerReferences' => array(
'CustomerReferenceType' => 'CUSTOMER_REFERENCE',
'Value' => $referenceData
),
'SpecialServicesRequested' => array(
'SpecialServiceTypes' => 'SIGNATURE_OPTION',
'SignatureOptionDetail' => array('OptionType' => $packageParams->getDeliveryConfirmation())
),
)
)
);
// for international shipping
if ($request->getShipperAddressCountryCode() != $request->getRecipientAddressCountryCode()) {
$requestClient['RequestedShipment']['CustomsClearanceDetail'] =
array(
'CustomsValue' =>
array(
'Currency' => $request->getBaseCurrencyCode(),
'Amount' => $customsValue,
),
'DutiesPayment' => array(
'PaymentType' => $paymentType,
'Payor' => array(
'AccountNumber' => $this->getConfigData('account'),
'CountryCode' => Mage::getStoreConfig(
Mage_Shipping_Model_Shipping::XML_PATH_STORE_COUNTRY_ID,
$request->getStoreId()
)
)
),
'Commodities' => array(
'Weight' => array(
'Units' => $weightUnits,
'Value' => $request->getPackageWeight()
),
'NumberOfPieces' => 1,
'CountryOfManufacture' => implode(',', array_unique($countriesOfManufacture)),
'Description' => implode(', ', $itemsDesc),
'Quantity' => ceil($itemsQty),
'QuantityUnits' => 'pcs',
'UnitPrice' => array(
'Currency' => $request->getBaseCurrencyCode(),
'Amount' => $unitPrice
),
'CustomsValue' => array(
'Currency' => $request->getBaseCurrencyCode(),
'Amount' => $customsValue
),
)
);
}
if ($request->getMasterTrackingId()) {
$requestClient['RequestedShipment']['MasterTrackingId'] = $request->getMasterTrackingId();
}
// set dimensions
if ($length || $width || $height) {
$requestClient['RequestedShipment']['RequestedPackageLineItems']['Dimensions'] = array();
$dimenssions = &$requestClient['RequestedShipment']['RequestedPackageLineItems']['Dimensions'];
$dimenssions['Length'] = $length;
$dimenssions['Width'] = $width;
$dimenssions['Height'] = $height;
$dimenssions['Units'] = $dimensionsUnits;
}
return $this->_getAuthDetails() + $requestClient;
}
/**
* Do shipment request to carrier web service, obtain Print Shipping Labels and process errors in response
*
* @param Varien_Object $request
* @return Varien_Object
*/
protected function _doShipmentRequest(Varien_Object $request)
{
$this->_prepareShipmentRequest($request);
$result = new Varien_Object();
$client = $this->_createShipSoapClient();
$requestClient = $this->_formShipmentRequest($request);
$response = $client->processShipment($requestClient);
if ($response->HighestSeverity != 'FAILURE' && $response->HighestSeverity != 'ERROR') {
$shippingLabelContent = $response->CompletedShipmentDetail->CompletedPackageDetails->Label->Parts->Image;
$trackingNumber = $response->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds->TrackingNumber;
$result->setShippingLabelContent($shippingLabelContent);
$result->setTrackingNumber($trackingNumber);
$debugData = array('request' => $client->__getLastRequest(), 'result' => $client->__getLastResponse());
$this->_debug($debugData);
} else {
$debugData = array(
'request' => $client->__getLastRequest(),
'result' => array(
'error' => '',
'code' => '',
'xml' => $client->__getLastResponse()
)
);
if (is_array($response->Notifications)) {
foreach ($response->Notifications as $notification) {
$debugData['result']['code'] .= $notification->Code . '; ';
$debugData['result']['error'] .= $notification->Message . '; ';
}
} else {
$debugData['result']['code'] = $response->Notifications->Code . ' ';
$debugData['result']['error'] = $response->Notifications->Message . ' ';
}
$this->_debug($debugData);
$result->setErrors($debugData['result']['error']);
}
$result->setGatewayResponse($client->__getLastResponse());
return $result;
}
/**
* For multi package shipments. Delete requested shipments if the current shipment
* request is failed
*
* @param array $data
* @return bool
*/
public function rollBack($data)
{
$requestData = $this->_getAuthDetails();
$requestData['DeletionControl'] = 'DELETE_ONE_PACKAGE';
foreach ($data as &$item) {
$requestData['TrackingId'] = $item['tracking_number'];
$client = $this->_createShipSoapClient();
$client->deleteShipment($requestData);
}
return true;
}
/**
* Return container types of carrier
*
* @param Varien_Object|null $params
* @return array|bool
*/
public function getContainerTypes(Varien_Object $params = null)
{
if ($params == null) {
return $this->_getAllowedContainers($params);
}
$method = $params->getMethod();
$countryShipper = $params->getCountryShipper();
$countryRecipient = $params->getCountryRecipient();
if (($countryShipper == self::USA_COUNTRY_ID && $countryRecipient == self::CANADA_COUNTRY_ID
|| $countryShipper == self::CANADA_COUNTRY_ID && $countryRecipient == self::USA_COUNTRY_ID)
&& $method == 'FEDEX_GROUND'
) {
return array('YOUR_PACKAGING' => Mage::helper('usa')->__('Your Packaging'));
} else if ($method == 'INTERNATIONAL_ECONOMY' || $method == 'INTERNATIONAL_FIRST') {
$allTypes = $this->getContainerTypesAll();
$exclude = array('FEDEX_10KG_BOX' => '', 'FEDEX_25KG_BOX' => '');
return array_diff_key($allTypes, $exclude);
} else if ($method == 'EUROPE_FIRST_INTERNATIONAL_PRIORITY') {
$allTypes = $this->getContainerTypesAll();
$exclude = array('FEDEX_BOX' => '', 'FEDEX_TUBE' => '');
return array_diff_key($allTypes, $exclude);
} else if ($countryShipper == self::CANADA_COUNTRY_ID && $countryRecipient == self::CANADA_COUNTRY_ID) {
// hack for Canada domestic. Apply the same filter rules as for US domestic
$params->setCountryShipper(self::USA_COUNTRY_ID);
$params->setCountryRecipient(self::USA_COUNTRY_ID);
}
return $this->_getAllowedContainers($params);
}
/**
* Return all container types of carrier
*
* @return array|bool
*/
public function getContainerTypesAll()
{
return $this->getCode('packaging');
}
/**
* Return structured data of containers witch related with shipping methods
*
* @return array|bool
*/
public function getContainerTypesFilter()
{
return $this->getCode('containers_filter');
}
/**
* Return delivery confirmation types of carrier
*
* @param Varien_Object|null $params
* @return array
*/
public function getDeliveryConfirmationTypes(Varien_Object $params = null)
{
return $this->getCode('delivery_confirmation_types');
}
}