class Omniship::FedEx
:key is your developer API key :password is your API password :account is your FedEx
account number :login is your meter number
Constants
- CarrierCodes
- DropoffTypes
- LIVE_URL
- PackageIdentifierTypes
- PackageTypes
- PaymentTypes
- ServiceTypes
- TEST_URL
Public Class Methods
service_name_for_code(service_code)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 85 def self.service_name_for_code(service_code) ServiceTypes[service_code] || begin name = service_code.downcase.split('_').collect{|word| word.capitalize }.join(' ') "FedEx #{name.sub(/Fedex /, '')}" end end
Public Instance Methods
create_shipment(origin, destination, packages, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 105 def create_shipment(origin, destination, packages, options={}) options = @options.merge(options) options[:test] = options[:test].nil? ? true : options[:test] packages = Array(packages) ship_request = build_ship_request(origin, destination, packages, options) response = commit(save_request(ship_request.gsub("\n", "")), options[:test]) parse_ship_response(response, options) end
delete_shipment(tracking_number, shipment_type, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 114 def delete_shipment(tracking_number, shipment_type, options={}) options = @options.merge(options) delete_shipment_request = build_delete_request(tracking_number, shipment_type, options) response = commit(save_request(delete_shipment_request.gsub("\n", "")), options[:test]) parse_delete_response(response, options) end
find_rates(origin, destination, packages, options = {})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 96 def find_rates(origin, destination, packages, options = {}) options = @options.merge(options) options[:test] = options[:test].nil? ? true : options[:test] packages = Array(packages) rate_request = build_rate_request(origin, destination, packages, options) response = commit(save_request(rate_request.gsub("\n", "")), options[:test]) parse_rate_response(origin, destination, packages, response, options) end
find_tracking_info(tracking_number, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 121 def find_tracking_info(tracking_number, options={}) options = @options.update(options) tracking_request = build_tracking_request(tracking_number, options) response = commit(save_request(tracking_request), (options[:test] || false)).gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>') parse_tracking_response(response, options) end
requirements()
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 92 def requirements [:key, :account, :meter, :password] end
Protected Instance Methods
build_access_request(xml)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 338 def build_access_request(xml) xml.WebAuthenticationDetail { xml.UserCredential { xml.Key @options[:key] xml.Password @options[:password] } } xml.ClientDetail { xml.AccountNumber @options[:account] xml.MeterNumber @options[:meter] } xml.TransactionDetail { xml.CustomerTransactionId 'Omniship' # TODO: Need to do something better with this... } end
build_delete_request(tracking_number, shipment_type, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 293 def build_delete_request(tracking_number, shipment_type, options={}) builder = Nokogiri::XML::Builder.new do |xml| xml.DeleteShipmentRequest('xmlns' => 'http://fedex.com/ws/ship/v12') { build_access_request(xml) xml.Version { xml.ServiceId "ship" xml.Major "12" xml.Intermediate "0" xml.Minor "0" } xml.ShipTimestamp options[:ship_timestamp] if options[:ship_timestamp] xml.TrackingId { xml.TrackingIdType shipment_type xml.TrackingNumber tracking_number } xml.DeletionControl options[:deletion_type] || "DELETE_ALL_PACKAGES" } end builder.to_xml end
build_location_node(name, location, xml)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 356 def build_location_node(name, location, xml) for name in name xml.send(name) { xml.Contact { xml.PersonName location.name unless location.name == "" || location.name == nil xml.CompanyName location.company unless location.company == "" || location.name == nil xml.PhoneNumber location.phone } xml.Address { xml.StreetLines location.address1 xml.StreetLines location.address2 unless location.address2.nil? xml.City location.city xml.StateOrProvinceCode location.state xml.PostalCode location.postal_code xml.CountryCode location.country_code(:alpha2) xml.Residential true unless location.commercial? } } end end
build_rate_request(origin, destination, packages, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 129 def build_rate_request(origin, destination, packages, options={}) imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2)) builder = Nokogiri::XML::Builder.new do |xml| xml.RateRequest('xmlns' => 'http://fedex.com/ws/rate/v12') { build_access_request(xml) xml.Version { xml.ServiceId "crs" xml.Major "12" xml.Intermediate "0" xml.Minor "0" } xml.ReturnTransitAndCommit options[:return_transit_and_commit] || false xml.VariableOptions "SATURDAY_DELIVERY" xml.RequestedShipment { xml.ShipTimestamp options[:ship_date] || DateTime.now.strftime xml.DropoffType options[:dropoff_type] || 'REGULAR_PICKUP' xml.PackagingType options[:packaging_type] || 'YOUR_PACKAGING' build_location_node(['Shipper'], (options[:shipper] || origin), xml) build_location_node(['Recipient'], destination, xml) if options[:shipper] && options[:shipper] != origin build_location_node(['Origin'], origin, xml) end xml.RateRequestTypes 'ACCOUNT' xml.PackageCount packages.size packages.each do |pkg| xml.RequestedPackageLineItems { xml.SequenceNumber 1 xml.GroupPackageCount 1 xml.Weight { xml.Units (imperial ? 'LB' : 'KG') xml.Value ((imperial ? pkg.weight : pkg.weight/2.2).to_f) } xml.SpecialServicesRequested { if options[:without_signature] xml.SpecialServiceTypes "SIGNATURE_OPTION" xml.SignatureOptionDetail { xml.OptionType "NO_SIGNATURE_REQUIRED" } end } # xml.Dimensions { # [:length, :width, :height].each do |axis| # name = axis.to_s.capitalize # value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f*1000).round/1000.0 # xml.name value # end # xml.Units (imperial ? 'IN' : 'CM') # } } end } } end builder.to_xml end
build_ship_request(origin, destination, packages, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 186 def build_ship_request(origin, destination, packages, options={}) imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2)) builder = Nokogiri::XML::Builder.new do |xml| xml.ProcessShipmentRequest('xmlns' => 'http://fedex.com/ws/ship/v12') { build_access_request(xml) xml.Version { xml.ServiceId "ship" xml.Major "12" xml.Intermediate "0" xml.Minor "0" } xml.RequestedShipment { xml.ShipTimestamp options[:ship_date] || DateTime.now.strftime xml.DropoffType options[:dropoff_type] || 'REGULAR_PICKUP' xml.ServiceType options[:service_type] || 'GROUND_HOME_DELIVERY' xml.PackagingType options[:package_type] || 'YOUR_PACKAGING' build_location_node(["Shipper"], (options[:shipper] || origin), xml) build_location_node(["Recipient"], destination, xml) if options[:shipper] && options[:shipper] != origin build_location_node(["Origin"], origin, xml) end xml.ShippingChargesPayment { xml.PaymentType "SENDER" xml.Payor { xml.ResponsibleParty { xml.AccountNumber @options[:account] xml.Contact nil } } } xml.SpecialServicesRequested { xml.SpecialServiceTypes "SATURDAY_DELIVERY" if options[:saturday_delivery] if options[:return_shipment] xml.SpecialServiceTypes "RETURN_SHIPMENT" xml.ReturnShipmentDetail { xml.ReturnType "PRINT_RETURN_LABEL" } end } # TODO: Add options to change the label specifications xml.LabelSpecification { xml.LabelFormatType 'COMMON2D' xml.ImageType 'PDF' xml.LabelStockType 'PAPER_7X4.75' } xml.RateRequestTypes 'ACCOUNT' xml.PackageCount packages.size packages.each do |pkg| xml.RequestedPackageLineItems { xml.SequenceNumber 1 xml.Weight { xml.Units (imperial ? 'LB' : 'KG') xml.Value ((imperial ? pkg.weight : pkg.weight/2.2).to_f) } xml.SpecialServicesRequested { if options[:without_signature] xml.SpecialServiceTypes "SIGNATURE_OPTION" xml.SignatureOptionDetail { xml.OptionType "NO_SIGNATURE_REQUIRED" } end } # xml.Dimensions { # [:length, :width, :height].each do |axis| # name = axis.to_s.capitalize # value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f*1000).round/1000.0 # xml.send name, value.to_s # end # xml.Units (imperial ? 'IN' : 'CM') # } } end if !!@options[:notifications] xml.SpecialServicesRequested { xml.SpecialServiceTypes "EMAIL_NOTIFICATION" xml.EmailNotificationDetail { xml.PersonalMessage # Personal Message to be sent to all recipients @options[:notifications].each do |email| xml.Recipients { xml.EmailAddress email.address xml.NotificationEventsRequested { xml.EmailNotificationEventType{ xml.ON_DELIVERY if email.on_delivery xml.ON_EXCEPTION if email.on_exception xml.ON_SHIPMENT if email.on_shipment xml.ON_TENDER if email.on_tender } } xml.Format email.format || "HTML" # options are "HTML" "Text" "Wireless" xml.Localization { xml.Language email.language || "EN" # Default to EN (English) xml.LocaleCode email.locale_code if !email.locale_code.nil? } } end xml.EMailNotificationAggregationType @options[:notification_aggregation_type] if @options.has_key?(:notification_aggregation_type) } } end } } end builder.to_xml end
build_tracking_request(tracking_number, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 314 def build_tracking_request(tracking_number, options={}) xml_request = XmlNode.new('TrackRequest', 'xmlns' => 'http://fedex.com/ws/track/v3') do |root_node| root_node << build_request_header # Version root_node << XmlNode.new('Version') do |version_node| version_node << XmlNode.new('ServiceId', 'trck') version_node << XmlNode.new('Major', '3') version_node << XmlNode.new('Intermediate', '0') version_node << XmlNode.new('Minor', '0') end root_node << XmlNode.new('PackageIdentifier') do |package_node| package_node << XmlNode.new('Value', tracking_number) package_node << XmlNode.new('Type', PackageIdentifierTypes[options['package_identifier_type'] || 'tracking_number']) end root_node << XmlNode.new('ShipDateRangeBegin', options['ship_date_range_begin']) if options['ship_date_range_begin'] root_node << XmlNode.new('ShipDateRangeEnd', options['ship_date_range_end']) if options['ship_date_range_end'] root_node << XmlNode.new('IncludeDetailedScans', 1) end xml_request.to_s end
commit(request, test = false)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 493 def commit(request, test = false) ssl_post(test ? TEST_URL : LIVE_URL, request) end
handle_uk_currency(currency)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 497 def handle_uk_currency(currency) currency =~ /UKL/i ? 'GBP' : currency end
parse_delete_response(response, options={})
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 423 def parse_delete_response(response, options={}) xml = Nokogiri::XML(response).remove_namespaces! success = response_success?(xml) message = response_message(xml) return [success, message] end
parse_rate_response(origin, destination, packages, response, options)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 377 def parse_rate_response(origin, destination, packages, response, options) xml = Nokogiri::XML(response).remove_namespaces! puts xml rate_estimates = [] success, message = nil success = response_success?(xml) message = response_message(xml) xml.xpath('//RateReplyDetails').each do |rate| rate_estimates << RateEstimate.new(origin, destination, @@name, :service_code => rate.xpath('ServiceType').text, :service_name => rate.xpath('AppliedOptions').text == "SATURDAY_DELIVERY" ? "#{self.class.service_name_for_code(rate.xpath('ServiceType').text + '_SATURDAY_DELIVERY')}".upcase : self.class.service_name_for_code(rate.xpath('ServiceType').text), :total_price => rate.xpath('RatedShipmentDetails').first.xpath('ShipmentRateDetail/TotalNetCharge/Amount').text.to_f, :currency => handle_uk_currency(rate.xpath('RatedShipmentDetails').first.xpath('ShipmentRateDetail/TotalNetCharge/Currency').text), :packages => packages, :delivery_date => rate.xpath('ServiceType').text == "FEDEX_GROUND" ? rate.xpath('TransitTime').text : rate.xpath('DeliveryTimestamp').text ) end if rate_estimates.empty? success = false message = "No shipping rates could be found for the destination address" if message.blank? 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, options)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 405 def parse_ship_response(response, options) xml = Nokogiri::XML(response).remove_namespaces! puts xml success = response_success?(xml) message = response_message(xml) label = nil tracking_number = nil if success label = xml.xpath("//Image").text tracking_number = xml.xpath("//TrackingNumber").text else success = false message = "Shipment was not succcessful." if message.blank? end ShipResponse.new(success, message, :tracking_number => tracking_number, :label_encoded => label) end
parse_tracking_response(response, options)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 430 def parse_tracking_response(response, options) xml = REXML::Document.new(response) root_node = xml.elements['TrackReply'] success = response_success?(xml) message = response_message(xml) if success tracking_number, origin, destination = nil shipment_events = [] tracking_details = root_node.elements['TrackDetails'] tracking_number = tracking_details.get_text('TrackingNumber').to_s destination_node = tracking_details.elements['DestinationAddress'] destination = Address.new( :country => destination_node.get_text('CountryCode').to_s, :province => destination_node.get_text('StateOrProvinceCode').to_s, :city => destination_node.get_text('City').to_s ) tracking_details.elements.each('Events') do |event| address = event.elements['Address'] city = address.get_text('City').to_s state = address.get_text('StateOrProvinceCode').to_s zip_code = address.get_text('PostalCode').to_s country = address.get_text('CountryCode').to_s next if country.blank? location = Address.new(:city => city, :state => state, :postal_code => zip_code, :country => country) description = event.get_text('EventDescription').to_s # for now, just assume UTC, even though it probably isn't time = Time.parse("#{event.get_text('Timestamp').to_s}") zoneless_time = Time.utc(time.year, time.month, time.mday, time.hour, time.min, time.sec) shipment_events << ShipmentEvent.new(description, zoneless_time, location) end shipment_events = shipment_events.sort_by(&:time) end TrackingResponse.new(success, message, Hash.from_xml(response), :xml => response, :request => last_request, :shipment_events => shipment_events, :destination => destination, :tracking_number => tracking_number ) end
response_message(xml)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 489 def response_message(xml) "#{xml.xpath('//Notifications/Severity').text} - #{xml.xpath('//Notifications/Code').text}: #{xml.xpath('//Notifications/Message').text}" end
response_status_node(xml)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 481 def response_status_node(xml) xml.ProcessShipmentReply end
response_success?(xml)
click to toggle source
# File lib/omniship/carriers/fedex.rb, line 485 def response_success?(xml) %w{SUCCESS WARNING NOTE}.include? xml.xpath('//Notifications/Severity').text end