fedex 1.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ doc/*
8
8
  pkg/*
9
9
  .yardopts
10
10
  fedex_credentials.yml
11
+ spec/vcr
data/Readme.md CHANGED
@@ -2,91 +2,149 @@
2
2
 
3
3
  For more information visit [Fedex Web Services for Shipping](https://www.fedex.com/wpor/web/jsp/drclinks.jsp?links=wss/index.html).
4
4
 
5
- This version uses the Non-SOAP Web Services so there is no need to download the Fedex WSDL files, note however that you will need to apply for
6
- development/production credentials.
5
+ This version uses the Non-SOAP Web Services so there is no need to download the
6
+ Fedex WSDL files, note however that you will need to apply for development/production credentials.
7
7
 
8
8
  Note: This is work in progress make sure to test your results.
9
9
 
10
10
  # Installation:
11
- // Rails 3.x
12
- $ gem 'fedex'
13
-
14
- // Rails 2.x
15
- $ gem install fedex
11
+
12
+ Rails 3.x using Bundler's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'fedex'
16
+ ````
17
+
18
+ Rails 2.x or without Rails or Bundler:
19
+
20
+ ```ruby
21
+ gem install fedex
22
+ ```
16
23
 
17
24
  # Usage example:
18
-
25
+
19
26
  Define the shipper:
20
-
21
- shipper = { :name => "Sender",
22
- :company => "Company",
23
- :phone_number => "555-555-5555",
24
- :address => "Main Street",
25
- :city => "Harrison",
26
- :state => "AR",
27
- :postal_code => "72601",
28
- :country_code => "US" }
29
-
30
- Define the recipient:
31
-
32
- recipient = { :name => "Recipient",
33
- :company => "Company",
34
- :phone_number => "555-555-5555",
35
- :address => "Main Street",
36
- :city => "Franklin Park",
37
- :state => "IL",
38
- :postal_code => "60131",
39
- :country_code => "US",
40
- :residential => "false" }
41
- Define the packages(multiple packages in a single shipment are allowed):
42
- Note that all the Dimensions must be integers
43
-
44
- packages = []
45
- packages << { :weight => {:units => "LB", :value => 2},
46
- :dimensions => {:length => 10, :width => 5, :height => 4, :units => "IN" } }
47
- packages << { :weight => {:units => "LB", :value => 6},
48
- :dimensions => {:length => 5, :width => 5, :height => 4, :units => "IN" } }
49
-
50
- By Default packaging type is "YOUR PACKAGING" and the drop off type is "REGULAR PICKUP", if you need something different you can pass an extra hash for shipping details
51
-
52
- shipping_details = { :packaging_type => "YOUR_PACKAGING", :drop_off_type => "REGULAR_PICKUP" }
53
-
54
-
55
- Create a Fedex::Shipment object using your FedEx credentials; mode should be either production or development depending on what Fedex environment you want to use.
56
-
57
- require 'fedex'
58
- fedex = Fedex::Shipment.new(:key => 'xxx',
59
- :password => 'xxxx',
60
- :account_number => 'xxxx',
61
- :meter => 'xxx',
62
- :mode=>'production')
63
-
64
- rate = fedex.rate({:shipper=>shipper, :recipient => recipient, :packages => packages, :service_type => "FEDEX_GROUND", :shipping_details => shipping_details})
65
-
66
- Fedex provides multiple total values; total_net_charge is the final amount you are looking for.
67
-
68
- $ rate.total_net_charge => "34.03"
69
- # Complete response
70
- $ <Fedex::Rate:0x1019ba5f8
71
- @total_net_charge="34.03",
72
- @total_surcharges="1.93",
73
- @total_billing_weight="8.0 LB",
74
- @total_taxes="0.0",
75
- @rate_type="PAYOR_ACCOUNT_PACKAGE",
76
- @total_base_charge="32.1",
77
- @total_freight_discounts=nil,
78
- @total_net_freight="32.1",
79
- @rate_zone="51">
80
-
81
-
27
+
28
+ ```ruby
29
+ shipper = { :name => "Sender",
30
+ :company => "Company",
31
+ :phone_number => "555-555-5555",
32
+ :address => "Main Street",
33
+ :city => "Harrison",
34
+ :state => "AR",
35
+ :postal_code => "72601",
36
+ :country_code => "US" }
37
+ ```
38
+
39
+ Define the recipient:
40
+
41
+ ```ruby
42
+ recipient = { :name => "Recipient",
43
+ :company => "Company",
44
+ :phone_number => "555-555-5555",
45
+ :address => "Main Street",
46
+ :city => "Franklin Park",
47
+ :state => "IL",
48
+ :postal_code => "60131",
49
+ :country_code => "US",
50
+ :residential => "false" }
51
+ ```
52
+
53
+ Define the packages; multiple packages in a single shipment are allowed:
54
+ Note that all the dimensions must be integers.
55
+
56
+ ```ruby
57
+ packages = []
58
+ packages << {
59
+ :weight => {:units => "LB", :value => 2},
60
+ :dimensions => {:length => 10, :width => 5, :height => 4, :units => "IN" }
61
+ }
62
+ packages << {
63
+ :weight => {:units => "LB", :value => 6},
64
+ :dimensions => {:length => 5, :width => 5, :height => 4, :units => "IN" }
65
+ }
66
+ ```
67
+
68
+ By default packaging type is "YOUR PACKAGING" and the drop off type is "REGULAR PICKUP".
69
+ If you need something different you can pass an extra hash for shipping details
70
+
71
+ ```ruby
72
+ shipping_details = {
73
+ :packaging_type => "YOUR_PACKAGING",
74
+ :drop_off_type => "REGULAR_PICKUP"
75
+ }
76
+ ```
77
+
78
+ Create a `Fedex::Shipment` object using your FedEx credentials; mode should be
79
+ either production or development depending on what Fedex environment you want to use.
80
+
81
+ ```ruby
82
+ require 'fedex'
83
+ fedex = Fedex::Shipment.new(:key => 'xxx',
84
+ :password => 'xxxx',
85
+ :account_number => 'xxxx',
86
+ :meter => 'xxx',
87
+ :mode => 'production')
88
+ ```
89
+
90
+ ### ** Getting Shipping Rates **
91
+
92
+ To find a shipping rate:
93
+
94
+ ```ruby
95
+ rate = fedex.rate(:shipper=>shipper,
96
+ :recipient => recipient,
97
+ :packages => packages,
98
+ :service_type => "FEDEX_GROUND",
99
+ :shipping_details => shipping_details)
100
+ ```
101
+
102
+ Fedex provides multiple total values; `total_net_charge` is the final amount you are looking for.
103
+
104
+ ```ruby
105
+ $ rate.total_net_charge => "34.03"
106
+ # Complete response
107
+ $ <Fedex::Rate:0x1019ba5f8
108
+ @total_net_charge="34.03",
109
+ @total_surcharges="1.93",
110
+ @total_billing_weight="8.0 LB",
111
+ @total_taxes="0.0",
112
+ @rate_type="PAYOR_ACCOUNT_PACKAGE",
113
+ @total_base_charge="32.1",
114
+ @total_freight_discounts=nil,
115
+ @total_net_freight="32.1",
116
+ @rate_zone="51">
117
+ ```
118
+
119
+ ### ** Generate a shipping label(PDF) **
120
+
121
+ To create a label for a shipment:
122
+
123
+ ```ruby
124
+ label = fedex.label(:filename => "my_dir/example.pdf",
125
+ :shipper=>shipper,
126
+ :recipient => recipient,
127
+ :packages => packages,
128
+ :service_type => "FEDEX_GROUND",
129
+ :shipping_details => shipping_details)
130
+ ```
131
+
132
+ The label will be saved to the file system as the filename you specify and is Adobe PDF format.
133
+ Note that you can currently print a label for a single package at a time.
134
+
82
135
  # Services/Options Available
83
136
 
84
- Fedex::Shipment::SERVICE_TYPES
85
- Fedex::Shipment::PACKAGING_TYPES
86
- Fedex::Shipment::DROP_OFF_TYPES
137
+ ```ruby
138
+ Fedex::Shipment::SERVICE_TYPES
139
+ Fedex::Shipment::PACKAGING_TYPES
140
+ Fedex::Shipment::DROP_OFF_TYPES
141
+ ````
87
142
 
88
- # Copyright/License:
89
- Copyright 2011 Jazmin Schroeder
143
+ # Contributors:
144
+ - [jazminschroeder](http://github.com/jazminschroeder) (Jazmin Schroeder)
145
+ - [parndt](https://github.com/parndt) (Philip Arndt)
90
146
 
91
- This gem is made available under the MIT license
147
+ # Copyright/License:
148
+ Copyright 2011 [Jazmin Schroeder](http://jazminschroeder.com)
92
149
 
150
+ This gem is made available under the MIT license.
data/fedex.gemspec CHANGED
@@ -13,9 +13,13 @@ Gem::Specification.new do |s|
13
13
  s.description = %q{Ruby Library to use Fedex Web Services(version 10)}
14
14
 
15
15
  s.rubyforge_project = "fedex"
16
- s.add_development_dependency "rspec", '~> 2.6.0'
16
+
17
17
  s.add_dependency 'httparty', '~> 0.8.0'
18
18
  s.add_dependency 'nokogiri', '~> 1.5.0'
19
+
20
+ s.add_development_dependency "rspec", '~> 2.9.0'
21
+ s.add_development_dependency 'vcr', '~> 2.0.0'
22
+ s.add_development_dependency 'fakeweb'
19
23
  # s.add_runtime_dependency "rest-client"
20
24
 
21
25
  s.files = `git ls-files`.split("\n")
@@ -23,5 +27,5 @@ Gem::Specification.new do |s|
23
27
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
28
  s.require_paths = ["lib"]
25
29
 
26
-
30
+
27
31
  end
data/lib/fedex.rb CHANGED
@@ -1,54 +1,55 @@
1
- require "fedex/shipment"
2
- require "fedex/rate"
1
+ require 'fedex/shipment'
2
+
3
3
  # Get shipping rates trough Fedex Web Services
4
- #
4
+ #
5
5
  # In order to use the API you will need to apply for developer/production credentials,
6
6
  # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
7
7
  #
8
8
  # ===Usage example
9
9
  # #Use your own Fedex Keys
10
- # fedex = Fedex::Shipment.new(:key => 'xxx',
11
- # :password => 'xxxx',
12
- # :account_number => 'xxxx',
13
- # :meter => 'xxx',
10
+ # fedex = Fedex::Shipment.new(:key => 'xxx',
11
+ # :password => 'xxxx',
12
+ # :account_number => 'xxxx',
13
+ # :meter => 'xxx',
14
14
  # :mode=>['production'|'development'])
15
- # shipper = {:name => "Sender",
16
- # :company => "Company",
17
- # :phone_number => "555-555-5555",
18
- # :address => "Main Street",
19
- # :city => "Harrison",
20
- # :state => "AR",
21
- # :postal_code => "72601",
15
+ # shipper = {:name => "Sender",
16
+ # :company => "Company",
17
+ # :phone_number => "555-555-5555",
18
+ # :address => "Main Street",
19
+ # :city => "Harrison",
20
+ # :state => "AR",
21
+ # :postal_code => "72601",
22
22
  # :country_code => "US" }
23
23
  #
24
- # recipient = { :name => "Recipient",
25
- # :company => "Company",
26
- # :phone_number => "555-555-5555",
27
- # :address => "Main Street",
28
- # :city => "City",
29
- # :state => "ST",
30
- # :postal_code => "55555",
31
- # :country_code => "US",
24
+ # recipient = { :name => "Recipient",
25
+ # :company => "Company",
26
+ # :phone_number => "555-555-5555",
27
+ # :address => "Main Street",
28
+ # :city => "City",
29
+ # :state => "ST",
30
+ # :postal_code => "55555",
31
+ # :country_code => "US",
32
32
  # :residential => "false" }
33
33
  # packages = []
34
- # packages << { :weight => {:units => "LB", :value => 2},
34
+ # packages << { :weight => {:units => "LB", :value => 2},
35
35
  # :dimensions => {:length => 10, :width => 5, :height => 4, :units => "IN" } }
36
- # packages << { :weight => {:units => "LB", :value => 6},
36
+ # packages << { :weight => {:units => "LB", :value => 6},
37
37
  # :dimensions => {:length => 5, :width => 5, :height => 4, :units => "IN" } }
38
38
  # # "YOUR PACKAGING" and "REGULAR PICKUP" are the default options for all shipments but you can easily change them by passing an extra hash for # shipping_options
39
- # shipping_options = { :packaging_type => "YOUR_PACKAGING", :drop_off_type => "REGULAR_PICKUP" }
39
+ # shipping_options = { :packaging_type => "YOUR_PACKAGING", :drop_off_type => "REGULAR_PICKUP" }
40
40
  # rate = fedex.rate({:shipper=>shipper, :recipient => recipient, :packages => packages, :service_type => "FEDEX_GROUND", :shipping_options => #shipping_options})
41
- #
42
- # $ <Fedex::Rate:0x1019ba5f8 @total_net_charge="34.03",
43
- # @total_surcharges="1.93",
44
- # @total_billing_weight="8.0 LB",
45
- # @total_taxes="0.0",
46
- # @rate_type="PAYOR_ACCOUNT_PACKAGE",
47
- # @total_base_charge="32.1",
48
- # @total_freight_discounts=nil,
49
- # @total_net_freight="32.1",
41
+ #
42
+ # $ <Fedex::Rate:0x1019ba5f8 @total_net_charge="34.03",
43
+ # @total_surcharges="1.93",
44
+ # @total_billing_weight="8.0 LB",
45
+ # @total_taxes="0.0",
46
+ # @rate_type="PAYOR_ACCOUNT_PACKAGE",
47
+ # @total_base_charge="32.1",
48
+ # @total_freight_discounts=nil,
49
+ # @total_net_freight="32.1",
50
50
  # @rate_zone="51">
51
51
  module Fedex
52
+ require 'fedex/version'
52
53
  #Exceptions: Fedex::RateError
53
54
  class RateError < StandardError; end
54
55
  end
@@ -0,0 +1,26 @@
1
+ require 'fedex/helpers'
2
+
3
+ module Fedex
4
+ class Credentials
5
+ include Helpers
6
+ attr_reader :key, :password, :account_number, :meter, :mode
7
+
8
+ # In order to use Fedex rates API you must first apply for a developer(and later production keys),
9
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
10
+ # @param [String] key - Fedex web service key
11
+ # @param [String] password - Fedex password
12
+ # @param [String] account_number - Fedex account_number
13
+ # @param [String] meter - Fedex meter number
14
+ # @param [String] mode - [development/production]
15
+ #
16
+ # return a Fedex::Credentials object
17
+ def initialize(options={})
18
+ requires!(options, :key, :password, :account_number, :meter, :mode)
19
+ @key = options[:key]
20
+ @password = options[:password]
21
+ @account_number = options[:account_number]
22
+ @meter = options[:meter]
23
+ @mode = options[:mode]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module Fedex
2
+ module Helpers
3
+
4
+ private
5
+ # String to CamelCase
6
+ def camelize(str)
7
+ str.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
8
+ end
9
+
10
+ # Helper method to validate required fields
11
+ def requires!(hash, *params)
12
+ params.each { |param| raise RateError, "Missing Required Parameter #{param}" if hash[param].nil? }
13
+ end
14
+
15
+ def underscorize(key) #:nodoc:
16
+ key.to_s.sub(/^(v[0-9]+|ns):/, "").gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module Fedex
2
+ class Label
3
+ attr_accessor :options
4
+
5
+ # Initialize Fedex::Label Object
6
+ # @param [Hash] options
7
+ def initialize(options = {})
8
+ @options = options
9
+ end
10
+ end
11
+ end
data/lib/fedex/rate.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  module Fedex
2
2
  # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for a complete list of values returned from the API
3
3
  #
4
- # Rate totals are contained in the node
4
+ # Rate totals are contained in the node
5
5
  # response[:rate_reply][:rate_reply_details][:rated_shipment_details]
6
6
  class Rate
7
- # Initialize Fedex::Rate Object
8
- # @param [Hash] options
7
+ # Initialize Fedex::Rate Object
8
+ # @param [Hash] options
9
9
  #
10
10
  #
11
- # return [Fedex::Rate Object]
11
+ # return [Fedex::Rate Object]
12
12
  # @rate_type #Type used for this specific set of rate data
13
13
  # @rate_zone #Indicates the rate zone used(based on origin and destination)
14
14
  # @total_billing_weight #The weight used to calculate these rates
@@ -29,8 +29,8 @@ module Fedex
29
29
  @total_net_freight = options[:total_net_freight][:amount]
30
30
  @total_surcharges = options[:total_surcharges][:amount]
31
31
  @total_base_charge = options[:total_base_charge][:amount]
32
- @total_net_fedex_charge = (options[:total_net_fe_dex_charge]||={})[:amount]
33
- @total_rebates = (options[:total_rebates]||={})[:amount]
32
+ @total_net_fedex_charge = (options[:total_net_fe_dex_charge]||{})[:amount]
33
+ @total_rebates = (options[:total_rebates]||{})[:amount]
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,251 @@
1
+ require 'httparty'
2
+ require 'nokogiri'
3
+ require 'fedex/helpers'
4
+ require 'fedex/rate'
5
+
6
+ module Fedex
7
+ module Request
8
+ class Base
9
+ include Helpers
10
+ include HTTParty
11
+ format :xml
12
+ # If true the rate method will return the complete response from the Fedex Web Service
13
+ attr_accessor :debug
14
+ # Fedex Text URL
15
+ TEST_URL = "https://gatewaybeta.fedex.com:443/xml/"
16
+
17
+ # Fedex Production URL
18
+ PRODUCTION_URL = "https://gateway.fedex.com:443/xml/"
19
+
20
+ # Fedex Version number for the Fedex service used
21
+ VERSION = 10
22
+
23
+ # List of available Service Types
24
+ SERVICE_TYPES = %w(EUROPE_FIRST_INTERNATIONAL_PRIORITY FEDEX_1_DAY_FREIGHT FEDEX_2_DAY FEDEX_2_DAY_AM FEDEX_2_DAY_FREIGHT FEDEX_3_DAY_FREIGHT FEDEX_EXPRESS_SAVER FEDEX_FIRST_FREIGHT FEDEX_FREIGHT_ECONOMY FEDEX_FREIGHT_PRIORITY FEDEX_GROUND FIRST_OVERNIGHT GROUND_HOME_DELIVERY INTERNATIONAL_ECONOMY INTERNATIONAL_ECONOMY_FREIGHT INTERNATIONAL_FIRST INTERNATIONAL_PRIORITY INTERNATIONAL_PRIORITY_FREIGHT PRIORITY_OVERNIGHT SMART_POST STANDARD_OVERNIGHT)
25
+
26
+ # List of available Packaging Type
27
+ PACKAGING_TYPES = %w(FEDEX_10KG_BOX FEDEX_25KG_BOX FEDEX_BOX FEDEX_ENVELOPE FEDEX_PAK FEDEX_TUBE YOUR_PACKAGING)
28
+
29
+ # List of available DropOffTypes
30
+ DROP_OFF_TYPES = %w(BUSINESS_SERVICE_CENTER DROP_BOX REGULAR_PICKUP REQUEST_COURIER STATION)
31
+
32
+ # Clearance Brokerage Type
33
+ CLEARANCE_BROKERAGE_TYPE = %w(BROKER_INCLUSIVE BROKER_INCLUSIVE_NON_RESIDENT_IMPORTER BROKER_SELECT BROKER_SELECT_NON_RESIDENT_IMPORTER BROKER_UNASSIGNED)
34
+
35
+ # Recipient Custom ID Type
36
+ RECIPIENT_CUSTOM_ID_TYPE = %w(COMPANY INDIVIDUAL PASSPORT)
37
+
38
+ # In order to use Fedex rates API you must first apply for a developer(and later production keys),
39
+ # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
40
+ # @param [String] key - Fedex web service key
41
+ # @param [String] password - Fedex password
42
+ # @param [String] account_number - Fedex account_number
43
+ # @param [String] meter - Fedex meter number
44
+ # @param [String] mode - [development/production]
45
+ #
46
+ # return a Fedex::Request::Base object
47
+ def initialize(credentials, options={})
48
+ requires!(options, :shipper, :recipient, :packages, :service_type)
49
+ @credentials = credentials
50
+ @shipper, @recipient, @packages, @service_type, @customs_clearance, @debug = options[:shipper], options[:recipient], options[:packages], options[:service_type], options[:customs_clearance], options[:debug]
51
+ @shipping_options = options[:shipping_options] ||={}
52
+ end
53
+
54
+ # Sends post request to Fedex web service and parse the response.
55
+ # Implemented by each subclass
56
+ def process_request
57
+ raise NotImplementedError, "Override process_request in subclass"
58
+ end
59
+
60
+ private
61
+ # Add web authentication detail information(key and password) to xml request
62
+ def add_web_authentication_detail(xml)
63
+ xml.WebAuthenticationDetail{
64
+ xml.UserCredential{
65
+ xml.Key @credentials.key
66
+ xml.Password @credentials.password
67
+ }
68
+ }
69
+ end
70
+
71
+ # Add Client Detail information(account_number and meter_number) to xml request
72
+ def add_client_detail(xml)
73
+ xml.ClientDetail{
74
+ xml.AccountNumber @credentials.account_number
75
+ xml.MeterNumber @credentials.meter
76
+ }
77
+ end
78
+
79
+ # Add Version to xml request, using the latest version V10 Sept/2011
80
+ def add_version(xml)
81
+ xml.Version{
82
+ xml.ServiceId service_id
83
+ xml.Major VERSION
84
+ xml.Intermediate 0
85
+ xml.Minor 0
86
+ }
87
+ end
88
+
89
+ # Add information for shipments
90
+ def add_requested_shipment(xml)
91
+ xml.RequestedShipment{
92
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
93
+ xml.ServiceType service_type
94
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
95
+ add_shipper(xml)
96
+ add_recipient(xml)
97
+ add_shipping_charges_payment(xml)
98
+ add_customs_clearance(xml) if @customs_clearance
99
+ xml.RateRequestTypes "ACCOUNT"
100
+ add_packages(xml)
101
+ }
102
+ end
103
+
104
+ # Add shipper to xml request
105
+ def add_shipper(xml)
106
+ xml.Shipper{
107
+ xml.Contact{
108
+ xml.PersonName @shipper[:name]
109
+ xml.CompanyName @shipper[:company]
110
+ xml.PhoneNumber @shipper[:phone_number]
111
+ }
112
+ xml.Address {
113
+ Array(@shipper[:address]).take(2).each do |address_line|
114
+ xml.StreetLines address_line
115
+ end
116
+ xml.City @shipper[:city]
117
+ xml.StateOrProvinceCode @shipper[:state]
118
+ xml.PostalCode @shipper[:postal_code]
119
+ xml.CountryCode @shipper[:country_code]
120
+ }
121
+ }
122
+ end
123
+
124
+ # Add recipient to xml request
125
+ def add_recipient(xml)
126
+ xml.Recipient{
127
+ xml.Contact{
128
+ xml.PersonName @recipient[:name]
129
+ xml.CompanyName @recipient[:company]
130
+ xml.PhoneNumber @recipient[:phone_number]
131
+ }
132
+ xml.Address {
133
+ Array(@recipient[:address]).take(2).each do |address_line|
134
+ xml.StreetLines address_line
135
+ end
136
+ xml.City @recipient[:city]
137
+ xml.StateOrProvinceCode @recipient[:state]
138
+ xml.PostalCode @recipient[:postal_code]
139
+ xml.CountryCode @recipient[:country_code]
140
+ xml.Residential @recipient[:residential]
141
+ }
142
+ }
143
+ end
144
+
145
+ # Add shipping charges to xml request
146
+ def add_shipping_charges_payment(xml)
147
+ xml.ShippingChargesPayment{
148
+ xml.PaymentType "SENDER"
149
+ xml.Payor{
150
+ xml.AccountNumber @credentials.account_number
151
+ xml.CountryCode @shipper[:country_code]
152
+ }
153
+ }
154
+ end
155
+
156
+ # Add packages to xml request
157
+ def add_packages(xml)
158
+ package_count = @packages.size
159
+ xml.PackageCount package_count
160
+ @packages.each do |package|
161
+ xml.RequestedPackageLineItems{
162
+ xml.GroupPackageCount 1
163
+ xml.Weight{
164
+ xml.Units package[:weight][:units]
165
+ xml.Value package[:weight][:value]
166
+ }
167
+ xml.Dimensions{
168
+ xml.Length package[:dimensions][:length]
169
+ xml.Width package[:dimensions][:width]
170
+ xml.Height package[:dimensions][:height]
171
+ xml.Units package[:dimensions][:units]
172
+ }
173
+ }
174
+ end
175
+ end
176
+
177
+ # Add customs clearance(for international shipments)
178
+ def add_customs_clearance(xml)
179
+ xml.CustomsClearanceDetail{
180
+ customs_to_xml(xml, @customs_clearance)
181
+ }
182
+ end
183
+
184
+ # Fedex Web Service Api
185
+ def api_url
186
+ @credentials.mode == "production" ? PRODUCTION_URL : TEST_URL
187
+ end
188
+
189
+ # Build xml Fedex Web Service request
190
+ # Implemented by each subclass
191
+ def build_xml
192
+ raise NotImplementedError, "Override build_xml in subclass"
193
+ end
194
+
195
+ # Build nodes dinamically from the provided customs clearance hash
196
+ def customs_to_xml(xml, hash)
197
+ hash.each do |key, value|
198
+ if value.is_a?(Hash)
199
+ xml.send "#{camelize(key.to_s)}" do |x|
200
+ customs_to_xml(x, value)
201
+ end
202
+ elsif value.is_a?(Array)
203
+ node = key
204
+ value.each do |v|
205
+ xml.send "#{camelize(node.to_s)}" do |x|
206
+ customs_to_xml(x, v)
207
+ end
208
+ end
209
+ else
210
+ xml.send "#{camelize(key.to_s)}", value unless key.is_a?(Hash)
211
+ end
212
+ end
213
+ end
214
+
215
+ # Parse response, convert keys to underscore symbols
216
+ def parse_response(response)
217
+ response = sanitize_response_keys(response)
218
+ end
219
+
220
+ # Recursively sanitizes the response object by clenaing up any hash keys.
221
+ def sanitize_response_keys(response)
222
+ if response.is_a?(Hash)
223
+ response.inject({}) { |result, (key, value)| result[underscorize(key).to_sym] = sanitize_response_keys(value); result }
224
+ elsif response.is_a?(Array)
225
+ response.collect { |result| sanitize_response_keys(result) }
226
+ else
227
+ response
228
+ end
229
+ end
230
+
231
+ def service_id
232
+ 'crs'
233
+ end
234
+
235
+ # Use GROUND_HOME_DELIVERY for shipments going to a residential address within the US.
236
+ def service_type
237
+ if @recipient[:residential].to_s =~ /true/i and @service_type =~ /GROUND/i and @recipient[:country_code] =~ /US/i
238
+ "GROUND_HOME_DELIVERY"
239
+ else
240
+ @service_type
241
+ end
242
+ end
243
+
244
+ # Successful request
245
+ def success?(response)
246
+ (!response[:rate_reply].nil? and %w{SUCCESS WARNING NOTE}.include? response[:rate_reply][:highest_severity])
247
+ end
248
+
249
+ end
250
+ end
251
+ end