|
|
require 'net/http'
require 'net/https'
require 'hpricot'
require 'ostruct'
module FEDEX
class FedExError < StandardError
end
class Client
def initialize(options)
@prefs = options
@logger = RAILS_DEFAULT_LOGGER
end
def rate_list(to_zip, weight, boxes, home_delivery = false)
response = self.rate_request(@prefs[:origin_zip], to_zip, weight, boxes, home_delivery)
Hpricot(response).search('entry').collect do |service|
OpenStruct.new(
#:label => Services[service.at("service").inner_html],
:service_code => service.at("service").inner_html,
:cost => service.at("estimatedcharges/discountedcharges/netcharge").inner_html.to_f
) #|| service.at("transportationcharges/monetaryvalue").inner_html.to_f)
end.sort_by(&:cost)
end
Services = {
"priority" => "PRIORITYOVERNIGHT",
"2day" => "FEDEX2DAY",
"standard_overnight" => "STANDARDOVERNIGHT",
"first_overnight" => "FIRSTOVERNIGHT",
"express_saver" => "FEDEXEXPRESSSAVER",
"1day_freight" => "FEDEX1DAYFREIGHT",
"2day_freight" => "FEDEX2DAYFREIGHT",
"3day_freight" => "FEDEX3DAYFREIGHT",
"international_priority" => "INTERNATIONALPRIORITY",
"international_economy" => "INTERNATIONALECONOMY",
"international_first" => "INTERNATIONALFIRST",
"international_priority_freight" => "INTERNATIONALPRIORITYFREIGHT",
"international_economy_freight" => "INTERNATIONALECONOMYFREIGHT",
"home_delivery" => "GROUNDHOMEDELIVERY",
"ground_service" => "FEDEXGROUND",
"international_ground_service" => "INTERNATIONALGROUND"
}
protected
def send_request(data)
uri = URI.parse(@prefs[:url])
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@logger.info "Sending to FedEx:\n #{data}" if @prefs[:debug]
body = http.post(uri.request_uri, data).body
@logger.info "Received from FedEx:\n #{body}" if @prefs[:debug]
if @error = Hpricot(body).at('error').inner_html rescue nil
raise FedExError, @error
end
body
end
def rate_request(from_zip, destination, weight, boxes, home_delivery = false)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
xml.FDXRateAvailableServicesRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXRateAvailableServicesRequest.xsd') do
xml.RequestHeader do
xml.AccountNumber @prefs[:account]
xml.MeterNumber @prefs[:meter]
#xml.CarrierCode 'ANY'
end
#xml.Service 'FEDEX2DAY'
#xml.ShipDate '2008-10-30'
xml.ReturnShipmentIndicator 'NONRETURN'
xml.DropoffType 'REGULARPICKUP'
xml.Packaging 'YOURPACKAGING'
xml.WeightUnits 'LBS'
xml.Weight weight
xml.ListRate true #tells fedex to return list rates as well as discounted rates
xml.OriginAddress do
xml.StateOrProvinceCode 'CA'
xml.PostalCode from_zip
xml.CountryCode 'US'
end
if home_delivery == true
xml.HomeDelivery do
xml.Type 'APPOINTMENT'
end
end
xml.DestinationAddress do
xml.StateOrProvinceCode self.state_from_zip(destination)
xml.PostalCode destination
xml.CountryCode "US"
end
xml.Payment do
xml.PayorType 'SENDER'
end
xml.PackageCount boxes
end
self.send_request(xml.target!)
end
def state_from_zip(zip)
zip = zip.to_i
{
(99500...99929) => "AK",
(35000...36999) => "AL",
(71600...72999) => "AR",
(75502...75505) => "AR",
(85000...86599) => "AZ",
(90000...96199) => "CA",
(80000...81699) => "CO",
(6000...6999) => "CT",
(20000...20099) => "DC",
(20200...20599) => "DC",
(19700...19999) => "DE",
(32000...33999) => "FL",
(34100...34999) => "FL",
(30000...31999) => "GA",
(96700...96798) => "HI",
(96800...96899) => "HI",
(50000...52999) => "IA",
(83200...83899) => "ID",
(60000...62999) => "IL",
(46000...47999) => "IN",
(66000...67999) => "KS",
(40000...42799) => "KY",
(45275...45275) => "KY",
(70000...71499) => "LA",
(71749...71749) => "LA",
(1000...2799) => "MA",
(20331...20331) => "MD",
(20600...21999) => "MD",
(3801...3801) => "ME",
(3804...3804) => "ME",
(3900...4999) => "ME",
(48000...49999) => "MI",
(55000...56799) => "MN",
(63000...65899) => "MO",
(38600...39799) => "MS",
(59000...59999) => "MT",
(27000...28999) => "NC",
(58000...58899) => "ND",
(68000...69399) => "NE",
(3000...3803) => "NH",
(3809...3899) => "NH",
(7000...8999) => "NJ",
(87000...88499) => "NM",
(89000...89899) => "NV",
(400...599) => "NY",
(6390...6390) => "NY",
(9000...14999) => "NY",
(43000...45999) => "OH",
(73000...73199) => "OK",
(73400...74999) => "OK",
(97000...97999) => "OR",
(15000...19699) => "PA",
(2800...2999) => "RI",
(6379...6379) => "RI",
(29000...29999) => "SC",
(57000...57799) => "SD",
(37000...38599) => "TN",
(72395...72395) => "TN",
(73300...73399) => "TX",
(73949...73949) => "TX",
(75000...79999) => "TX",
(88501...88599) => "TX",
(84000...84799) => "UT",
(20105...20199) => "VA",
(20301...20301) => "VA",
(20370...20370) => "VA",
(22000...24699) => "VA",
(5000...5999) => "VT",
(98000...99499) => "WA",
(49936...49936) => "WI",
(53000...54999) => "WI",
(24700...26899) => "WV",
(82000...83199) => "WY"
}.each do |range, state|
return state if range.include? zip
end
raise ShippingError, "Invalid zip code"
end
end
end
|