You are here

uc_fedex.ship.inc in FedEx Shipping 6.2

Same filename and directory in other branches
  1. 7.2 uc_fedex.ship.inc

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.ship.inc
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>
 */

/******************************************************************************
 * Label Printing (ship service)                                              *
 ******************************************************************************/

/**
 * Constructs and executes a SOAP ShipService request.
 *
 * @param $packages
 *   Array of packages received from the cart.
 * @param $origin
 *   Delivery origin address information.
 * @param $destination
 *   Delivery destination address information.
 * @param $fedex_service
 *   FedEx service code (refers to FedEx Ground, Standard Overnight, etc.).
 *
 * @return
 *   Response object from SOAP request.
 */
function uc_fedex_shipment_request($packages, $origin, $destination, $fedex_service) {

  // Assemble information about origin
  $country = uc_get_country_data(array(
    'country_id' => $origin->country,
  ));
  $origin->country_iso_code_2 = $country[0]['country_iso_code_2'];

  // Assemble information about destination
  $country = uc_get_country_data(array(
    'country_id' => $destination->country,
  ));
  $destination->country_iso_code_2 = $country[0]['country_iso_code_2'];

  // 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') . '/ShipService_v7.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
  $request['TransactionDetail'] = array(
    'CustomerTransactionId' => '*** Shipping Request v7 from Ubercart ***',
  );

  // Shipping Request v7.0.0
  $request['Version'] = array(
    'ServiceId' => 'ship',
    'Major' => '7',
    'Intermediate' => '0',
    'Minor' => '0',
  );

  // Timestamp
  $request['RequestedShipment']['ShipTimestamp'] = date('c');

  // Drupal 6.x version of format_date() doesn't support 'c', so until
  // then we use date() directly.  date() doesn't take care of site
  // timezone, though.

  //$request['RequestedShipment']['ShipTimestamp'] = format_date(time(), 'custom', 'c');

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

  // Set Packaging type
  // First element of the packages array is used because all packages
  // in a multi-package shipment must have the same packaging type
  $request['RequestedShipment']['PackagingType'] = reset($packages)->pkg_type;

  // Set Service
  $request['RequestedShipment']['ServiceType'] = $fedex_service;
  $street_lines = array();
  $street_lines[] = $origin->street1;
  if (!empty($origin->street2)) {
    $street_lines[] = $origin->street2;
  }

  // Get address
  $request['RequestedShipment']['Shipper'] = array(
    'Contact' => array(
      'CompanyName' => $origin->company,
      'PhoneNumber' => $origin->phone,
    ),
    'Address' => array(
      'StreetLines' => $street_lines,
      'City' => $origin->city,
      'StateOrProvinceCode' => uc_get_zone_code($origin->zone),
      'PostalCode' => $origin->postal_code,
      'CountryCode' => $origin->country_iso_code_2,
    ),
  );
  $street_lines = array();
  $street_lines[] = $destination->street1;
  if (!empty($destination->street2)) {
    $street_lines[] = $destination->street2;
  }

  // Grab details of package destination
  $request['RequestedShipment']['Recipient'] = array(
    'Contact' => array(
      'PersonName' => $destination->first_name . ' ' . $destination->last_name,
      'CompanyName' => $destination->company,
      'PhoneNumber' => $destination->phone,
    ),
    'Address' => array(
      'StreetLines' => $street_lines,
      'City' => $destination->city,
      'StateOrProvinceCode' => uc_get_zone_code($destination->zone),
      'PostalCode' => $destination->postal_code,
      'CountryCode' => $destination->country_iso_code_2,
      'Residential' => $destination->residential,
    ),
  );

  // CompanyName needs to be unset if empty
  if (empty($destination->company)) {
    unset($request['RequestedShipment']['Recipient']['Contact']['CompanyName']);
  }

  // Label specifications
  $request['RequestedShipment']['LabelSpecification'] = array(
    'LabelFormatType' => 'COMMON2D',
    'ImageType' => variable_get('uc_fedex_label_image_format', 'PDF'),
    'LabelStockType' => variable_get('uc_fedex_label_stock', 'PAPER_7X4.75'),
    'LabelPrintingOrientation' => variable_get('uc_fedex_label_orientation', 'TOP_EDGE_OF_TEXT_FIRST'),
  );

  // Bill shipping to?
  $request['RequestedShipment']['ShippingChargesPayment'] = array(
    'PaymentType' => 'SENDER',
    'Payor' => array(
      'AccountNumber' => variable_get('uc_fedex_account_number', 0),
      'CountryCode' => $origin->country_iso_code_2,
    ),
  );

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

  // Fill in Package details
  $request['RequestedShipment']['RequestedPackageLineItems'] = array();

  // Determine weight and length units to send to FedEx
  $weight_units = strtoupper(variable_get('uc_weight_unit', 'LB'));
  $weight_conversion_factor = 1;
  if ($weight_units != 'LB' && $weight_units != 'KG') {
    $weight_conversion_factor = uc_weight_conversion($weight_units, 'LB');
    $weight_units = 'LB';
  }
  $length_units = strtoupper(variable_get('uc_length_unit', 'IN'));
  $length_conversion_factor = 1;
  if ($length_units != 'IN' && $length_units != 'CM') {
    $length_conversion_factor = uc_length_conversion($length_units, 'IN');
    $length_units = 'IN';
  }

  // Iterate over $packages to account for multi-package shipments
  $sequence = 0;
  $package_properties = array();
  foreach ($packages as $package) {
    $sequence++;
    $package_properties[$sequence] = array(
      'SequenceNumber' => $sequence,
      'CustomerReferences' => array(
        '0' => array(
          'CustomerReferenceType' => 'INVOICE_NUMBER',
          // Gag - shouldn't be getting $order_id out of _SESSION
          'Value' => $_SESSION['fedex']['order_id'],
        ),
        '1' => array(
          'CustomerReferenceType' => 'CUSTOMER_REFERENCE',
          'Value' => $_SESSION['fedex']['reference'],
        ),
      ),
      // Weights must be rounded up to nearest integer value
      'Weight' => array(
        'Value' => ceil($package->weight * $weight_conversion_factor),
        'Units' => $weight_units,
      ),
      // Lengths must be rounded up to nearest integer value
      'Dimensions' => array(
        'Length' => ceil($package->length * $length_conversion_factor),
        'Width' => ceil($package->width * $length_conversion_factor),
        'Height' => ceil($package->height * $length_conversion_factor),
        'Units' => $length_units,
      ),
    );

    // Only need Package Dimensions if using our own packaging.
    if ($request['RequestedShipment']['PackagingType'] != 'YOUR_PACKAGING') {
      unset($package_properties[$sequence]['Dimensions']);
    }

    // Add Insurance if requested
    if (variable_get('uc_fedex_insurance', FALSE)) {
      $package_properties[$sequence]['InsuredValue'] = array(
        'Amount' => $package->value,
        'Currency' => 'USD',
      );
    }

    // Multi-package shipments need to reference master tracking #
    if ($sequence > 1) {
      $request['RequestedShipment']['MasterTrackingId'] = $master_tracking_id;
    }

    // Fill in SOAP request with $package_properties
    $request['RequestedShipment']['RequestedPackageLineItems'][0] = $package_properties[$sequence];

    //
    // For Multi-Package shipments, need to send one SOAP request
    // *per package* to the FedEx server
    //
    try {
      $response[$sequence] = $client
        ->processShipment($request);
      if ($response[$sequence]->HighestSeverity != 'FAILURE' && $response[$sequence]->HighestSeverity != 'ERROR') {
        print_request_response($client);

        // Save master tracking # to use for remaining package in shipment
        if ($sequence == 1 && count($packages) > 1) {
          $master_tracking_id = $response[$sequence]->CompletedShipmentDetail->MasterTrackingId;
        }
      }
      else {
        drupal_set_message(t('Error in processing FedEx Shipping Request.'), 'error');
        foreach ($response[$sequence]->Notifications as $notification) {
          if (is_array($response[$sequence]->Notifications)) {
            drupal_set_message($notification->Severity . ': ' . $notification->Message, 'error');
          }
          else {
            drupal_set_message($notification, 'error');
          }
        }
      }
    } catch (SoapFault $exception) {
      drupal_set_message('<h2>Fault</h2><br /><b>Code:</b>' . $exception->faultcode . '<br /><b>String:</b>' . $exception->faultstring . '<br />', 'error');
    }
  }
  return $response;
}

/**
 * Last chance for user to review shipment.
 *
 * @see theme_uc_fedex_confirm_shipment()
 * @see uc_fedex_confirm_shipment_submit()
 * @ingroup forms
 */
function uc_fedex_confirm_shipment($order) {
  $form = array();
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Generate Label'),
  );
  return $form;
}

/**
 * Displays final shipment information for review.
 *
 * @see uc_fedex_confirm_shipment()
 * @ingroup themeable
 */
function theme_uc_fedex_confirm_shipment($form) {
  $output = '';
  $output .= '<div class="shipping-address"><b>' . t('Ship from:') . '</b><br />';
  $output .= uc_address_format(check_plain($_SESSION['fedex']['origin']->first_name), check_plain($_SESSION['fedex']['origin']->last_name), check_plain($_SESSION['fedex']['origin']->company), check_plain($_SESSION['fedex']['origin']->street1), check_plain($_SESSION['fedex']['origin']->street2), check_plain($_SESSION['fedex']['origin']->city), check_plain($_SESSION['fedex']['origin']->zone), check_plain($_SESSION['fedex']['origin']->postal_code), check_plain($_SESSION['fedex']['origin']->country));
  $output .= '<br />' . check_plain($_SESSION['fedex']['origin']->email);
  $output .= '</div>';
  $output .= '<div class="shipping-address"><b>' . t('Ship to:') . '</b><br />';
  $output .= uc_address_format(check_plain($_SESSION['fedex']['destination']->first_name), check_plain($_SESSION['fedex']['destination']->last_name), check_plain($_SESSION['fedex']['destination']->company), check_plain($_SESSION['fedex']['destination']->street1), check_plain($_SESSION['fedex']['destination']->street2), check_plain($_SESSION['fedex']['destination']->city), check_plain($_SESSION['fedex']['destination']->zone), check_plain($_SESSION['fedex']['destination']->postal_code), check_plain($_SESSION['fedex']['destination']->country));
  $output .= '<br />' . check_plain($_SESSION['fedex']['destination']->email);
  $output .= '</div>';
  $output .= '<div class="shipment-data">';
  $method = uc_fedex_shipping_method();
  $accessorials = array_merge($method['fedex']['quote']['accessorials'], $method['fedex_ground']['quote']['accessorials'], $method['fedex_freight']['quote']['accessorials']);
  $output .= '<b>' . $accessorials[$_SESSION['fedex']['service']] . '</b><br />';
  $context = array(
    'revision' => 'themed',
    'type' => 'amount',
  );
  $output .= '<i>' . check_plain($_SESSION['fedex']['rate']['type']) . '</i>: ' . uc_price($_SESSION['fedex']['rate']['amount'], $context) . ' (' . check_plain($_SESSION['fedex']['rate']['currency']) . ') -- ';
  $output .= '<i>' . t('Paid') . '</i>: ' . $_SESSION['fedex']['paid'] . '<br />';
  $ship_date = $_SESSION['fedex']['ship_date'];
  $output .= 'Ship date: ' . format_date(gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']), 'custom', variable_get('uc_date_format_default', 'm/d/Y'));
  $output .= "</div>\n<br style=\"clear: both;\" />";
  $output .= drupal_render($form);
  return $output;
}

/**
 * Generates shipping labels.
 *
 * @see uc_fedex_confirm_shipment()
 */
function uc_fedex_confirm_shipment_submit($form, &$form_state) {

  // Recover shipment data from session
  $order_id = $_SESSION['fedex']['order_id'];
  $packages = $_SESSION['fedex']['packages'];
  $origin = $_SESSION['fedex']['origin'];
  $destination = $_SESSION['fedex']['destination'];

  //
  // Assemble the label request and send it off to FedEx
  //
  $mp_response = uc_fedex_shipment_request($_SESSION['fedex']['packages'], $origin, $destination, $_SESSION['fedex']['service']);

  //
  // Loop over responses, to handle case of multi-package shipments
  //
  foreach ($mp_response as $response) {

    // Package tracking numbers
    $tracking[] = $response->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds->TrackingNumber;
    if (isset($response->CompletedShipmentDetail->RoutingDetail->DeliveryDate)) {
      $exp_delivery[] = date_parse($response->CompletedShipmentDetail->RoutingDetail->DeliveryDate);
    }
    else {
      $exp_delivery[] = array(
        'month' => 0,
        'day' => 0,
        'year' => 0,
      );

      // Ground and Home Delivery don't have DeliveryDate,
      // they have TransitTime instead
    }
  }

  // Final package in shipment contains summary information
  $last_package = end($mp_response);
  $rating = $last_package->CompletedShipmentDetail->ShipmentRating;

  // 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 ACCOUNT rates (in ShipmentRateDetails[0])
    // and LIST rates (in ShipmentRateDetails[1])
    $ratedetail = $rating->ShipmentRateDetails[1];
  }
  else {

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

  // Build $shipment object to return to uc_shipment
  $shipment = new stdClass();
  $shipment->order_id = $order_id;
  $shipment->origin = clone $_SESSION['fedex']['origin'];
  $shipment->destination = clone $_SESSION['fedex']['destination'];
  $shipment->packages = $_SESSION['fedex']['packages'];
  $shipment->shipping_method = 'fedex';
  $shipment->accessorials = $_SESSION['fedex']['service'];
  $shipment->carrier = t('FedEx');
  $shipment->cost = (string) $shipment_charge->Amount;
  $shipment->tracking_number = (string) $tracking[0];
  $ship_date = $_SESSION['fedex']['ship_date'];
  $shipment->ship_date = gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']);
  $shipment->expected_delivery = gmmktime(12, 0, 0, $exp_delivery[0]['month'], $exp_delivery[0]['day'], $exp_delivery[0]['year']);

  // Loop over packages in shipment to store labels and tracking numbers
  foreach ($mp_response as $package_results) {
    $package =& current($shipment->packages);
    $package->tracking_number = (string) $package_results->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds->TrackingNumber;
    if (file_check_directory(file_create_path('fedex_labels'), FILE_CREATE_DIRECTORY)) {
      $label_path = file_create_path('fedex_labels') . '/label' . $package->tracking_number . '.' . strtolower(variable_get('uc_fedex_label_image_format', 'PDF'));
      if ($label_file = fopen($label_path, 'wb')) {
        fwrite($label_file, $package_results->CompletedShipmentDetail->CompletedPackageDetails->Label->Parts->Image);
        fclose($label_file);
        $package->label_image = $label_path;
      }
      else {
        drupal_set_message(t('Could not open a file to save the label image.'), 'error');
      }
    }
    else {
      drupal_set_message(t('Could not find or create the directory "fedex_labels" in the file system path.'), 'error');
    }
    unset($package);
    next($shipment->packages);
  }
  uc_shipping_shipment_save($shipment);
  unset($_SESSION['fedex']);
  $form_state['redirect'] = 'admin/store/orders/' . $order_id . '/shipments';
}

/**
 * Displays the shipping label for printing.
 *
 * Each argument is a component of the file path to the image.
 *
 * @ingroup themeable
 */
function uc_fedex_label_image() {
  $args = func_get_args();
  $image_path = implode('/', $args);
  drupal_goto($image_path);

  // Alternate strategy for image display:
  // Handle PDF files differently, because they can't be displayed
  // with an <img> tag
  $filename = explode('.', end($args));
  $extension = $filename[1];
  if ($extension == 'pdf') {
    drupal_goto($image_path);
  }
}

/**
 * Shipment creation callback.
 *
 * Confirms shipment data before requesting a shipping label.
 *
 * @param $order
 *   The order object for the shipment.
 * @param $package_ids
 *   Array of package ids to shipped.
 *
 * @see uc_fedex_fulfill_order_validate()
 * @see uc_fedex_fulfill_order_submit()
 * @ingroup forms
 */
function uc_fedex_fulfill_order($form_state, $order, $package_ids) {
  $form = array();
  $pkg_types = _uc_fedex_package_types();
  $form['order_id'] = array(
    '#type' => 'value',
    '#value' => $order->order_id,
  );
  $packages = array();
  $addresses = array();

  // Container for package data
  $form['packages'] = array(
    '#tree' => TRUE,
  );
  foreach ($package_ids as $id) {
    $package = uc_shipping_package_load($id);
    if ($package) {
      foreach ($package->addresses as $address) {
        if (!in_array($address, $addresses)) {
          $addresses[] = $address;
        }
      }

      // Create list of products and get a representative product
      // for default values
      $product_list = array();
      $declared_value = 0;
      foreach ($package->products as $product) {
        $product_list[] = $product->qty . ' x ' . $product->model;
        $declared_value += $product->qty * $product->price;
      }
      $fedex_data = db_fetch_array(db_query("SELECT pkg_type FROM {uc_fedex_products} WHERE nid = %d", $product->nid));
      $package->fedex = $fedex_data;
      $pkg_form = array(
        '#type' => 'fieldset',
        '#title' => t('Package !id', array(
          '!id' => $id,
        )),
      );
      $pkg_form['products'] = array(
        '#value' => theme('item_list', $product_list),
      );
      $pkg_form['package_id'] = array(
        '#type' => 'hidden',
        '#value' => $id,
      );
      $pkg_form['pkg_type'] = array(
        '#type' => 'select',
        '#title' => t('Package type'),
        '#options' => $pkg_types,
        '#default_value' => $package->fedex['pkg_type'],
        '#required' => TRUE,
      );
      $pkg_form['declared_value'] = array(
        '#type' => 'textfield',
        '#title' => t('Declared value'),
        '#default_value' => $declared_value,
        '#required' => TRUE,
      );
      $pkg_type['weight'] = array(
        '#type' => 'fieldset',
        '#title' => t('Weight'),
        '#description' => t('Weight of the package. Default value is sum of product weights in the package.'),
        '#theme' => 'uc_shipping_package_dimensions',
      );
      $pkg_form['weight']['units'] = array(
        '#type' => 'select',
        '#title' => t('Weight units'),
        '#options' => array(
          'lb' => t('Pounds'),
          'kg' => t('Kilograms'),
          'oz' => t('Ounces'),
          'g' => t('Grams'),
        ),
        '#default_value' => isset($package->weight_units) ? $package->weight_units : variable_get('uc_weight_unit', 'lb'),
      );
      $pkg_form['weight']['weight'] = array(
        '#type' => 'textfield',
        '#title' => t('Weight'),
        '#default_value' => isset($package->weight) ? $package->weight : 0,
      );
      $pkg_type['dimensions'] = array(
        '#type' => 'fieldset',
        '#title' => t('Dimensions'),
        '#description' => t('Physical dimensions of the package.'),
        '#theme' => 'uc_shipping_package_dimensions',
      );
      $pkg_form['dimensions']['units'] = array(
        '#type' => 'select',
        '#title' => t('Length units'),
        '#options' => array(
          'in' => t('Inches'),
          'ft' => t('Feet'),
          'cm' => t('Centimeters'),
          'mm' => t('Millimeters'),
        ),
        '#default_value' => isset($package->length_units) ? $package->length_units : variable_get('uc_length_unit', 'in'),
      );
      $pkg_form['dimensions']['length'] = array(
        '#type' => 'textfield',
        '#title' => t('Length'),
        '#default_value' => isset($package->length) ? $package->length : 1,
      );
      $pkg_form['dimensions']['width'] = array(
        '#type' => 'textfield',
        '#title' => t('Width'),
        '#default_value' => isset($package->width) ? $package->width : 1,
      );
      $pkg_form['dimensions']['height'] = array(
        '#type' => 'textfield',
        '#title' => t('Height'),
        '#default_value' => isset($package->height) ? $package->height : 1,
      );
      $form['packages'][$id] = $pkg_form;
    }
  }
  $form = array_merge($form, uc_shipping_address_form($form_state, $addresses, $order));

  // Mark fields needed by the FedEx API as 'required'
  foreach (array(
    'delivery_email',
    'delivery_last_name',
    'delivery_phone',
    'delivery_street1',
    'delivery_city',
    'delivery_zone',
    'delivery_country',
    'delivery_postal_code',
  ) as $field) {
    $form['destination'][$field]['#required'] = TRUE;
  }

  // Allow selection of address type
  $form['destination']['delivery_residential'] = array(
    '#type' => 'radios',
    '#title' => t('Address type'),
    '#default_value' => variable_get('uc_fedex_residential_quotes', TRUE),
    '#options' => array(
      0 => t('Commercial'),
      1 => t('Residential'),
    ),
    '#required' => TRUE,
  );

  // Determine shipping option chosen by the customer
  $methods = module_invoke_all('shipping_method');
  $services = $methods[$order->quote['method']]['quote']['accessorials'];
  $method = $services[$order->quote['accessorials']];

  // Container for shipment data
  $form['shipment'] = array(
    '#type' => 'fieldset',
    '#title' => t('Shipment data'),
    '#collapsible' => TRUE,
  );

  // Inform user of customer's shipping choice
  $form['shipment']['shipping_choice'] = array(
    '#type' => 'markup',
    '#prefix' => '<div>',
    '#value' => t('Customer selected "@method" as the shipping method and paid @rate', array(
      '@method' => $method,
      '@rate' => uc_currency_format($order->quote['rate']),
    )),
    '#suffix' => '</div>',
  );

  // Pass shipping charge paid information on to validation function so it
  // can be displayed alongside actual costs
  $form['shipment']['paid'] = array(
    '#type' => 'value',
    '#value' => uc_currency_format($order->quote['rate']),
  );
  $services = array_merge(_uc_fedex_ground_services(), _uc_fedex_express_services(), _uc_fedex_freight_services());
  $default_service = '';
  $method = $order->quote['method'];
  if ($method == 'fedex_ground' || $method == 'fedex' || $method == 'fedex_freight') {
    $default_service = $order->quote['accessorials'];
  }
  $form['shipment']['service'] = array(
    '#type' => 'select',
    '#title' => t('FedEx service'),
    '#options' => $services,
    '#default_value' => $default_service,
  );
  $today = getdate();
  $form['shipment']['ship_date'] = array(
    '#type' => 'date',
    '#title' => t('Ship date'),
    '#default_value' => array(
      'year' => $today['year'],
      'month' => $today['mon'],
      'day' => $today['mday'],
    ),
  );
  $form['shipment']['reference'] = array(
    '#type' => 'textfield',
    '#title' => t('Reference Number'),
    '#maxlength' => 40,
    // FedEx limit
    '#default_value' => '',
    '#required' => FALSE,
    '#description' => t('Optional information to include on shipping label.  Limited to 40 characters.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Review shipment'),
  );
  return $form;
}

/**
 * Passes final information into shipment object.
 *
 * @see uc_fedex_fulfill_order()
 * @see uc_fedex_confirm_shipment()
 */
function uc_fedex_fulfill_order_validate($form, &$form_state) {
  $origin = new stdClass();
  $destination = new stdClass();
  $packages = array();
  foreach ($form_state['values'] as $key => $value) {
    if (substr($key, 0, 7) == 'pickup_') {
      $field = substr($key, 7);
      $origin->{$field} = $value;
    }
    elseif (substr($key, 0, 9) == 'delivery_') {
      $field = substr($key, 9);
      $destination->{$field} = $value;
    }
  }

  // Populate $_SESSION variable with information we'll need
  // later to generate the label
  $_SESSION['fedex'] = array();
  $_SESSION['fedex']['origin'] = $origin;

  // Determine if address is Residential or Commercial
  // If Address Validation API is not used or fails, default to form choice
  $destination->residential = uc_fedex_address_is_residential($destination, $destination->residential, FALSE);
  $_SESSION['fedex']['destination'] = $destination;
  foreach ($form_state['values']['packages'] as $id => $pkg_form) {
    $package = uc_shipping_package_load($id);
    $package->pkg_type = $pkg_form['pkg_type'];
    $package->value = $pkg_form['declared_value'];
    $package->weight = $pkg_form['weight']['weight'];
    $package->weight_units = $pkg_form['weight']['units'];
    $package->length = $pkg_form['dimensions']['length'];
    $package->width = $pkg_form['dimensions']['width'];
    $package->height = $pkg_form['dimensions']['height'];
    $package->length_units = $pkg_form['dimensions']['units'];
    $package->qty = 1;
    $_SESSION['fedex']['packages'][$id] = $package;
  }
  $_SESSION['fedex']['service'] = $form_state['values']['service'];
  $_SESSION['fedex']['paid'] = $form_state['values']['paid'];
  $_SESSION['fedex']['ship_date'] = $form_state['values']['ship_date'];
  $_SESSION['fedex']['reference'] = $form_state['values']['reference'];
  $_SESSION['fedex']['order_id'] = $form_state['values']['order_id'];

  //
  // Assemble a quote request and send it off to FedEx
  //
  $response = uc_fedex_shipment_request($_SESSION['fedex']['packages'], $origin, $destination, $form_state['values']['service']);

  // Response may contain multiple packages
  foreach ($response as $package) {

    // Need to fail validation if SOAP request returns ERROR
    if ($package->HighestSeverity == 'FAILURE' || $package->HighestSeverity == 'ERROR') {
      form_set_error('uc_fedex_fulfill_order', $package->Notifications->Message);
    }

    // Shipping Charge
    $rating = $package->CompletedShipmentDetail->CompletedPackageDetails->PackageRating;

    // 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 ACCOUNT rates (in PackageRateDetails[0])
      // and LIST rates (in PackageRateDetails[1])
      $ratedetail = $rating->PackageRateDetails[1];
    }
    else {

      // ACCOUNT rate
      // ACCOUNT quotes may return either ACCOUNT rates only OR
      // ACCOUNT rates and LIST rates.  Check.
      if (is_array($rating->PackageRateDetails)) {
        $ratedetail = $rating->PackageRateDetails[0];
      }
      else {
        $ratedetail = $rating->PackageRateDetails;
      }
    }
    $charge[] = $ratedetail->NetCharge;
  }
  $last_package = end($response);
  $rating = $last_package->CompletedShipmentDetail->ShipmentRating;

  // 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 ACCOUNT rates (in ShipmentRateDetails[0])
    // and LIST rates (in ShipmentRateDetails[1])
    $ratedetail = $rating->ShipmentRateDetails[1];
    $_SESSION['fedex']['rate']['type'] = t('List Rates');
  }
  else {

    // ACCOUNT rate
    // ACCOUNT quotes may return either ACCOUNT rates only OR
    // ACCOUNT rates and LIST rates.  Check.
    if (is_array($rating->ShipmentRateDetails)) {
      $ratedetail = $rating->ShipmentRateDetails[0];
    }
    else {
      $ratedetail = $rating->ShipmentRateDetails;
    }
    $_SESSION['fedex']['rate']['type'] = t('Account Rates');
  }
  $shipment_charge = $ratedetail->TotalNetCharge;
  $_SESSION['fedex']['rate']['currency'] = (string) $shipment_charge->Currency;
  $_SESSION['fedex']['rate']['amount'] = (string) $shipment_charge->Amount;
}

/**
 * Passes final information into shipment object.
 *
 * @see uc_fedex_fulfill_order()
 * @see uc_fedex_confirm_shipment()
 */
function uc_fedex_fulfill_order_submit($form, &$form_state) {
  $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id'] . '/shipments/fedex';
}

/**
 * Convenience function to get FedEx codes for payor.
 *
 * @return
 *   An array of human-friendly names for available FedEx payor option codes.
 */
function _uc_fedex_payor_types() {
  return array(
    'RECIPIENT' => t('Shipping billed to Recipient'),
    'SENDER' => t('Shipping billed to Sender'),
    'THIRD_PARTY' => t('Shipping billed to third party'),
  );
}

/**
 * Convenience function to get FedEx codes for label image format.
 *
 * @return
 *   An array of human-friendly names for available FedEx label image formats.
 */
function _uc_fedex_label_image_types() {
  return array(
    'PNG' => t('PNG - Portable Next Generation'),
    'PDF' => t('PDF - Adobe Portable Document Format'),
    'EPL2' => t('EPL2 - Eltron Thermal Printer Format'),
    'DPL' => t('DPL - Unimark Thermal Printer Format'),
    'ZPLII' => t('ZPLII - Zebra Thermal Printer Format'),
  );
}

/**
 * Convenience function to get FedEx codes for label orientation.
 *
 * @return
 *   An array of human-friendly names for the different FedEx label orientation.
 */
function _uc_fedex_label_orientation_types() {
  return array(
    'BOTTOM_EDGE_OF_TEXT_FIRST' => t('BOTTOM_EDGE_OF_TEXT_FIRST'),
    'TOP_EDGE_OF_TEXT_FIRST' => t('TOP_EDGE_OF_TEXT_FIRST'),
  );
}

/**
 * Convenience function to get FedEx codes for label paper stock type.
 *
 * @return
 *   An array of human-friendly names for available FedEx label
 *   paper stock types.
 */
function _uc_fedex_label_stock_types() {
  return array(
    'PAPER_4X6' => t('Laser 4" x 6"'),
    'PAPER_4X8' => t('Laser 4" x 8"'),
    'PAPER_4X9' => t('Laser 4" x 9"'),
    'PAPER_7X4.75' => t('Laser 7" x 4.75"'),
    'PAPER_8.5X11_BOTTOM_HALF_LABEL' => t('Laser 8.5" x 11", Label on Bottom Half'),
    'PAPER_8.5X11_TOP_HALF_LABEL' => t('Laser 8.5" x 11", Label on Top Half'),
    'STOCK_4X6' => t('Thermal 4" x 6"'),
    'STOCK_4X6.75_LEADING_DOC_TAB' => t('Thermal 4" x 6.75", Leading DocTab'),
    'STOCK_4X6.75_TRAILING_DOC_TAB' => t('Thermal 4" x 6.75", Trailing DocTab'),
    'STOCK_4X8' => t('Thermal 4" x 8"'),
    'STOCK_4X9_LEADING_DOC_TAB' => t('Thermal 4" x 9", Leading DocTab'),
    'STOCK_4X9_TRAILING_DOC_TAB' => t('Thermal 4" x 9", Trailing DocTab'),
  );
}

Functions

Namesort descending Description
theme_uc_fedex_confirm_shipment Displays final shipment information for review.
uc_fedex_confirm_shipment Last chance for user to review shipment.
uc_fedex_confirm_shipment_submit Generates shipping labels.
uc_fedex_fulfill_order Shipment creation callback.
uc_fedex_fulfill_order_submit Passes final information into shipment object.
uc_fedex_fulfill_order_validate Passes final information into shipment object.
uc_fedex_label_image Displays the shipping label for printing.
uc_fedex_shipment_request Constructs and executes a SOAP ShipService request.
_uc_fedex_label_image_types Convenience function to get FedEx codes for label image format.
_uc_fedex_label_orientation_types Convenience function to get FedEx codes for label orientation.
_uc_fedex_label_stock_types Convenience function to get FedEx codes for label paper stock type.
_uc_fedex_payor_types Convenience function to get FedEx codes for payor.