You are here

uc_fedex.module in FedEx Shipping 6

FedEx Web Services Rate / Available Services Quote

Shipping quote module that interfaces with the FedEx Web Services API to get rates for small package shipments. Implements a SOAP Web Service client.

@author Tim Rohaly. <http://drupal.org/user/202830>

File

uc_fedex.module
View source
<?php

/**
 * @file
 * FedEx Web Services Rate / Available Services Quote
 *
 * Shipping quote module that interfaces with the FedEx Web Services API
 * to get rates for small package shipments.  Implements a SOAP Web Service
 * client.
 *
 * @author Tim Rohaly.    <http://drupal.org/user/202830>
 */

/* Set to 0 to disable caching of SOAP WSDL when developing your WSDL */
ini_set("soap.wsdl_cache_enabled", "1");

/******************************************************************************
 * Drupal Hooks                                                               *
 ******************************************************************************/

/**
 * Implementation of hook_menu().
 * Called when Drupal is building menus.  Cache parameter lets module know
 * if Drupal intends to cache menu or not - different results may be
 * returned for either case.
 *
 * @return
 *   An array with the menu path, callback, and parameters.
 */
function uc_fedex_menu() {
  $items = array();
  $items['admin/store/settings/quotes/methods/fedex'] = array(
    'title' => 'FedEx',
    'access arguments' => array(
      'configure quotes',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'uc_fedex_admin_settings',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_init().
 *
 * Used to load module CSS.  This is the wrong place to do it, because
 * the CSS will be included on every page, not just on the pages where
 * it is needed.  However, this is currently the Ubercart-recommended
 * place for adding shipping quotes CSS.
 */
function uc_fedex_init() {
  drupal_add_css(drupal_get_path('module', 'uc_fedex') . '/uc_fedex.css');
}

/**
 * Implementation of hook_theme() used to declare module theme functions.
 */
function uc_fedex_theme() {
  return array(
    'uc_fedex_option_label' => array(
      'file' => 'uc_fedex.module',
      'arguments' => array(
        'product' => NULL,
        'packages' => NULL,
      ),
    ),
  );
}

/******************************************************************************
 * Conditional Actions Hooks                                                  *
 ******************************************************************************/

/**
 * Implementation of hook_ca_predicate().
 *
 * Connect the FedEx quote Action and Event.
 */
function uc_fedex_ca_predicate() {
  $enabled = variable_get('uc_quote_enabled', array());
  $predicates = array(
    'uc_fedex_get_quote' => array(
      '#title' => t('Shipping quote from FedEx'),
      '#trigger' => 'get_quote_from_fedex',
      '#class' => 'uc_fedex',
      '#status' => $enabled['fedex'],
      '#actions' => array(
        array(
          '#name' => 'uc_quote_action_get_quote',
          '#title' => t('Fetch a shipping quote'),
          '#argument_map' => array(
            'order' => 'order',
            'method' => 'method',
          ),
        ),
      ),
    ),
  );
  return $predicates;
}

/******************************************************************************
 * Ubercart Hooks                                                             *
 ******************************************************************************/

/**
 * Implementation of Ubercart's hook_shipping_type().
 *
 * @return
 *   Array of package types for FedEx shipping method
 */
function uc_fedex_shipping_type() {
  $weight = variable_get('uc_quote_type_weight', array(
    'small_package' => 0,
  ));
  $types = array(
    'small_package' => array(
      'id' => 'small_package',
      'title' => t('Small Package'),
      'weight' => $weight['small_package'],
    ),
  );
  return $types;
}

/**
 * Implementation of Ubercart's hook_shipping_method().
 *
 * @return
 *   Array of FedEx shipping services
 */
function uc_fedex_shipping_method() {
  $enabled = variable_get('uc_quote_enabled', array(
    'fedex' => TRUE,
  ));
  $weight = variable_get('uc_quote_method_weight', array(
    'fedex' => 0,
  ));
  $methods = array(
    'fedex' => array(
      'id' => 'fedex',
      'title' => t('FedEx'),
      'quote' => array(
        'type' => 'small_package',
        'callback' => 'uc_fedex_quote',
        'accessorials' => _uc_fedex_services(),
      ),
      'enabled' => $enabled['fedex'],
      'weight' => $weight['fedex'],
      'module' => 'uc_fedex',
    ),
  );
  return $methods;
}

/**
 * Implementation of Ubercart's hook_store_status().
 *
 * Lets the administrator know if the FedEx account information has not been
 * filled out.
 *
 * @return
 *   Array of error or status messages relating to configuration of FedEx module
 */
function uc_fedex_store_status() {
  $messages = array();
  $key = variable_get('uc_fedex_user_credential_key', 0);
  $password = variable_get('uc_fedex_user_credential_password', 0);
  $account = variable_get('uc_fedex_account_number', 0);
  $meter = variable_get('uc_fedex_meter_number', 0);
  if ($key && $password && $account && $meter) {
    $messages[] = array(
      'status' => 'ok',
      'title' => t('FedEx Ship Manager'),
      'desc' => t('Information needed to access FedEx Ship Manager has been entered.'),
    );
  }
  else {
    $messages[] = array(
      'status' => 'error',
      'title' => t('FedEx Ship Manager'),
      'desc' => t('More information is needed to access FedEx Ship Manager. Please enter it !link.', array(
        '!link' => l('here', 'admin/store/settings/quotes/methods/fedex'),
      )),
    );
  }
  return $messages;
}

/******************************************************************************
 * Menu Callbacks                                                             *
 ******************************************************************************/

/**
 * Default FedEx Web Services API settings.
 *
 * Records FedEx account information neccessary to use service. Allows testing
 * or production mode. Configures which FedEx services are quoted to customers.
 *
 * @return
 *   Forms for store administrator to set configuration options.
 */
function uc_fedex_admin_settings() {

  /* Container for credentials forms */
  $form['uc_fedex_credentials'] = array(
    '#type' => 'fieldset',
    '#title' => t('Credentials'),
    '#description' => t('Account number and authorization information'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );

  /* Form to set the developer key */
  $form['uc_fedex_credentials']['uc_fedex_user_credential_key'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Web Services API User Key'),
    '#default_value' => variable_get('uc_fedex_user_credential_key', ''),
    '#required' => TRUE,
  );

  //
  // This form will be changed to 'password' field, eventually
  // But it takes too much trouble to re-enter a long password
  // every time I change a form option during development and testing.
  //

  /* Form to set the developer password */
  $form['uc_fedex_credentials']['uc_fedex_user_credential_password'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Web Services API Password'),
    '#default_value' => variable_get('uc_fedex_user_credential_password', ''),
    '#required' => TRUE,
  );

  /* Form to set user account number */
  $form['uc_fedex_credentials']['uc_fedex_account_number'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Account #'),
    '#default_value' => variable_get('uc_fedex_account_number', 0),
    '#required' => TRUE,
  );

  /* Form to set user meter number */
  $form['uc_fedex_credentials']['uc_fedex_meter_number'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Meter #'),
    '#default_value' => variable_get('uc_fedex_meter_number', 0),
    '#required' => TRUE,
  );

  /*
   * Form to set choose between Production and Testing server
   * ***Defaults to Testing!***
   */
  $form['uc_fedex_credentials']['uc_fedex_server_role'] = array(
    '#type' => 'select',
    '#title' => t('FedEx Server Role'),
    '#description' => t('Quotes and shipments requested in Testing mode will not be picked up or charged to your account.'),
    '#options' => array(
      'testing' => t('Testing'),
      'production' => t('Production'),
    ),
    '#default_value' => variable_get('uc_fedex_server_role', 'testing'),
  );

  /*
   * Form to set choose between LIST quotes and ACCOUNT quotes
   * ***Defaults to LIST!***
   */
  $form['uc_fedex_quote_type'] = array(
    '#type' => 'select',
    '#title' => t('FedEx Quote Type'),
    '#description' => t('Choose to present the customer with FedEx list prices or your discounted FedEx account prices. LIST prices only exist for US shipments - if you specify LIST for international shipments you will not receive any quotes.  Note that ACCOUNT prices are accurate only on the PRODUCTION server!'),
    '#options' => array(
      'list' => t('List Prices'),
      'account' => t('Discount Account Prices'),
    ),
    '#default_value' => variable_get('uc_fedex_quote_type', 'list'),
  );

  /* Form to restrict FedEx services available to customer */
  $form['uc_fedex_services'] = array(
    '#type' => 'checkboxes',
    '#title' => t('FedEx Services'),
    '#default_value' => variable_get('uc_fedex_services', _uc_fedex_services()),
    '#options' => _uc_fedex_services(),
    '#description' => t('Select the FedEx services customers are allowed to use.'),
  );

  /* Form to set how the package is handed over to FedEx  */
  $form['uc_fedex_dropoff_type'] = array(
    '#type' => 'select',
    '#title' => t('FedEx Pickup/Dropoff Options'),
    '#default_value' => variable_get('uc_fedex_dropoff_type', _uc_fedex_dropoff_types()),
    '#options' => _uc_fedex_dropoff_types(),
    '#description' => t('Pickup/Dropoff options.  It is assumed that all your packages are using the same method.'),
  );

  /* Form to select FedEx packaging to use */
  $form['uc_fedex_package_type'] = array(
    '#type' => 'select',
    '#title' => t('FedEx Package Type'),
    '#default_value' => variable_get('uc_fedex_package_type', _uc_fedex_package_types()),
    '#options' => _uc_fedex_package_types(),
    '#description' => t('Package Type.  It is assumed that all your packages are using the same packaging.'),
  );

  /* Form to select Residential/Commercial destination address */
  $form['uc_fedex_residential_quotes'] = array(
    '#type' => 'radios',
    '#title' => t('Quote rates assuming destination is a'),
    '#default_value' => variable_get('uc_fedex_residential_quotes', 1),
    '#options' => array(
      0 => t('Commercial address'),
      1 => t('Residential address (extra fees)'),
    ),
  );

  /* Container for markup forms */
  $form['uc_fedex_markups'] = array(
    '#type' => 'fieldset',
    '#title' => t('Markups'),
    '#description' => t('Modifiers to the shipping weight and quoted rate'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );

  /* Form to select type of rate markup */
  $form['uc_fedex_markups']['uc_fedex_rate_markup_type'] = array(
    '#type' => 'select',
    '#title' => t('Rate Markup Type'),
    '#default_value' => variable_get('uc_fedex_rate_markup_type', 'percentage'),
    '#options' => array(
      'percentage' => t('Percentage (%)'),
      'multiplier' => t('Multiplier (×)'),
      'currency' => t('Addition (!currency)', array(
        '!currency' => variable_get('uc_currency_sign', '$'),
      )),
    ),
  );

  /* Form to select type of rate amount */
  $form['uc_fedex_markups']['uc_fedex_rate_markup'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Shipping Rate Markup'),
    '#default_value' => variable_get('uc_fedex_rate_markup', '0'),
    '#description' => t('Markup FedEx shipping rate quote by dollar amount, percentage, or multiplier.'),
  );

  /* Form to select type of weight markup */
  $form['uc_fedex_markups']['uc_fedex_weight_markup_type'] = array(
    '#type' => 'select',
    '#title' => t('Weight Markup Type'),
    '#default_value' => variable_get('uc_fedex_weight_markup_type', 'percentage'),
    '#options' => array(
      'percentage' => t('Percentage (%)'),
      'multiplier' => t('Multiplier (×)'),
      'mass' => t('Addition (!mass)', array(
        '!mass' => '#',
      )),
    ),
  );

  /* Form to select type of weight markup amount */
  $form['uc_fedex_markups']['uc_fedex_weight_markup'] = array(
    '#type' => 'textfield',
    '#title' => t('FedEx Shipping Weight Markup'),
    '#default_value' => variable_get('uc_fedex_weight_markup', '0'),
    '#description' => t('Markup FedEx shipping weight before quote by weight amount, percentage, or multiplier.'),
  );

  /* Form to select packaging type */
  $form['uc_fedex_all_in_one'] = array(
    '#type' => 'radios',
    '#title' => t('Number of Packages'),
    '#default_value' => variable_get('uc_fedex_all_in_one', 1),
    '#options' => array(
      0 => t('Each product in its own package'),
      1 => t('All products in one package'),
    ),
    '#description' => t('Indicate whether each product is quoted as shipping separately or all in one package.'),
  );
  return system_settings_form($form);
}

/**
 * Validation handler for uc_fedex_admin_settings form.
 *
 * Require password only if it hasn't been set.
 *
 * @param form_id
 *   Form identifier
 * @param form_value
 *   Values entered into form
 * @param form
 *   Form itself
 */
function uc_fedex_admin_settings_validate($form, &$form_state) {
  $old_password = variable_get('uc_fedex_user_credential_password', '');
  if (!$form_state['values']['uc_fedex_user_credential_password']) {
    if ($old_password) {
      form_set_value($form['uc_fedex_user_credential_password'], $old_password, $form_state);
    }
    else {
      form_set_error('uc_fedex_user_credential_password', t('Password field is required.'));
    }
  }
}

/******************************************************************************
 * Module Functions                                                           *
 ******************************************************************************/

/**
 * Callback for retrieving a FedEx shipping quote.
 *
 * Requests a quote of all available FedEx services.  Quote returned
 * from the FedEx server is parsed and only the selected services are
 * presented to the user.
 *
 * @param $products
 *   Array of cart contents.
 * @param $details
 *   Order details other than product information.
 *
 * @return
 *   JSON object containing rate, error, and debugging information.
 */
function uc_fedex_quote($products, $details) {

  // For now, everything is put in one package, so there is
  // only one element in the $packages array.

  /* Assign products to one or more packages for quoting */
  $packages = _uc_fedex_package_products($products);

  /* Create and fill object with info needed about origin */
  $origin = variable_get('uc_quote_store_default_address', new stdClass());
  $country = db_query("SELECT * FROM {uc_countries} WHERE country_id = %d", $origin->country);
  $country_data = db_fetch_object($country);
  $origin->country_iso_code_2 = $country_data->country_iso_code_2;

  /* Create and fill object with info needed about destination */
  $destination = (object) $details;
  if ($origin->country == $destination->country) {

    // Try to save a DB query
    $destination->country_iso_code_2 = $origin->country_iso_code_2;
  }
  else {
    $country = db_query("SELECT * FROM {uc_countries} WHERE country_id = %d", $destination->country);
    $country_data = db_fetch_object($country);
    $destination->country_iso_code_2 = $country_data->country_iso_code_2;
  }

  /* Load preference for Residence/Commercial destination address */
  if (variable_get('uc_fedex_residential_quotes', 1)) {
    $destination->residence = TRUE;
  }
  else {
    $destination->residence = FALSE;
  }

  /* Call the method that does the actual SOAP request to the FedEx Server */

  /* Response contains all available services and rates                    */
  $response = uc_fedex_rate_request($packages, $origin, $destination);

  /* Construct an array containing only those services that the   */

  /* store admin has allowed in admin/store/settings/quotes/edit  */
  $fedex_services = array_filter(variable_get('uc_fedex_services', _uc_fedex_services()));
  $quotes = array();
  $method = uc_fedex_shipping_method();
  if (!isset($response->Options)) {

    // Memphis, we have a problem ...
    // Error returned from FedEx server - will print in $message box
    // Don't even try to extract a quote from the response, just return
    // empty quote array.
    return array();
  }

  /* Test responses to see if we are interested in that service */
  foreach ($response->Options as $options) {
    $service = $options->ServiceDetail->ServiceType;
    if (in_array($service, $fedex_services)) {

      // Check to see if we're quoting ACCOUNT or LIST rates
      if (variable_get('uc_fedex_quote_type', 'list') == 'list') {

        // LIST rate
        // LIST quotes return both ACCOUNT rates (in RatedShipmentDetails[0])
        // and LIST rates (in RatedShipmentDetails[1])
        $ratedetail = $options->RatedShipmentDetails[1];
      }
      else {

        // ACCOUNT rate
        // ACCOUNT quotes may return either ACCOUNT rates only OR
        // ACCOUNT rates and LIST rates.  Check.
        if (is_array($options->RatedShipmentDetails)) {
          $ratedetail = $options->RatedShipmentDetails[0];
        }
        else {
          $ratedetail = $options->RatedShipmentDetails;
        }
      }

      // need to handle dimensional rates, other modifiers.
      // Markup rate before customer sees it
      $rate = uc_fedex_rate_markup($ratedetail->ShipmentRateDetail->TotalNetCharge->Amount);
      $quotes[$service] = array(
        'rate' => $rate,
        'format' => uc_currency_format($rate),
        'option_label' => theme('uc_fedex_option_label', $method['fedex']['quote']['accessorials'][$service], $packages),
      );
    }
  }
  if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) {

    //  $quotes['data']['debug'] = htmlentities($response).'<br />';
  }

  // Sort rate quotes in order of increasing price
  uasort($quotes, 'uc_quote_price_sort');
  return $quotes;
}

/**
 * Constructs and executes a SOAP RateAvailabilityService request.
 * Obtains Rate and Available Services information needed for
 * shipping quote.
 *
 * SOAP call parameters are set in the order they appear in the WSDL file
 * Associative array of DOM returned.
 *
 * @param $packages
 *   Array of packages received from the cart.
 * @param $origin
 *   Delivery origin address information.
 * @param $destination
 *   Delivery destination address information.
 *
 * @return
 *   Associative array mirroring contents of SOAP object returned from server.
 */
function uc_fedex_rate_request($packages, $origin, $destination) {

  /* Set up SOAP call. */

  /*  Allow tracing so details of request can be retrieved for error logging */
  $client = new SoapClient(drupal_get_path('module', 'uc_fedex') . '/wsdl-' . variable_get('uc_fedex_server_role', 'testing') . '/RateAvailableServicesService_v2.wsdl', array(
    'trace' => 1,
  ));

  /* FedEx user key and password filled in by user on admin form */
  $request['WebAuthenticationDetail'] = array(
    'UserCredential' => array(
      'Key' => variable_get('uc_fedex_user_credential_key', 0),
      'Password' => variable_get('uc_fedex_user_credential_password', 0),
    ),
  );

  /* FedEx account and meter number filled in by user on admin form */
  $request['ClientDetail'] = array(
    'AccountNumber' => variable_get('uc_fedex_account_number', 0),
    'MeterNumber' => variable_get('uc_fedex_meter_number', 0),
  );

  /* Optional parameter, contains anything - let admin configure */
  $request['TransactionDetail'] = array(
    'CustomerTransactionId' => '*** Rate/Available Services Request v2 from Ubercart ***',
  );

  /* Rate Available Services Request v2.0.0 */
  $request['Version'] = array(
    'ServiceId' => 'crss',
    // crs for rate, crss for rate&services
    'Major' => '2',
    'Intermediate' => '0',
    'Minor' => '0',
  );

  /* Grab details of package origin - assumed to be store location */
  $request['Origin'] = array(
    'PostalCode' => $origin->postal_code,
    'CountryCode' => $origin->country_iso_code_2,
  );

  /* Grab details of package destination */
  $request['Destination'] = array(
    'PostalCode' => $destination->postal_code,
    'CountryCode' => $destination->country_iso_code_2,
    'Residential' => $destination->residence,
  );

  /* Currency for quote */
  $request['CurrencyType'] = variable_get('uc_currency_code', 'USD');

  /* Set Pickup/Dropoff type */
  $request['DropoffType'] = variable_get('uc_fedex_dropoff_type', 'REGULAR_PICKUP');

  // These next two aren't needed right now because quotes are being requested
  // for all avaiable services, then filtered before presenting the customer
  // with options.

  /* If CarrierCode is left out, all available services will be quoted */

  //$request['CarrierCode'] = 'FDXE';

  /* Service type - valid codes STANDARD_OVERNIGHT, FEDEX_GROUND, ... */

  //$request['ServiceType'] = 'STANDARD_OVERNIGHT';

  //
  // Packaging type - need this to be settable for each package rather
  // than one site-wide setting?
  //
  $request['PackagingType'] = variable_get('uc_fedex_package_type', 'YOUR_PACKAGING');

  /* When the package is going to ship */

  // have to think about this -
  // cutoff times, commits store owner to exact ship date, etc.
  // Probably have to make an admin menu option with cutoff time, after
  // which ShipDate becomes "tomorrow" unless of course tomorrow is a
  // weekend when you're closed...  But this shouldn't affect the rate
  $request['ShipDate'] = date('Y-m-d');

  // Note that ACCOUNT rates *require* a valid account number
  // and return accurate answers on the production server
  $request['RateRequestTypes'] = strtoupper(variable_get('uc_fedex_quote_type', 'list'));

  // Need to allow admin to configure special services
  $request['SpecialServicesRequested'] = array(
    'SpecialServiceTypes' => 'WEEKDAY_DELIVERY',
  );

  // Need to iterate over $packages to account for multi-package
  // shipments.  Right now everything is assumed to be one package.
  $PassRateRequestPackageSummary = TRUE;

  // Passing multi piece shipment rate request (by setting PieceCount > 1)
  if ($PassRateRequestPackageSummary) {
    $request['RateRequestPackageSummary'] = array(
      'PieceCount' => 1,
      'TotalWeight' => array(
        'Value' => $packages[0]->shipweight,
        'Units' => 'LB',
      ),
      'PerPieceDimensions' => array(
        'Length' => '1',
        'Width' => '1',
        'Height' => '1',
        'Units' => 'IN',
      ),
    );
  }
  else {

    // Passing single piece shipment rate request
    // currently only one occurrence of RequestedPackage is supported
    $request['PackageCount'] = 1;
    $request['Packages'] = array(
      0 => array(
        'Weight' => array(
          'Value' => 10.0,
          'Units' => 'LB',
        ),
        'InsuredValue' => array(
          'Amount' => 100,
          'Currency' => 'USD',
        ),
        'Dimensions' => array(
          'Length' => '1',
          'Width' => '1',
          'Height' => '1',
          'Units' => 'IN',
        ),
        'SpecialServicesRequested' => array(
          'SpecialServiceTypes' => 'WEEKDAY_DELIVERY',
        ),
      ),
    );
  }

  //
  // Send the SOAP request to the FedEx server
  //
  try {
    $response = $client
      ->__soapCall("rateAvailableServices", array(
      'parameters' => $request,
    ));
    if ($response->HighestSeverity != 'FAILURE' && $response->HighestSeverity != 'ERROR') {
      print_request_response($client);
    }
    else {
      drupal_set_message('Error in processing FedEx Shipping Quote transaction.', 'error');
      foreach ($response->Notifications as $notification) {
        if (is_array($response->Notifications)) {
          drupal_set_message($notification->Severity . ': ' . $notification->Message, 'error');
        }
        else {
          drupal_set_message($notification, 'error');
        }
      }
    }
    return $response;
  } catch (SoapFault $exception) {
    drupal_set_message('<h2>Fault</h2><br /><b>Code:</b>' . $exception->faultcode . '<br /><b>String:</b>' . $exception->faultstring . '<br />', 'error');

    // what else needs to be done here if FedEx quote fails?  What to display
    // to customer?
  }
}

/**
 * Constructs and executes a SOAP TrackService request.
 * Returns Tracking information.
 *
 * SOAP call parameters are set in the order they appear in the WSDL file
 * Associative array of DOM returned.
 *
 * @param $tracking_number
 *   FedEx Tracking number
 *
 * @return
 *   Associative array mirroring contents of SOAP object returned from server.
 */
function uc_fedex_tracking_request($tracking_number) {

  /* Set up SOAP call. */

  /* Allow tracing so details of request can be retrieved for error logging */
  $client = new SoapClient(drupal_get_path('module', 'uc_fedex') . '/wsdl-' . variable_get('uc_fedex_server_role', 'testing') . '/TrackService_v3.wsdl', array(
    'trace' => 1,
  ));

  /* FedEx user key and password filled in by user on admin form */
  $request['WebAuthenticationDetail'] = array(
    'UserCredential' => array(
      'Key' => variable_get('uc_fedex_user_credential_key', 0),
      'Password' => variable_get('uc_fedex_user_credential_password', 0),
    ),
  );

  /* FedEx account and meter number filled in by user on admin form */
  $request['ClientDetail'] = array(
    'AccountNumber' => variable_get('uc_fedex_account_number', 0),
    'MeterNumber' => variable_get('uc_fedex_meter_number', 0),
  );

  /* Optional parameter, contains anything - let admin configure */
  $request['TransactionDetail'] = array(
    'CustomerTransactionId' => '*** Track Service Request v3 from Ubercart ***',
  );

  /* Track Request v3.0.0 */
  $request['Version'] = array(
    'ServiceId' => 'trck',
    // crs for rate, crss for rate&services
    'Major' => '3',
    'Intermediate' => '0',
    'Minor' => '0',
  );

  /* Tracking Numbeer */
  $request['PackageIdentifier'] = array(
    'Value' => $tracking_number,
    'Type' => 'TRACKING_NUMBER_OR_DOORTAG',
  );

  /* Include Details - 1 is TRUE */
  $request['IncludeDetailedScans'] = 1;

  //
  // Send the SOAP request to the FedEx server
  //
  try {
    $response = $client
      ->__soapCall("track", array(
      'parameters' => $request,
    ));
    if ($response->HighestSeverity != 'FAILURE' && $response->HighestSeverity != 'ERROR') {
      print_request_response($client);

      /*
            // Just an Example of how to use this information.
            // You would typically do the following in whatever routine
            // called this function, not here.
            $reply = $response->TrackDetails;

            print('Tracking Number = ' . $reply->TrackingNumber . "<br />");
            print('                  ' . $reply->StatusDescription);
            print('  by ' . $reply->ServiceInfo);
            print('  weight ' . $reply->PackageWeight->Value. ' ' . $reply->PackageWeight->Units . "<br />");
            print('Estimated Delivery ' . $reply->EstimatedDeliveryTimestamp . "<br />");

            foreach($reply->Events as $event) {
              print('    Event = ' . $event->EventDescription . " " . $event->Address->City . ", " . $event->Address->StateOrProvinceCode . " on " . $event->Timestamp. "<br />");
            }
      */
    }
    else {
      drupal_set_message('Error in processing FedEx tracking transaction.', 'error');
      foreach ($response->Notifications as $notification) {
        if (is_array($response->Notifications)) {
          drupal_set_message($notification->Severity . ': ' . $notification->Message, 'error');
        }
        else {
          drupal_set_message($notification, 'error');
        }
      }
    }
    return $response;
  } catch (SoapFault $exception) {
    drupal_set_message('<h2>Fault</h2><br /><b>Code:</b>' . $exception->faultcode . '<br /><b>String:</b>' . $exception->faultstring . '<br />', 'error');

    // what else needs to be done here if FedEx quote fails?  What to display
    // to customer?
  }
}

/**
 * Modify the rate received from FedEx before displaying to the customer.
 *
 * @param rate
 *   Shipping rate without any rate markup
 *
 * @return
 *   Shipping rate after markup
 */
function uc_fedex_rate_markup($rate) {
  $markup = trim(variable_get('uc_fedex_rate_markup', '0'));
  $type = variable_get('uc_fedex_rate_markup_type', 'percentage');
  if (is_numeric($markup)) {
    switch ($type) {
      case 'percentage':
        return $rate + $rate * floatval($markup) / 100;
      case 'multiplier':
        return $rate * floatval($markup);
      case 'currency':
        return $rate + floatval($markup);
    }
  }
  else {
    return $rate;
  }
}

/**
 * Modify the weight of shipment before sending to FedEx for a quote.
 *
 * @param weight
 *   Shipping weight without any weight markup
 *
 * @return
 *   Shipping weight after markup
 */
function uc_fedex_weight_markup($weight) {
  $markup = trim(variable_get('uc_fedex_weight_markup', '0'));
  $type = variable_get('uc_fedex_weight_markup_type', 'percentage');
  if (is_numeric($markup)) {
    switch ($type) {
      case 'percentage':
        return $weight + $weight * floatval($markup) / 100;
      case 'multiplier':
        return $weight * floatval($markup);
      case 'mass':
        return $weight + floatval($markup);
    }
  }
  else {
    return $weight;
  }
}

/**
 * Puts everything in one package, for now.
 *
 * @param products
 *   An array of nodes of type product
 *
 * @return
 *   An array of package objects, each containing one or more of the products
 */
function _uc_fedex_package_products($products) {
  $packages = array();

  //Always one package for now...

  //if (variable_get('uc_fedex_all_in_one', TRUE)) {
  if (TRUE) {
    $package = new stdClass();
    foreach ($products as $product) {
      $package->price += $product->price * $product->qty;
      $package->weight += $product->weight * $product->qty * uc_weight_conversion($product->weight_units, 'lb');
    }

    /* Markup weight on a per-package basis before sending out for a quote */
    $package->weight = uc_fedex_weight_markup($package->weight);

    /* Round up to nearest lb for FedEx weight */
    $package->shipweight = ceil($package->weight);
    $package->qty = 1;

    // What package descriptions do we need here?

    //$package->container = 'RECTANGULAR';

    //$package->size = 'REGULAR';
    $packages[] = $package;
  }
  return $packages;
}

/**
 * Convenience function to get FedEx codes for their package types.
 *
 * @return
 *   An array of human-friendly names for the different FedEx package types
 */
function _uc_fedex_package_types() {
  return array(
    'YOUR_PACKAGING' => t('Your Packaging'),
    'FEDEX_ENVELOPE' => t('FedEx Envelope'),
    'FEDEX_PAK' => t('FedEx Pak'),
    'FEDEX_BOX' => t('FedEx Box'),
    'FEDEX_TUBE' => t('FedEx Tube'),
    'FEDEX_10KG_BOX' => t('FedEx 10kg Box'),
    'FEDEX_25KG_BOX' => t('FedEx 25kg Box'),
  );
}

/**
 * Convenience function to get FedEx codes for their services.
 * This should probably be sucked out of the WSDL file, to be sure
 * the options stay correct and up-to-date.
 *
 * @return
 *   An array of human-friendly names for the different FedEx service codes
 */
function _uc_fedex_services() {
  return array(
    'FEDEX_GROUND' => t('FedEx Ground'),
    'GROUND_HOME_DELIVERY' => t('FedEx Home Delivery'),
    'STANDARD_OVERNIGHT' => t('FedEx Standard Overnight'),
    'PRIORITY_OVERNIGHT' => t('FedEx Priority Overnight'),
    'FIRST_OVERNIGHT' => t('FedEx First Overnight'),
    'FEDEX_2_DAY' => t('FedEx 2nd Day'),
    'FEDEX_EXPRESS_SAVER' => t('FedEx Express Saver'),
    'FEDEX_1_DAY_FREIGHT' => t('FedEx 1-Day Freight'),
    'FEDEX_2_DAY_FREIGHT' => t('FedEx 2-Day Freight'),
    'FEDEX_3_DAY_FREIGHT' => t('FedEx 3-Day Freight'),
    'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => t('FedEx Europe First International Priority'),
    'INTERNATIONAL_ECONOMY' => t('FedEx International Economy'),
    'INTERNATIONAL_ECONOMY_FREIGHT' => t('FedEx International Economy Freight'),
    'INTERNATIONAL_ECONOMY_DISTRIBUTION' => t('FedEx International Economy Distribution'),
    'INTERNATIONAL_PRIORITY' => t('FedEx International Priority'),
    'INTERNATIONAL_PRIORITY_FREIGHT' => t('FedEx International Priority Freight'),
    'INTERNATIONAL_PRIORITY_DISTRIBUTION' => t('FedEx International Priority Distribution'),
    'INTERNATIONAL_FIRST' => t('FedEx International First'),
    'INTERNATIONAL_DISTRIBUTION_FREIGHT' => t('FedEx International Distribution Freight'),
  );
}

/**
 * Convenience function to get FedEx codes for special services options.
 *
 * @return
 *   An array of human-friendly names for the different FedEx special services options codes
 */
function _uc_fedex_shipment_special_types() {
  return array(
    'BROKER_SELECT_OPTION' => t('FedEx International First'),
    'COD' => t('COD Shipment'),
    'DANGEROUS_GOODS' => t('Dangerous Goods'),
    'DRY_ICE' => t('Dry Ice'),
    'EMAIL_NOTIFICATION' => t('E-Mail Notification'),
    'FUTURE_DAY_SHIPMENT' => t('Future Day Shipment'),
    'HOLD_AT_LOCATION' => t('FedEx International First'),
    'HOLD_SATURDAY' => t('FedEx International First'),
    'INSIDE_DELIVERY' => t('FedEx International First'),
    'INSIDE_PICKUP' => t('FedEx International First'),
    'PRIORITY_ALERT' => t('FedEx International First'),
    'RETURN_SHIPMENT' => t('FedEx International First'),
    'SATURDAY_DELIVERY' => t('Saturday Delivery'),
    'SATURDAY_PICKUP' => t('Saturday Pickup'),
    'THIRD_PARTY_CONSIGNEE' => t('FedEx International First'),
    'WEEKDAY_DELIVERY' => t('Weekday delivery '),
  );
}

/**
 * Convenience function to get FedEx codes for dropoff and pickup .
 * This should probably be sucked out of the WSDL file, to be sure
 * the options stay correct and up-to-date.
 *
 * @return
 *   An array of human-friendly names for the different FedEx pickup/dropoff option codes
 */
function _uc_fedex_dropoff_types() {
  return array(
    'BUSINESS_SERVICE_CENTER' => t('Dropoff at FedEx Business Service Center'),
    'DROP_BOX' => t('Dropoff at FedEx Drop Box'),
    'REGULAR_PICKUP' => t('Regularly scheduled Pickup from your location'),
    'REQUEST_COURIER' => t('One-time Pickup request'),
    'STATION' => t('Dropoff at FedEx Staffed Location'),
  );
}

/**
 * Print SOAP request and response, iff allowed by user access permissions.
 * To view transaction details, set display debug TRUE in
 * admin/store/settings/quotes/edit
 *
 * @param client
 *   SOAP client object containing transaction history
 */
function print_request_response($client) {
  if (user_access('configure quotes') && variable_get('uc_quote_display_debug', FALSE)) {
    drupal_set_message('<h2>FedEx Rate Quote Transaction processed successfully.</h2>' . '<h3>Request: </h3><pre>' . check_plain($client
      ->__getLastRequest()) . '</pre>' . '<h3>Response: </h3><pre>' . check_plain($client
      ->__getLastResponse()) . '</pre>');
  }
}

/**
 * Theme function to format the FedEx service name and rate amount
 * line-item shown to the customer.
 *
 * @param $service
 *   The FedEx service name
 * @param $packages
 *   Package information
 * @ingroup themeable
 */
function theme_uc_fedex_option_label($service, $packages) {

  // Start with FedEx logo
  $output = '<img class="fedex-logo" src="' . base_path() . drupal_get_path('module', 'uc_fedex') . '/uc_fedex_logo.gif" alt="FedEx Logo" />';

  // Add FedEx service name, removing the first six characters
  // (== 'FedEx ') because these replicate the logo image
  $output .= substr($service, 6);

  // Add package information

  //$output .= ' ('. format_plural(count($packages), '1 package', '@count packages') .')';
  return $output;
}

Functions

Namesort descending Description
print_request_response Print SOAP request and response, iff allowed by user access permissions. To view transaction details, set display debug TRUE in admin/store/settings/quotes/edit
theme_uc_fedex_option_label Theme function to format the FedEx service name and rate amount line-item shown to the customer.
uc_fedex_admin_settings Default FedEx Web Services API settings.
uc_fedex_admin_settings_validate Validation handler for uc_fedex_admin_settings form.
uc_fedex_ca_predicate Implementation of hook_ca_predicate().
uc_fedex_init Implementation of hook_init().
uc_fedex_menu Implementation of hook_menu(). Called when Drupal is building menus. Cache parameter lets module know if Drupal intends to cache menu or not - different results may be returned for either case.
uc_fedex_quote Callback for retrieving a FedEx shipping quote.
uc_fedex_rate_markup Modify the rate received from FedEx before displaying to the customer.
uc_fedex_rate_request Constructs and executes a SOAP RateAvailabilityService request. Obtains Rate and Available Services information needed for shipping quote.
uc_fedex_shipping_method Implementation of Ubercart's hook_shipping_method().
uc_fedex_shipping_type Implementation of Ubercart's hook_shipping_type().
uc_fedex_store_status Implementation of Ubercart's hook_store_status().
uc_fedex_theme Implementation of hook_theme() used to declare module theme functions.
uc_fedex_tracking_request Constructs and executes a SOAP TrackService request. Returns Tracking information.
uc_fedex_weight_markup Modify the weight of shipment before sending to FedEx for a quote.
_uc_fedex_dropoff_types Convenience function to get FedEx codes for dropoff and pickup . This should probably be sucked out of the WSDL file, to be sure the options stay correct and up-to-date.
_uc_fedex_package_products Puts everything in one package, for now.
_uc_fedex_package_types Convenience function to get FedEx codes for their package types.
_uc_fedex_services Convenience function to get FedEx codes for their services. This should probably be sucked out of the WSDL file, to be sure the options stay correct and up-to-date.
_uc_fedex_shipment_special_types Convenience function to get FedEx codes for special services options.