module Shippinglogic class FedEx # An interface to the rate services provided by FedEx. Allows you to get an array of rates from fedex for a shipment, # or a single rate for a specific service. # # == Options # === Shipper options # # * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines. # * <tt>shipper_city</tt> - city part of the address. # * <tt>shipper_state_</tt> - state part of the address, use state abreviations. # * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US. # * <tt>shipper_country</tt> - country code part of the address, use abbreviations, ex: 'US' # * <tt>shipper_residential</tt> - a boolean value representing if the address is redential or not (default: false) # # === Recipient options # # * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines. # * <tt>recipient_city</tt> - city part of the address. # * <tt>recipient_state</tt> - state part of the address, use state abreviations. # * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US. # * <tt>recipient_country</tt> - country code part of the address, use abbreviations, ex: 'US' # * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false) # # === Packaging options # # One thing to note is that FedEx does support multiple package shipments. The problem is that all of the packages must be identical. # FedEx specifically notes in their documentation that mutiple package specifications are not allowed. So your only option for a # multi package shipment is to increase the package_count option and keep the dimensions and weight the same for all packages. Then again, # the documentation for the FedEx web services is terrible, so I could be wrong. Any tests I tried resulted in an error though. # # * <tt>packaging_type</tt> - one of PACKAGE_TYPES. (default: YOUR_PACKAGING) # * <tt>package_count</tt> - the number of packages in your shipment. (default: 1) # * <tt>package_weight</tt> - a single packages weight. # * <tt>package_weight_units</tt> - either LB or KG. (default: LB) # * <tt>package_length</tt> - a single packages length. # * <tt>package_width</tt> - a single packages width. # * <tt>package_height</tt> - a single packages height. # * <tt>package_dimension_units</tt> - either IN or CM. (default: IN) # # === Monetary options # # * <tt>currency_type</tt> - the type of currency. (default: nil, because FedEx will default to your account preferences) # * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil) # * <tt>payment_type</tt> - one of PAYMENT_TYPES. (default: SENDER) # * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then # you can specify that here. (default: your account number) # * <tt>payor_country</tt> - the country code for the account number. (default: US) # # === Delivery options # # * <tt>ship_time</tt> - a Time object representing when you want to ship the package. (default: Time.now) # * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all # available services. (default: nil) # * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true) # * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP) # * <tt>special_services_requested</tt> - any exceptions or special services FedEx needs to be aware of, this should be # one or more of SPECIAL_SERVICES. (default: nil) # # === Misc options # # * <tt>rate_request_types</tt> - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT) # * <tt>include_transit_times</tt> - whether or not to include estimated transit times. (default: true) # # == Simple Example # # Here is a very simple example. Mix and match the options above to get more accurate rates: # # fedex = Shippinglogic::FedEx.new(key, password, account, meter) # rates = fedex.rate( # :shipper_postal_code => "10007", # :shipper_country => "US", # :recipient_postal_code => "75201", # :recipient_country_code => "US", # :package_weight => 24, # :package_length => 12, # :package_width => 12, # :package_height => 12 # ) # # rates.first # #<Shippinglogic::FedEx::Rates::Rate @currency="USD", @name="First Overnight", @cost=#<BigDecimal:19ea290,'0.7001E2',8(8)>, # @deadline=Fri Aug 07 08:00:00 -0400 2009, @type="FIRST_OVERNIGHT", @saturday=false> # # # to show accessor methods # rates.first.name # # => "First Overnight" class Rate < Service # Each rate result is an object of this class class Service; attr_accessor :name, :type, :saturday, :deadline, :rate, :currency; end VERSION = {:major => 6, :intermediate => 0, :minor => 0} # shipper options attribute :shipper_streets, :string attribute :shipper_city, :string attribute :shipper_state, :string attribute :shipper_postal_code, :string attribute :shipper_country, :string, :modifier => :country_code attribute :shipper_residential, :boolean, :default => false # recipient options attribute :recipient_streets, :string attribute :recipient_city, :string attribute :recipient_state, :string attribute :recipient_postal_code, :string attribute :recipient_country, :string, :modifier => :country_code attribute :recipient_residential, :boolean, :default => false # packaging options attribute :packaging_type, :string, :default => "YOUR_PACKAGING" attribute :package_count, :integer, :default => 1 attribute :package_weight, :float attribute :package_weight_units, :string, :default => "LB" attribute :package_length, :integer attribute :package_width, :integer attribute :package_height, :integer attribute :package_dimension_units, :string, :default => "IN" # monetary options attribute :currency_type, :string attribute :insured_value, :big_decimal attribute :payment_type, :string, :default => "SENDER" attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.account } attribute :payor_country, :string # delivery options attribute :ship_time, :datetime, :default => lambda { |rate| Time.now } attribute :service_type, :string attribute :delivery_deadline, :datetime attribute :dropoff_type, :string, :default => "REGULAR_PICKUP" attribute :special_services_requested, :array # misc options attribute :rate_request_types, :array, :default => ["ACCOUNT"] attribute :include_transit_times, :boolean, :default => true private def target @target ||= parse_response(request(build_request)) end def build_request b = builder xml = b.RateRequest(:xmlns => "http://fedex.com/ws/rate/v#{VERSION[:major]}") do build_authentication(b) build_version(b, "crs", VERSION[:major], VERSION[:intermediate], VERSION[:minor]) b.ReturnTransitAndCommit include_transit_times b.SpecialServicesRequested special_services_requested.join(",") if special_services_requested.any? b.RequestedShipment do b.ShipTimestamp ship_time.xmlschema if ship_time b.ServiceType service_type if service_type b.DropoffType dropoff_type if dropoff_type b.PackagingType packaging_type if packaging_type b.TotalInsuredValue insured_value if insured_value b.Shipper { build_address(b, :shipper) } b.Recipient { build_address(b, :recipient) } b.ShippingChargesPayment do b.PaymentType payment_type if payment_type b.Payor do b.AccountNumber payor_account_number if payor_account_number b.CountryCode payor_country if payor_country end end b.RateRequestTypes rate_request_types.join(",") if rate_request_types build_package(b) end end end def parse_response(response) return [] if !response[:rate_reply_details] response[:rate_reply_details].collect do |details| shipment_detail = details[:rated_shipment_details].is_a?(Array) ? details[:rated_shipment_details].first : details[:rated_shipment_details] cost = shipment_detail[:shipment_rate_detail][:total_net_charge] deadline = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp]) if meets_deadline?(deadline) service = Service.new service.name = details[:service_type].titleize service.type = details[:service_type] service.saturday = details[:applied_options] == "SATURDAY_DELIVERY" service.deadline = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp]) service.rate = BigDecimal.new(cost[:amount]) service.currency = cost[:currency] service end end.compact end def meets_deadline?(deadline) return true if !delivery_deadline deadline && deadline <= delivery_deadline end end end end