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 # # * shipper_streets - street part of the address, separate multiple streets with a new line, dont include blank lines. # * shipper_city - city part of the address. # * shipper_state_ - state part of the address, use state abreviations. # * shipper_postal_code - postal code part of the address. Ex: zip for the US. # * shipper_country - country code part of the address. FedEx expects abbreviations, but Shippinglogic will convert full names to abbreviations for you. # * shipper_residential - a boolean value representing if the address is redential or not (default: false) # # === Recipient options # # * recipient_streets - street part of the address, separate multiple streets with a new line, dont include blank lines. # * recipient_city - city part of the address. # * recipient_state - state part of the address, use state abreviations. # * recipient_postal_code - postal code part of the address. Ex: zip for the US. # * recipient_country - country code part of the address. FedEx expects abbreviations, but Shippinglogic will convert full names to abbreviations for you. # * recipient_residential - 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. # # * packaging_type - one of PACKAGE_TYPES. (default: YOUR_PACKAGING) # * package_count - the number of packages in your shipment. (default: 1) # * package_weight - a single packages weight. # * package_weight_units - either LB or KG. (default: LB) # * package_length - a single packages length, only required if using YOUR_PACKAGING for packaging_type. # * package_width - a single packages width, only required if using YOUR_PACKAGING for packaging_type. # * package_height - a single packages height, only required if using YOUR_PACKAGING for packaging_type. # * package_dimension_units - either IN or CM. (default: IN) # # === Monetary options # # * currency_type - the type of currency. (default: nil, because FedEx will default to your account preferences) # * insured_value - the value you want to insure, if any. (default: nil) # * payment_type - one of PAYMENT_TYPES. (default: SENDER) # * payor_account_number - if the account paying for this ship is different than the account you specified then # you can specify that here. (default: your account number) # * payor_country - the country code for the account number. (default: US) # # === Delivery options # # * ship_time - a Time object representing when you want to ship the package. (default: Time.now) # * service_type - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all # available services. (default: nil) # * delivery_deadline - whether or not to include estimated transit times. (default: true) # * dropoff_type - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP) # * special_services_requested - any exceptions or special services FedEx needs to be aware of, this should be # one or more of SPECIAL_SERVICES. (default: nil) # # === Misc options # # * rate_request_types - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT) # * include_transit_times - 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 # #, # @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, :delivered_by, :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] delivered_by = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp]) if meets_deadline?(delivered_by) service = Service.new service.name = details[:service_type].titleize service.type = details[:service_type] service.saturday = details[:applied_options] == "SATURDAY_DELIVERY" service.delivered_by = delivered_by service.rate = BigDecimal.new(cost[:amount]) service.currency = cost[:currency] service end end.compact end def meets_deadline?(delivered_by) return true if !delivery_deadline delivered_by && delivered_by <= delivery_deadline end end end end