class ActiveShipping::FedEx

FedEx carrier implementation.

FedEx module by Jimmy Baker (github.com/jimmyebaker) Documentation can be found here: images.fedex.com/us/developer/product/WebServices/MyWebHelp/PropDevGuide.pdf

Constants

CARRIER_CODES
DEFAULT_LABEL_STOCK_TYPE
DELIVERY_ADDRESS_NODE_NAMES
DROPOFF_TYPES
LABEL_FORMATS

Available return formats for image data when creating labels

LIVE_URL
PACKAGE_IDENTIFIER_TYPES
PACKAGE_TYPES
PAYMENT_TYPES
SERVICE_TYPES
SHIPPER_ADDRESS_NODE_NAMES
SIGNATURE_OPTION_CODES
TEST_URL
TRACKING_STATUS_CODES

FedEx tracking codes as described in the FedEx Tracking Service WSDL Guide All delays also have been marked as exceptions

TRANSIENT_TRACK_RESPONSE_CODES
TRANSIT_TIMES
UNRECOVERABLE_TRACK_RESPONSE_CODES

Public Class Methods

service_name_for_code(service_code) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 146
def self.service_name_for_code(service_code)
  SERVICE_TYPES[service_code] || "FedEx #{service_code.titleize.sub(/Fedex /, '')}"
end

Public Instance Methods

create_shipment(origin, destination, packages, options = {}) click to toggle source

Get Shipping labels

# File lib/active_shipping/carriers/fedex.rb, line 175
def create_shipment(origin, destination, packages, options = {})
  options = @options.merge(options)
  packages = Array(packages)
  raise Error, "Multiple packages are not supported yet." if packages.length > 1

  request = build_shipment_request(origin, destination, packages, options)
  logger.debug(request) if logger

  response = commit(save_request(request), (options[:test] || false))
  parse_ship_response(response)
end
find_rates(origin, destination, packages, options = {}) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 154
def find_rates(origin, destination, packages, options = {})
  options = @options.merge(options)
  packages = Array(packages)

  rate_request = build_rate_request(origin, destination, packages, options)

  xml = commit(save_request(rate_request), (options[:test] || false))

  parse_rate_response(origin, destination, packages, xml, options)
end
find_tracking_info(tracking_number, options = {}) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 165
def find_tracking_info(tracking_number, options = {})
  options = @options.merge(options)

  tracking_request = build_tracking_request(tracking_number, options)
  xml = commit(save_request(tracking_request), (options[:test] || false))
  parse_tracking_response(xml, options)
end
maximum_address_field_length() click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 187
def maximum_address_field_length
  # See Fedex Developper Guide
  35
end
requirements() click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 150
def requirements
  [:key, :password, :account, :login]
end

Protected Instance Methods

build_contact_address_nodes(xml, location) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 267
def build_contact_address_nodes(xml, location)
  xml.Contact do
    xml.PersonName(location.name)
    xml.CompanyName(location.company)
    xml.PhoneNumber(location.phone)
  end
  xml.Address do
    xml.StreetLines(location.address1) if location.address1
    xml.StreetLines(location.address2) if location.address2
    xml.City(location.city) if location.city
    xml.StateOrProvinceCode(location.state)
    xml.PostalCode(location.postal_code)
    xml.CountryCode(location.country_code(:alpha2))
    xml.Residential('true') if location.residential?
  end
end
build_document(xml, expected_root_tag) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 782
def build_document(xml, expected_root_tag)
  document = Nokogiri.XML(xml) { |config| config.strict }
  document.remove_namespaces!
  if document.root.nil? || document.root.name != expected_root_tag
    raise ActiveShipping::ResponseContentError.new(StandardError.new('Invalid document'), xml)
  end
  document
rescue Nokogiri::XML::SyntaxError => e
  raise ActiveShipping::ResponseContentError.new(e, xml)
end
build_freight_shipment_detail_node(xml, freight_options, packages, imperial) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 373
def build_freight_shipment_detail_node(xml, freight_options, packages, imperial)
  xml.FreightShipmentDetail do
    # TODO: case of different freight account numbers?
    xml.FedExFreightAccountNumber(freight_options[:account])
    build_location_node(xml, 'FedExFreightBillingContactAndAddress', freight_options[:billing_location])
    xml.Role(freight_options[:role])

    packages.each do |pkg|
      xml.LineItems do
        xml.FreightClass(freight_options[:freight_class])
        xml.Packaging(freight_options[:packaging])
        build_package_weight_node(xml, pkg, imperial)
        build_package_dimensions_node(xml, pkg, imperial)
      end
    end
  end
end
build_location_node(xml, name, location) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 466
def build_location_node(xml, name, location)
  xml.public_send(name) do
    xml.Address do
      xml.StreetLines(location.address1) if location.address1
      xml.StreetLines(location.address2) if location.address2
      xml.City(location.city) if location.city
      xml.PostalCode(location.postal_code)
      xml.CountryCode(location.country_code(:alpha2))
      xml.Residential(true) unless location.commercial?
    end
  end
end
build_package_dimensions_node(xml, pkg, imperial) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 402
def build_package_dimensions_node(xml, pkg, imperial)
  xml.Dimensions do
    [:length, :width, :height].each do |axis|
      value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f * 1000).round / 1000.0 # 3 decimals
      xml.public_send(axis.to_s.capitalize, value.ceil)
    end
    xml.Units(imperial ? 'IN' : 'CM')
  end
end
build_package_weight_node(xml, pkg, imperial) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 395
def build_package_weight_node(xml, pkg, imperial)
  xml.Weight do
    xml.Units(imperial ? 'LB' : 'KG')
    xml.Value([((imperial ? pkg.lbs : pkg.kgs).to_f * 1000).round / 1000.0, 0.1].max)
  end
end
build_packages_nodes(xml, packages, imperial) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 351
def build_packages_nodes(xml, packages, imperial)
  packages.map do |pkg|
    xml.RequestedPackageLineItems do
      xml.GroupPackageCount(1)
      build_package_weight_node(xml, pkg, imperial)
      build_package_dimensions_node(xml, pkg, imperial)
    end
  end
end
build_rate_request(origin, destination, packages, options = {}) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 295
def build_rate_request(origin, destination, packages, options = {})
  imperial = location_uses_imperial(origin)

  xml_builder = Nokogiri::XML::Builder.new do |xml|
    xml.RateRequest(xmlns: 'http://fedex.com/ws/rate/v13') do
      build_request_header(xml)
      build_version_node(xml, 'crs', 13, 0 ,0)

      # Returns delivery dates
      xml.ReturnTransitAndCommit(true)

      # Returns saturday delivery shipping options when available
      xml.VariableOptions('SATURDAY_DELIVERY') if options[:saturday_delivery]

      xml.RequestedShipment do
        if options[:pickup_date]
          xml.ShipTimestamp(options[:pickup_date].to_time.iso8601(0))
        else
          xml.ShipTimestamp(ship_timestamp(options[:turn_around_time]).iso8601(0))
        end

        freight = has_freight?(options)

        unless freight
          # fedex api wants this up here otherwise request returns an error
          xml.DropoffType(options[:dropoff_type] || 'REGULAR_PICKUP')
          xml.PackagingType(options[:packaging_type] || 'YOUR_PACKAGING')
        end

        build_location_node(xml, 'Shipper', options[:shipper] || origin)
        build_location_node(xml, 'Recipient', destination)
        if options[:shipper] && options[:shipper] != origin
          build_location_node(xml, 'Origin', origin)
        end

        if freight
          freight_options = options[:freight]
          build_shipping_charges_payment_node(xml, freight_options)
          build_freight_shipment_detail_node(xml, freight_options, packages, imperial)
          build_rate_request_types_node(xml)
        else
          xml.SmartPostDetail do
            xml.Indicia(options[:smart_post_indicia] || 'PARCEL_SELECT')
            xml.HubId(options[:smart_post_hub_id] || 5902) # default to LA
          end

          build_rate_request_types_node(xml)
          xml.PackageCount(packages.size)
          build_packages_nodes(xml, packages, imperial)
        end
      end
    end
  end
  xml_builder.to_xml
end
build_rate_request_types_node(xml, type = 'ACCOUNT') click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 412
def build_rate_request_types_node(xml, type = 'ACCOUNT')
  xml.RateRequestTypes(type)
end
build_request_header(xml) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 439
def build_request_header(xml)
  xml.WebAuthenticationDetail do
    xml.UserCredential do
      xml.Key(@options[:key])
      xml.Password(@options[:password])
    end
  end

  xml.ClientDetail do
    xml.AccountNumber(@options[:account])
    xml.MeterNumber(@options[:login])
  end

  xml.TransactionDetail do
    xml.CustomerTransactionId(@options[:transaction_id] || 'ActiveShipping') # TODO: Need to do something better with this...
  end
end
build_shipment_request(origin, destination, packages, options = {}) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 194
def build_shipment_request(origin, destination, packages, options = {})
  imperial = location_uses_imperial(origin)

  xml_builder = Nokogiri::XML::Builder.new do |xml|
    xml.ProcessShipmentRequest(xmlns: 'http://fedex.com/ws/ship/v13') do
      build_request_header(xml)
      build_version_node(xml, 'ship', 13, 0 ,0)

      xml.RequestedShipment do
        xml.ShipTimestamp(ship_timestamp(options[:turn_around_time]).iso8601(0))
        xml.DropoffType('REGULAR_PICKUP')
        xml.ServiceType(options[:service_type] || 'FEDEX_GROUND')
        xml.PackagingType('YOUR_PACKAGING')

        xml.Shipper do
          build_contact_address_nodes(xml, options[:shipper] || origin)
        end

        xml.Recipient do
          build_contact_address_nodes(xml, destination)
        end

        xml.Origin do
          build_contact_address_nodes(xml, origin)
        end

        xml.ShippingChargesPayment do
          xml.PaymentType('SENDER')
          xml.Payor do
            build_shipment_responsible_party_node(xml, options[:shipper] || origin)
          end
        end

        xml.LabelSpecification do
          xml.LabelFormatType('COMMON2D')
          xml.ImageType(options[:label_format] || 'PNG')
          xml.LabelStockType(options[:label_stock_type] || DEFAULT_LABEL_STOCK_TYPE)
        end

        xml.RateRequestTypes('ACCOUNT')

        xml.PackageCount(packages.size)
        packages.each do |package|
          xml.RequestedPackageLineItems do
            xml.GroupPackageCount(1)
            build_package_weight_node(xml, package, imperial)
            build_package_dimensions_node(xml, package, imperial)

            # Reference Numbers
            reference_numbers = Array(package.options[:reference_numbers])
            if reference_numbers.size > 0
              reference_numbers.each do |reference_number_info|
                xml.CustomerReferences do
                  xml.CustomerReferenceType(reference_number_info[:type] || "CUSTOMER_REFERENCE")
                  xml.Value(reference_number_info[:value])
                end
              end
            end

            xml.SpecialServicesRequested do
              xml.SpecialServiceTypes("SIGNATURE_OPTION")
              xml.SignatureOptionDetail do
                xml.OptionType(SIGNATURE_OPTION_CODES[package.options[:signature_option] || :default_for_service])
              end
            end
          end
        end
      end
    end
  end
  xml_builder.to_xml
end
build_shipment_responsible_party_node(xml, origin) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 284
def build_shipment_responsible_party_node(xml, origin)
  xml.ResponsibleParty do
    xml.AccountNumber(@options[:account])
    xml.Contact do
      xml.PersonName(origin.name)
      xml.CompanyName(origin.company)
      xml.PhoneNumber(origin.phone)
    end
  end
end
build_shipping_charges_payment_node(xml, freight_options) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 361
def build_shipping_charges_payment_node(xml, freight_options)
  xml.ShippingChargesPayment do
    xml.PaymentType(freight_options[:payment_type])
    xml.Payor do
      xml.ResponsibleParty do
        # TODO: case of different freight account numbers?
        xml.AccountNumber(freight_options[:account])
      end
    end
  end
end
build_tracking_request(tracking_number, options = {}) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 416
def build_tracking_request(tracking_number, options = {})
  xml_builder = Nokogiri::XML::Builder.new do |xml|
    xml.TrackRequest(xmlns: 'http://fedex.com/ws/track/v7') do
      build_request_header(xml)
      build_version_node(xml, 'trck', 7, 0, 0)

      xml.SelectionDetails do
        xml.PackageIdentifier do
          xml.Type(PACKAGE_IDENTIFIER_TYPES[options[:package_identifier_type] || 'tracking_number'])
          xml.Value(tracking_number)
        end

        xml.ShipDateRangeBegin(options[:ship_date_range_begin])         if options[:ship_date_range_begin]
        xml.ShipDateRangeEnd(options[:ship_date_range_end])             if options[:ship_date_range_end]
        xml.TrackingNumberUniqueIdentifier(options[:unique_identifier]) if options[:unique_identifier]
      end

      xml.ProcessingOptions('INCLUDE_DETAILED_SCANS')
    end
  end
  xml_builder.to_xml
end
build_version_node(xml, service_id, major, intermediate, minor) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 457
def build_version_node(xml, service_id, major, intermediate, minor)
  xml.Version do
    xml.ServiceId(service_id)
    xml.Major(major)
    xml.Intermediate(intermediate)
    xml.Minor(minor)
  end
end
business_day?(date) click to toggle source

Transit times for FedEx® Ground do not include Saturdays, Sundays, or holidays.

# File lib/active_shipping/carriers/fedex.rb, line 577
def business_day?(date)
  (1..5).include?(date.wday)
end
business_days_from(date, days, is_home_delivery=false) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 560
def business_days_from(date, days, is_home_delivery=false)
  future_date = date
  count       = 0

  while count < days
    future_date += 1.day
    if is_home_delivery
      count += 1 if home_delivery_business_day?(future_date)
    else
      count += 1 if business_day?(future_date)
    end
  end

  future_date
end
commit(request, test = false) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 739
def commit(request, test = false)
  ssl_post(test ? TEST_URL : LIVE_URL, request.gsub("\n", ''))
end
delivery_range_from(transit_time, max_transit_time, delivery_timestamp, is_home_delivery, options) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 533
def delivery_range_from(transit_time, max_transit_time, delivery_timestamp, is_home_delivery, options)
  delivery_range = [delivery_timestamp, delivery_timestamp]

  # if there's no delivery timestamp but we do have a transit time, use it
  if delivery_timestamp.blank? && transit_time.present?
    transit_range  = parse_transit_times([transit_time, max_transit_time.presence || transit_time])
    pickup_date = options[:pickup_date] || ship_date(options[:turn_around_time])

    delivery_range = transit_range.map { |days| business_days_from(pickup_date, days, is_home_delivery) }
  end

  delivery_range
end
extract_address(document, possible_node_names) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 752
def extract_address(document, possible_node_names)
  node = nil
  args = {}
  possible_node_names.each do |name|
    node = document.at(name)
    break if node
  end

  if node
    args[:country] =
      node.at('CountryCode').try(:text) ||
      ActiveUtils::Country.new(:alpha2 => 'ZZ', :name => 'Unknown or Invalid Territory', :alpha3 => 'ZZZ', :numeric => '999')

    args[:province] = node.at('StateOrProvinceCode').try(:text) || 'unknown'
    args[:city] = node.at('City').try(:text) || 'unknown'
  end

  Location.new(args)
end
extract_timestamp(document, node_name) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 772
def extract_timestamp(document, node_name)
  if timestamp_node = document.at(node_name)
    if timestamp_node.text =~ /\A(\d{4}-\d{2}-\d{2})T00:00:00\Z/
      Date.parse($1)
    else
      Time.parse(timestamp_node.text)
    end
  end
end
has_freight?(options) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 391
def has_freight?(options)
  options[:freight] && options[:freight].present?
end
home_delivery_business_day?(date) click to toggle source

Transit times for FedEx® Home Delivery, do not include Sundays, Mondays, or holidays.

# File lib/active_shipping/carriers/fedex.rb, line 582
def home_delivery_business_day?(date)
  (2..6).include?(date.wday)
end
location_uses_imperial(location) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 793
def location_uses_imperial(location)
  %w(US LR MM).include?(location.country_code(:alpha2))
end
parse_rate_response(origin, destination, packages, response, options) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 479
def parse_rate_response(origin, destination, packages, response, options)
  xml = build_document(response, 'RateReply')

  success = response_success?(xml)
  message = response_message(xml)

  if success
    missing_xml_field = false
    rate_estimates = xml.root.css('> RateReplyDetails').map do |rated_shipment|
      begin
        service_code = rated_shipment.at('ServiceType').text
        is_saturday_delivery = rated_shipment.at('AppliedOptions').try(:text) == 'SATURDAY_DELIVERY'
        service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code

        transit_time = rated_shipment.at('TransitTime').text if ["FEDEX_GROUND", "GROUND_HOME_DELIVERY"].include?(service_code)
        max_transit_time = rated_shipment.at('MaximumTransitTime').try(:text) if service_code == "FEDEX_GROUND"

        delivery_timestamp = rated_shipment.at('DeliveryTimestamp').try(:text)
        delivery_range = delivery_range_from(transit_time, max_transit_time, delivery_timestamp, (service_code == "GROUND_HOME_DELIVERY"), options)

        currency = rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').text

        RateEstimate.new(origin, destination, @@name,
             self.class.service_name_for_code(service_type),
             :service_code => service_code,
             :total_price => rated_shipment.at('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').text.to_f,
             :currency => currency,
             :packages => packages,
             :delivery_range => delivery_range)
      rescue NoMethodError
        missing_xml_field = true
        nil
      end
    end

    rate_estimates = rate_estimates.compact
    logger.warn("[FedexParseRateError] Some fields where missing in the response: #{response}") if logger && missing_xml_field

    if rate_estimates.empty?
      success = false
      if missing_xml_field
        message = "The response from the carrier contained errors and could not be treated"
      else
        message = "No shipping rates could be found for the destination address" if message.blank?
      end
    end

  else
    rate_estimates = []
  end

  RateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :request => last_request, :log_xml => options[:log_xml])
end
parse_ship_response(response) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 547
def parse_ship_response(response)
  xml = build_document(response, 'ProcessShipmentReply')
  success = response_success?(xml)
  message = response_message(xml)

  response_info = Hash.from_xml(response)
  tracking_number = xml.css("CompletedPackageDetails TrackingIds TrackingNumber").text
  base_64_image = xml.css("Label Image").text

  labels = [Label.new(tracking_number, Base64.decode64(base_64_image))]
  LabelResponse.new(success, message, response_info, {labels: labels})
end
parse_tracking_response(response, options) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 586
def parse_tracking_response(response, options)
  xml = build_document(response, 'TrackReply')

  success = response_success?(xml)
  message = response_message(xml)

  if success
    tracking_details_root = xml.at('CompletedTrackDetails')
    success = response_success?(tracking_details_root)
    message = response_message(tracking_details_root)
  end

  if success
    delivery_signature = nil
    shipment_events = []

    all_tracking_details = xml.root.xpath('CompletedTrackDetails/TrackDetails')
    tracking_details = case all_tracking_details.length
      when 1
        all_tracking_details.first
      when 0
        message = "The response did not contain tracking details"
        return TrackingResponse.new(
          false,
          message,
          Hash.from_xml(response),
          carrier: @@name,
          xml: response,
          request: last_request
        )
      else
        all_unique_identifiers = xml.root.xpath('CompletedTrackDetails/TrackDetails/TrackingNumberUniqueIdentifier').map(&:text)
        message = "Multiple matches were found. Specify a unqiue identifier: #{all_unique_identifiers.join(', ')}"
        return TrackingResponse.new(
          false,
          message,
          Hash.from_xml(response),
          carrier: @@name,
          xml: response,
          request: last_request
        )
    end

    first_notification = tracking_details.at('Notification')
    severity = first_notification.at('Severity').text
    if severity == 'ERROR' || severity == 'FAILURE'
      message = first_notification.try(:text)
      code = first_notification.at('Code').try(:text)
      case code
      when *TRANSIENT_TRACK_RESPONSE_CODES
        raise ActiveShipping::ShipmentNotFound, first_notification.at('Message').text
      else
        raise ActiveShipping::ResponseContentError, StandardError.new(first_notification.at('Message').text)
      end
    end

    tracking_number = tracking_details.at('TrackingNumber').text
    status_detail = tracking_details.at('StatusDetail')
    if status_detail.blank?
      status_code, status, status_description, delivery_signature = nil
    else
      status_code = status_detail.at('Code').try(:text)
      status_description = status_detail.at('AncillaryDetails/ReasonDescription').try(:text) || status_detail.at('Description').try(:text)

      status = TRACKING_STATUS_CODES[status_code]

      if status_code == 'DL' && tracking_details.at('AvailableImages').try(:text) == 'SIGNATURE_PROOF_OF_DELIVERY'
        delivery_signature = tracking_details.at('DeliverySignatureName').try(:text)
      end
    end

    origin = if origin_node = tracking_details.at('OriginLocationAddress')
      Location.new(
        country: origin_node.at('CountryCode').text,
        province: origin_node.at('StateOrProvinceCode').text,
        city: origin_node.at('City').text
      )
    end

    destination = extract_address(tracking_details, DELIVERY_ADDRESS_NODE_NAMES)
    shipper_address = extract_address(tracking_details, SHIPPER_ADDRESS_NODE_NAMES)

    ship_time = extract_timestamp(tracking_details, 'ShipTimestamp')
    actual_delivery_time = extract_timestamp(tracking_details, 'ActualDeliveryTimestamp')
    scheduled_delivery_time = extract_timestamp(tracking_details, 'EstimatedDeliveryTimestamp')

    tracking_details.xpath('Events').each do |event|
      address  = event.at('Address')
      next if address.nil? || address.at('CountryCode').nil?

      city     = address.at('City').try(:text)
      state    = address.at('StateOrProvinceCode').try(:text)
      zip_code = address.at('PostalCode').try(:text)
      country  = address.at('CountryCode').try(:text)

      location = Location.new(:city => city, :state => state, :postal_code => zip_code, :country => country)
      description = event.at('EventDescription').text
      type_code = event.at('EventType').text

      time          = Time.parse(event.at('Timestamp').text)
      zoneless_time = time.utc

      shipment_events << ShipmentEvent.new(description, zoneless_time, location, description, type_code)
    end

    shipment_events = shipment_events.sort_by(&:time)
  end

  TrackingResponse.new(
    success,
    message,
    Hash.from_xml(response),
    carrier: @@name,
    xml: response,
    request: last_request,
    status: status,
    status_code: status_code,
    status_description: status_description,
    ship_time: ship_time,
    scheduled_delivery_date: scheduled_delivery_time,
    actual_delivery_date: actual_delivery_time,
    delivery_signature: delivery_signature,
    shipment_events: shipment_events,
    shipper_address: (shipper_address.nil? || shipper_address.unknown?) ? nil : shipper_address,
    origin: origin,
    destination: destination,
    tracking_number: tracking_number
  )
end
parse_transit_times(times) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 743
def parse_transit_times(times)
  results = []
  times.each do |day_count|
    days = TRANSIT_TIMES.index(day_count.to_s.chomp)
    results << days.to_i
  end
  results
end
response_message(document) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 732
def response_message(document)
  notifications = document.at('Notifications')
  return "" if notifications.nil?

  "#{notifications.at('Severity').text} - #{notifications.at('Code').text}: #{notifications.at('Message').text}"
end
response_success?(document) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 726
def response_success?(document)
  highest_severity = document.at('HighestSeverity')
  return false if highest_severity.nil?
  %w(SUCCESS WARNING NOTE).include?(highest_severity.text)
end
ship_date(delay_in_hours) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 721
def ship_date(delay_in_hours)
  delay_in_hours ||= 0
  (Time.now + delay_in_hours.hours).to_date
end
ship_timestamp(delay_in_hours) click to toggle source
# File lib/active_shipping/carriers/fedex.rb, line 716
def ship_timestamp(delay_in_hours)
  delay_in_hours ||= 0
  Time.now + delay_in_hours.hours
end