Accessing FedEx Web Services From Ruby/Rails

I recently played around with adding FedEx shipping services to the active_shipping plugin and found a bit of confusion online in regards to how to hit FedEx depending upon the style of service being exposed. Somewhat confusingly, FedEx has non-SOAP services as well as SOAP services that you can hit restfully in an HTTPS Post. For the SOAP service, FedEx has very specific requirements about how to formulate the payload and HTTP headers that took a bit of tinkering to get working. Non-SOAP FedEx support was recently added to active_shipping prior to the completion of my work, but I figured I would post if for those who want to use what I think are newer SOAP services.

Without much further ado, here are the basics for creating a tracking request:


TEST_DOMAIN = 'gatewaybeta.fedex.com'

def find_tracking_info(tracking_number, options={})
        options = @options.update(options)
        tracking_request = build_tracking_request(tracking_number, options)
        response = commit(:track, save_request(tracking_request),
                          (options[:test] || false))
        parse_tracking_response(response, options)
  end

protected
   def build_access_request
        xml_request = XmlNode.new('ns:WebAuthenticationDetail') do |access|
          access << XmlNode.new('ns:UserCredential') do |user|
             user << XmlNode.new('ns:Key', @options[:key])
             user << XmlNode.new('ns:Password', @options[:password])
          end
        end
        xml_request
      end

      def build_client_detail
        xml_request = XmlNode.new('ns:ClientDetail') do |client_detail|
          client_detail << XmlNode.new('ns:AccountNumber', @options[:account])
          client_detail << XmlNode.new('ns:MeterNumber', @options[:meter])
        end
        xml_request
      end

     def build_tracking_request(tracking_number, options={})
        xml_request = XmlNode.new('ns:TrackRequest',
                         :'xmlns:ns'=>"http://fedex.com/ws/track/v2",
                         :'xmlns:xsi'=>"http://www.w3.org/2001/XMLSchema-instance",
                         :'xsi:schemaLocation'=>"http://fedex.com/ws/track/v2") do |root_node|
          root_node << build_access_request
          root_node << build_client_detail
          root_node << XmlNode.new('ns:TransactionDetail') do |detail|
            detail << XmlNode.new('ns:CustomerTransactionId', 'Ground Track')
          end
          root_node <<  XmlNode.new('ns:Version') do |version|
            version << XmlNode.new('ns:ServiceId', 'trck')
            version << XmlNode.new('ns:Major', '2')
            version << XmlNode.new('ns:Intermediate', '0')
            version << XmlNode.new('ns:Minor', '0')
          end
           root_node <<  XmlNode.new('ns:PackageIdentifier') do |package|
              package << XmlNode.new('ns:Value', tracking_number.to_s)
              package << XmlNode.new('ns:Type', "TRACKING_NUMBER_OR_DOORTAG")
          end
        end
        xml_request.to_xml
      end

     def commit(action, request, test = false)
        http = Net::HTTP.new((test ? TEST_DOMAIN : LIVE_DOMAIN),
                                (USE_SSL[action] ? 443 : 80 ))
        http.use_ssl = USE_SSL[action]

        headers = {
          'Referer' => 'me',
          'Port' => '443',
          'Accept' => "image/gif, image/jpeg, image/pjpeg, text/plain, text/html, */*",
          'Content-Type' => 'image/gif'
        }

        http.verify_mode = OpenSSL::SSL::VERIFY_NONE if USE_SSL[action]
        response = http.start do |http|
          http.request_post ('/xml', request, headers)
        end
        response.body
      end

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Leave a Reply