fedex 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -8,4 +8,7 @@ doc/*
8
8
  pkg/*
9
9
  .yardopts
10
10
  fedex_credentials.yml
11
- spec/vcr
11
+ spec/vcr
12
+ *.bbprojectd
13
+ .DS_Store
14
+ hacks
data/Readme.md CHANGED
@@ -116,7 +116,7 @@ Fedex provides multiple total values; `total_net_charge` is the final amount you
116
116
  @rate_zone="51">
117
117
  ```
118
118
 
119
- ### ** Generate a shipping label(PDF) **
119
+ ### ** Generate a shipping label (PDF) **
120
120
 
121
121
  To create a label for a shipment:
122
122
 
@@ -129,8 +129,84 @@ label = fedex.label(:filename => "my_dir/example.pdf",
129
129
  :shipping_details => shipping_details)
130
130
  ```
131
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.
132
+ ### ** Generate a shipping label in any available format **
133
+
134
+ Change the filename extension and pass a label_specification hash. For example:
135
+
136
+ ```ruby
137
+ example_spec = {
138
+ :image_type => "EPL2",
139
+ :label_stock_type => "STOCK_4X6"
140
+ }
141
+
142
+ label = fedex.label(:filename => "my_dir/example_epl2.pcx",
143
+ :shipper=>shipper,
144
+ :recipient => recipient,
145
+ :packages => packages,
146
+ :service_type => "FEDEX_GROUND",
147
+ :shipping_details => shipping_details,
148
+ :label_specification => example_spec)
149
+ ```
150
+
151
+ ### ** Tracking a shipment **
152
+
153
+ To track a shipment:
154
+
155
+ ```ruby
156
+ tracking_info = fedex.track(:tracking_number => "1234567890123")
157
+
158
+ tracking_info.tracking_number
159
+ # => "1234567890123"
160
+
161
+ tracking_info.status
162
+ # => "Delivered"
163
+
164
+ tracking_info.events.first.description
165
+ # => "On FedEx vehicle for delivery"
166
+ ```
167
+
168
+ ### ** Tracking a shipment **
169
+
170
+ To track a shipment:
171
+
172
+ ```ruby
173
+ tracking_info = fedex.track(:tracking_number => "1234567890123")
174
+
175
+ tracking_info.tracking_number
176
+ # => "1234567890123"
177
+
178
+ tracking_info.status
179
+ # => "Delivered"
180
+
181
+ tracking_info.events.first.description
182
+ # => "On FedEx vehicle for delivery"
183
+ ```
184
+
185
+ ### ** Verifying an address **
186
+
187
+ To verify an address is valid and deliverable:
188
+
189
+ ```ruby
190
+
191
+ address = {
192
+ :street => "5 Elm Street",
193
+ :city => "Norwalk",
194
+ :state => "CT",
195
+ :postal_code => "06850",
196
+ :country => "USA"
197
+ }
198
+
199
+ address_result = fedex.validate_address(:address => address)
200
+
201
+ address_result.residential
202
+ # => true
203
+
204
+ address_result.score
205
+ # => 100
206
+
207
+ address_result.postal_code
208
+ # => "06850-3901"
209
+ ```
134
210
 
135
211
  # Services/Options Available
136
212
 
@@ -143,6 +219,8 @@ Fedex::Shipment::DROP_OFF_TYPES
143
219
  # Contributors:
144
220
  - [jazminschroeder](http://github.com/jazminschroeder) (Jazmin Schroeder)
145
221
  - [parndt](https://github.com/parndt) (Philip Arndt)
222
+ - [mmell](https://github.com/mmell) (Michael Mell)
223
+ - [jordanbyron](https://github.com/jordanbyron) (Jordan Byron)
146
224
 
147
225
  # Copyright/License:
148
226
  Copyright 2011 [Jazmin Schroeder](http://jazminschroeder.com)
data/fedex.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Jazmin Schroeder"]
10
10
  s.email = ["jazminschroeder@gmail.com"]
11
11
  s.homepage = "https://github.com/jazminschroeder/fedex"
12
- s.summary = %q{Fedex Rate Webservice}
13
- s.description = %q{Ruby Library to use Fedex Web Services(version 10)}
12
+ s.summary = %q{Fedex Web Services}
13
+ s.description = %q{Provides an interface to Fedex Web Services(version 10) - shipping rates, generate labels and address validation}
14
14
 
15
15
  s.rubyforge_project = "fedex"
16
16
 
@@ -0,0 +1,30 @@
1
+ module Fedex
2
+ class Address
3
+
4
+ attr_reader :changes, :score, :confirmed, :available, :status, :residential,
5
+ :business, :street_lines, :city, :state, :province_code,
6
+ :postal_code, :country_code
7
+
8
+ def initialize(options)
9
+ @changes = options[:changes]
10
+ @score = options[:score].to_i
11
+ @confirmed = options[:delivery_point_validation] == "CONFIRMED"
12
+ @available = options[:delivery_point_validation] != "UNAVAILABLE"
13
+
14
+ @status = options[:residential_status]
15
+ @residential = status == "RESIDENTIAL"
16
+ @business = status == "BUSINESS"
17
+
18
+ address = options[:address]
19
+
20
+ @street_lines = address[:street_lines]
21
+ @city = address[:city]
22
+ @state = address[:state_or_province_code]
23
+ @province_code = address[:state_or_province_code]
24
+ @postal_code = address[:postal_code]
25
+ @country_code = address[:country_code]
26
+
27
+ @options = options
28
+ end
29
+ end
30
+ end
data/lib/fedex/helpers.rb CHANGED
@@ -2,9 +2,10 @@ module Fedex
2
2
  module Helpers
3
3
 
4
4
  private
5
- # String to CamelCase
6
- def camelize(str)
7
- str.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
5
+ # String or :symbol to CamelCase
6
+ def camelize(s)
7
+ # s.to_s.split('_').map { |e| e.capitalize }.join('')
8
+ s.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
8
9
  end
9
10
 
10
11
  # Helper method to validate required fields
@@ -16,4 +17,4 @@ module Fedex
16
17
  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
18
  end
18
19
  end
19
- end
20
+ end
data/lib/fedex/label.rb CHANGED
@@ -1,11 +1,52 @@
1
+ require 'base64'
2
+ require 'pathname'
3
+
1
4
  module Fedex
2
5
  class Label
3
- attr_accessor :options
6
+ attr_accessor :options, :image, :response_details
4
7
 
5
8
  # Initialize Fedex::Label Object
6
9
  # @param [Hash] options
7
- def initialize(options = {})
8
- @options = options
10
+ def initialize(label_details = {})
11
+ @response_details = label_details[:process_shipment_reply]
12
+ package_details = label_details[:process_shipment_reply][:completed_shipment_detail][:completed_package_details]
13
+ @options = package_details[:label]
14
+ @options[:format] = label_details[:format]
15
+ @options[:tracking_number] = package_details[:tracking_ids][:tracking_number]
16
+ @options[:file_name] = label_details[:file_name]
17
+
18
+ @image = Base64.decode64(options[:parts][:image]) if has_image?
19
+
20
+ if file_name = @options[:file_name]
21
+ save(file_name, false)
22
+ end
23
+ end
24
+
25
+ def name
26
+ [tracking_number, format].join('.')
27
+ end
28
+
29
+ def format
30
+ options[:format]
31
+ end
32
+
33
+ def tracking_number
34
+ options[:tracking_number]
35
+ end
36
+
37
+ def has_image?
38
+ options[:parts] && options[:parts][:image]
39
+ end
40
+
41
+ def save(path, append_name = true)
42
+ return unless has_image?
43
+
44
+ full_path = Pathname.new(path)
45
+ full_path = full_path.join(name) if append_name
46
+
47
+ File.open(full_path, 'wb') do|f|
48
+ f.write(@image)
49
+ end
9
50
  end
10
51
  end
11
52
  end
@@ -0,0 +1,92 @@
1
+ require 'fedex/request/base'
2
+ require 'fedex/address'
3
+ require 'fileutils'
4
+
5
+ module Fedex
6
+ module Request
7
+ class Address < Base
8
+ def initialize(credentials, options={})
9
+ requires!(options, :address)
10
+ @credentials = credentials
11
+ @address = options[:address]
12
+ end
13
+
14
+ def process_request
15
+ api_response = self.class.post(api_url, :body => build_xml)
16
+ puts api_response if @debug == true
17
+ response = parse_response(api_response)
18
+ if success?(response)
19
+ options = response[:address_validation_reply][:address_results][:proposed_address_details]
20
+
21
+ Fedex::Address.new(options)
22
+ else
23
+ error_message = if response[:address_validation_reply]
24
+ [response[:address_validation_reply][:notifications]].flatten.first[:message]
25
+ else
26
+ api_response["Fault"]["detail"]["fault"]["reason"]
27
+ end rescue $1
28
+ raise RateError, error_message
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Build xml Fedex Web Service request
35
+ def build_xml
36
+ builder = Nokogiri::XML::Builder.new do |xml|
37
+ xml.AddressValidationRequest(:xmlns => "http://fedex.com/ws/addressvalidation/v2"){
38
+ add_web_authentication_detail(xml)
39
+ add_client_detail(xml)
40
+ add_version(xml)
41
+ add_request_timestamp(xml)
42
+ add_address_validation_options(xml)
43
+ add_address_to_validate(xml)
44
+ }
45
+ end
46
+ builder.doc.root.to_xml
47
+ end
48
+
49
+ def add_request_timestamp(xml)
50
+ timestamp = Time.now
51
+
52
+ # Calculate current timezone offset manually.
53
+ # Ruby <= 1.9.2 does not support this in Time#strftime
54
+ #
55
+ utc_offest = "#{timestamp.gmt_offset < 0 ? "-" : "+"}%02d:%02d" %
56
+ (timestamp.gmt_offset / 60).abs.divmod(60)
57
+ timestamp = timestamp.strftime("%Y-%m-%dT%H:%M:%S") + utc_offest
58
+
59
+ xml.RequestTimestamp timestamp
60
+ end
61
+
62
+ def add_address_validation_options(xml)
63
+ xml.Options{
64
+ xml.CheckResidentialStatus true
65
+ }
66
+ end
67
+
68
+ def add_address_to_validate(xml)
69
+ xml.AddressesToValidate{
70
+ xml.Address{
71
+ xml.StreetLines @address[:street]
72
+ xml.City @address[:city]
73
+ xml.StateOrProvinceCode @address[:state]
74
+ xml.PostalCode @address[:postal_code]
75
+ xml.CountryCode @address[:country]
76
+ }
77
+ }
78
+ end
79
+
80
+ def service
81
+ { :id => 'aval', :version => 2 }
82
+ end
83
+
84
+ # Successful request
85
+ def success?(response)
86
+ response[:address_validation_reply] &&
87
+ %w{SUCCESS WARNING NOTE}.include?(response[:address_validation_reply][:highest_severity])
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -17,9 +17,6 @@ module Fedex
17
17
  # Fedex Production URL
18
18
  PRODUCTION_URL = "https://gateway.fedex.com:443/xml/"
19
19
 
20
- # Fedex Version number for the Fedex service used
21
- VERSION = 10
22
-
23
20
  # List of available Service Types
24
21
  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
22
 
@@ -48,6 +45,7 @@ module Fedex
48
45
  requires!(options, :shipper, :recipient, :packages, :service_type)
49
46
  @credentials = credentials
50
47
  @shipper, @recipient, @packages, @service_type, @customs_clearance, @debug = options[:shipper], options[:recipient], options[:packages], options[:service_type], options[:customs_clearance], options[:debug]
48
+ @debug = ENV['DEBUG'] == 'true'
51
49
  @shipping_options = options[:shipping_options] ||={}
52
50
  end
53
51
 
@@ -73,14 +71,18 @@ module Fedex
73
71
  xml.ClientDetail{
74
72
  xml.AccountNumber @credentials.account_number
75
73
  xml.MeterNumber @credentials.meter
74
+ xml.Localization{
75
+ xml.LanguageCode 'en' # English
76
+ xml.LocaleCode 'us' # United States
77
+ }
76
78
  }
77
79
  end
78
80
 
79
81
  # Add Version to xml request, using the latest version V10 Sept/2011
80
82
  def add_version(xml)
81
83
  xml.Version{
82
- xml.ServiceId service_id
83
- xml.Major VERSION
84
+ xml.ServiceId service[:id]
85
+ xml.Major service[:version]
84
86
  xml.Intermediate 0
85
87
  xml.Minor 0
86
88
  }
@@ -164,12 +166,22 @@ module Fedex
164
166
  xml.Units package[:weight][:units]
165
167
  xml.Value package[:weight][:value]
166
168
  }
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
- }
169
+ if package[:dimensions]
170
+ xml.Dimensions{
171
+ xml.Length package[:dimensions][:length]
172
+ xml.Width package[:dimensions][:width]
173
+ xml.Height package[:dimensions][:height]
174
+ xml.Units package[:dimensions][:units]
175
+ }
176
+ end
177
+ if package[:customer_refrences]
178
+ xml.CustomerReferences{
179
+ package[:customer_refrences].each do |value|
180
+ xml.CustomerReferenceType 'CUSTOMER_REFERENCE'
181
+ xml.Value value
182
+ end
183
+ }
184
+ end
173
185
  }
174
186
  end
175
187
  end
@@ -177,7 +189,7 @@ module Fedex
177
189
  # Add customs clearance(for international shipments)
178
190
  def add_customs_clearance(xml)
179
191
  xml.CustomsClearanceDetail{
180
- customs_to_xml(xml, @customs_clearance)
192
+ hash_to_xml(xml, @customs_clearance)
181
193
  }
182
194
  end
183
195
 
@@ -192,22 +204,22 @@ module Fedex
192
204
  raise NotImplementedError, "Override build_xml in subclass"
193
205
  end
194
206
 
195
- # Build nodes dinamically from the provided customs clearance hash
196
- def customs_to_xml(xml, hash)
207
+ # Build xml nodes dynamically from the hash keys and values
208
+ def hash_to_xml(xml, hash)
197
209
  hash.each do |key, value|
210
+ element = camelize(key)
198
211
  if value.is_a?(Hash)
199
- xml.send "#{camelize(key.to_s)}" do |x|
200
- customs_to_xml(x, value)
212
+ xml.send element do |x|
213
+ hash_to_xml(x, value)
201
214
  end
202
215
  elsif value.is_a?(Array)
203
- node = key
204
216
  value.each do |v|
205
- xml.send "#{camelize(node.to_s)}" do |x|
206
- customs_to_xml(x, v)
207
- end
208
- end
217
+ xml.send element do |x|
218
+ hash_to_xml(x, v)
219
+ end
220
+ end
209
221
  else
210
- xml.send "#{camelize(key.to_s)}", value unless key.is_a?(Hash)
222
+ xml.send element, value
211
223
  end
212
224
  end
213
225
  end
@@ -217,7 +229,7 @@ module Fedex
217
229
  response = sanitize_response_keys(response)
218
230
  end
219
231
 
220
- # Recursively sanitizes the response object by clenaing up any hash keys.
232
+ # Recursively sanitizes the response object by cleaning up any hash keys.
221
233
  def sanitize_response_keys(response)
222
234
  if response.is_a?(Hash)
223
235
  response.inject({}) { |result, (key, value)| result[underscorize(key).to_sym] = sanitize_response_keys(value); result }
@@ -228,8 +240,9 @@ module Fedex
228
240
  end
229
241
  end
230
242
 
231
- def service_id
232
- 'crs'
243
+ def service
244
+ raise NotImplementedError,
245
+ "Override service in subclass: {:id => 'service', :version => 1}"
233
246
  end
234
247
 
235
248
  # Use GROUND_HOME_DELIVERY for shipments going to a residential address within the US.
@@ -248,4 +261,4 @@ module Fedex
248
261
 
249
262
  end
250
263
  end
251
- end
264
+ end