fedex 1.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,94 @@
1
+ require 'fedex/request/base'
2
+ require 'fedex/label'
3
+ require 'fileutils'
4
+
5
+ module Fedex
6
+ module Request
7
+ class Label < Base
8
+ def initialize(credentials, options={})
9
+ super(credentials, options)
10
+ requires!(options, :filename)
11
+ @filename = options[:filename]
12
+ end
13
+
14
+ # Sends post request to Fedex web service and parse the response.
15
+ # A Fedex::Label object is created if the response is successful and
16
+ # a PDF file is created with the label at the specified location.
17
+ def process_request
18
+ api_response = self.class.post(api_url, :body => build_xml)
19
+ puts api_response if @debug == true
20
+ response = parse_response(api_response)
21
+ if success?(response)
22
+ label_details = response[:process_shipment_reply][:completed_shipment_detail][:completed_package_details][:label]
23
+
24
+ create_pdf(label_details)
25
+ Fedex::Label.new(label_details)
26
+ else
27
+ error_message = if response[:process_shipment_reply]
28
+ [response[:process_shipment_reply][:notifications]].flatten.first[:message]
29
+ else
30
+ api_response["Fault"]["detail"]["fault"]["reason"]
31
+ end rescue $1
32
+ raise RateError, error_message
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Add information for shipments
39
+ def add_requested_shipment(xml)
40
+ xml.RequestedShipment{
41
+ xml.ShipTimestamp Time.now.utc.iso8601(2)
42
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
43
+ xml.ServiceType service_type
44
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
45
+ add_shipper(xml)
46
+ add_recipient(xml)
47
+ add_shipping_charges_payment(xml)
48
+ add_customs_clearance(xml) if @customs_clearance
49
+ xml.LabelSpecification {
50
+ xml.LabelFormatType "COMMON2D"
51
+ xml.ImageType "PDF"
52
+ }
53
+ xml.RateRequestTypes "ACCOUNT"
54
+ add_packages(xml)
55
+ }
56
+ end
57
+
58
+ # Build xml Fedex Web Service request
59
+ def build_xml
60
+ builder = Nokogiri::XML::Builder.new do |xml|
61
+ xml.ProcessShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v10"){
62
+ add_web_authentication_detail(xml)
63
+ add_client_detail(xml)
64
+ add_version(xml)
65
+ add_requested_shipment(xml)
66
+ }
67
+ end
68
+ builder.doc.root.to_xml
69
+ end
70
+
71
+ def create_pdf(label_details)
72
+ [label_details[:parts]].flatten.each do |part|
73
+ if image = (Base64.decode64(part[:image]) if part[:image])
74
+ FileUtils.mkdir_p File.dirname(@filename)
75
+ File.open(@filename, 'w') do |file|
76
+ file.write image
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def service_id
83
+ 'ship'
84
+ end
85
+
86
+ # Successful request
87
+ def success?(response)
88
+ response[:process_shipment_reply] &&
89
+ %w{SUCCESS WARNING NOTE}.include?(response[:process_shipment_reply][:highest_severity])
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,66 @@
1
+ require 'fedex/request/base'
2
+
3
+ module Fedex
4
+ module Request
5
+ class Rate < Base
6
+ # Sends post request to Fedex web service and parse the response, a Rate object is created if the response is successful
7
+ def process_request
8
+ api_response = self.class.post(api_url, :body => build_xml)
9
+ puts api_response if @debug == true
10
+ response = parse_response(api_response)
11
+ if success?(response)
12
+ rate_details = [response[:rate_reply][:rate_reply_details][:rated_shipment_details]].flatten.first[:shipment_rate_detail]
13
+ Fedex::Rate.new(rate_details)
14
+ else
15
+ error_message = if response[:rate_reply]
16
+ [response[:rate_reply][:notifications]].flatten.first[:message]
17
+ else
18
+ api_response["Fault"]["detail"]["fault"]["reason"]
19
+ end rescue $1
20
+ raise RateError, error_message
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # Add information for shipments
27
+ def add_requested_shipment(xml)
28
+ xml.RequestedShipment{
29
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
30
+ xml.ServiceType service_type
31
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
32
+ add_shipper(xml)
33
+ add_recipient(xml)
34
+ add_shipping_charges_payment(xml)
35
+ add_customs_clearance(xml) if @customs_clearance
36
+ xml.RateRequestTypes "ACCOUNT"
37
+ add_packages(xml)
38
+ }
39
+ end
40
+
41
+ # Build xml Fedex Web Service request
42
+ def build_xml
43
+ builder = Nokogiri::XML::Builder.new do |xml|
44
+ xml.RateRequest(:xmlns => "http://fedex.com/ws/rate/v10"){
45
+ add_web_authentication_detail(xml)
46
+ add_client_detail(xml)
47
+ add_version(xml)
48
+ add_requested_shipment(xml)
49
+ }
50
+ end
51
+ builder.doc.root.to_xml
52
+ end
53
+
54
+ def service_id
55
+ 'crs'
56
+ end
57
+
58
+ # Successful request
59
+ def success?(response)
60
+ response[:rate_reply] &&
61
+ %w{SUCCESS WARNING NOTE}.include?(response[:rate_reply][:highest_severity])
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -1,285 +1,39 @@
1
- require 'httparty'
2
- require 'nokogiri'
1
+ require 'fedex/credentials'
2
+ require 'fedex/request/label'
3
+ require 'fedex/request/rate'
4
+
3
5
  module Fedex
4
- #Fedex::Shipment
5
6
  class Shipment
6
- include HTTParty
7
- format :xml
8
- # If true the rate method will return the complete response from the Fedex Web Service
9
- attr_accessor :debug
10
- # Fedex Text URL
11
- TEST_URL = "https://gatewaybeta.fedex.com:443/xml/"
12
-
13
- # Fedex Production URL
14
- PRODUCTION_URL = "https://gateway.fedex.com:443/xml/"
15
-
16
- # Fedex Version number for the Fedex service used
17
- VERSION = 10
18
-
19
- # List of available Service Types
20
- 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)
21
-
22
- # List of available Packaging Type
23
- PACKAGING_TYPES = %w(FEDEX_10KG_BOX FEDEX_25KG_BOX FEDEX_BOX FEDEX_ENVELOPE FEDEX_PAK FEDEX_TUBE YOUR_PACKAGING)
24
-
25
- # List of available DropOffTypes
26
- DROP_OFF_TYPES = %w(BUSINESS_SERVICE_CENTER DROP_BOX REGULAR_PICKUP REQUEST_COURIER STATION)
27
-
28
- # Clearance Brokerage Type
29
- CLEARANCE_BROKERAGE_TYPE = %w(BROKER_INCLUSIVE BROKER_INCLUSIVE_NON_RESIDENT_IMPORTER BROKER_SELECT BROKER_SELECT_NON_RESIDENT_IMPORTER BROKER_UNASSIGNED)
30
-
31
- # Recipient Custom ID Type
32
- RECIPIENT_CUSTOM_ID_TYPE = %w(COMPANY INDIVIDUAL PASSPORT)
33
-
34
- # In order to use Fedex rates API you must first apply for a developer(and later production keys),
7
+
8
+ # In order to use Fedex rates API you must first apply for a developer(and later production keys),
35
9
  # Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
36
10
  # @param [String] key - Fedex web service key
37
11
  # @param [String] password - Fedex password
38
12
  # @param [String] account_number - Fedex account_number
39
- # @param [String] meter - Fedex meter number
13
+ # @param [String] meter - Fedex meter number
40
14
  # @param [String] mode - [development/production]
41
- #
15
+ #
42
16
  # return a Fedex::Shipment object
43
17
  def initialize(options={})
44
- requires!(options, :key, :password, :account_number, :meter, :mode)
45
- @key = options[:key]
46
- @password = options[:password]
47
- @account_number = options[:account_number]
48
- @meter = options[:meter]
49
- @mode = options[:mode]
18
+ @credentials = Credentials.new(options)
50
19
  end
51
-
52
-
20
+
53
21
  # @param [Hash] shipper, A hash containing the shipper information
54
22
  # @param [Hash] recipient, A hash containing the recipient information
55
23
  # @param [Array] packages, An arrary including a hash for each package being shipped
56
24
  # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
57
- def rate(options = {})
58
- requires!(options, :shipper, :recipient, :packages, :service_type)
59
- @shipper, @recipient, @packages, @service_type, @customs_clearance, @debug = options[:shipper], options[:recipient], options[:packages], options[:service_type], options[:customs_clearance], options[:debug]
60
- @shipping_options = options[:shipping_options] ||={}
61
- process_request
62
- end
63
-
64
- # Sends post request to Fedex web service and parse the response, a Rate object is created if the response is successful
65
- def process_request
66
- api_response = Shipment.post(api_url, :body => build_xml)
67
- puts api_response if @debug == true
68
- response = parse_response(api_response)
69
- if success?(response)
70
- rate_details = [response[:rate_reply][:rate_reply_details][:rated_shipment_details]].flatten.first[:shipment_rate_detail]
71
- rate = Fedex::Rate.new(rate_details)
72
- else
73
- error_message = (response[:rate_reply].nil? ? api_response["Fault"]["detail"]["fault"]["reason"] : [response[:rate_reply][:notifications]].flatten.first[:message]) rescue $1
74
- raise RateError, error_message
75
- end
76
- end
77
-
78
- # Build xml Fedex Web Service request
79
- def build_xml
80
- builder = Nokogiri::XML::Builder.new do |xml|
81
- xml.RateRequest(:xmlns => "http://fedex.com/ws/rate/v10"){
82
- add_web_authentication_detail(xml)
83
- add_client_detail(xml)
84
- add_version(xml)
85
- add_requested_shipment(xml)
86
- }
87
- end
88
- builder.doc.root.to_xml
89
- end
90
-
91
- # Fedex Web Service Api
92
- def api_url
93
- @mode == "production" ? PRODUCTION_URL : TEST_URL
94
- end
95
-
96
- private
97
- # Helper method to validate required fields
98
- def requires!(hash, *params)
99
- params.each { |param| raise RateError, "Missing Required Parameter #{param}" if hash[param].nil? }
100
- end
101
-
102
- def camelize(str) #:nodoc:
103
- str.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
104
- end
105
-
106
- # Add web authentication detail information(key and password) to xml request
107
- def add_web_authentication_detail(xml)
108
- xml.WebAuthenticationDetail{
109
- xml.UserCredential{
110
- xml.Key @key
111
- xml.Password @password
112
- }
113
- }
114
- end
115
-
116
- # Add Client Detail information(account_number and meter_number) to xml request
117
- def add_client_detail(xml)
118
- xml.ClientDetail{
119
- xml.AccountNumber @account_number
120
- xml.MeterNumber @meter
121
- }
122
- end
123
-
124
- # Add Version to xml request, using the latest version V10 Sept/2011
125
- def add_version(xml)
126
- xml.Version{
127
- xml.ServiceId 'crs'
128
- xml.Major VERSION
129
- xml.Intermediate 0
130
- xml.Minor 0
131
- }
132
- end
133
-
134
- # Add information for shipments
135
- def add_requested_shipment(xml)
136
- xml.RequestedShipment{
137
- xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
138
- xml.ServiceType service_type
139
- xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
140
- add_shipper(xml)
141
- add_recipient(xml)
142
- add_shipping_charges_payment(xml)
143
- add_customs_clearance(xml) if @customs_clearance
144
- xml.RateRequestTypes "ACCOUNT"
145
- add_packages(xml)
146
- }
147
- end
148
-
149
- # Add shipper to xml request
150
- def add_shipper(xml)
151
- xml.Shipper{
152
- xml.Contact{
153
- xml.PersonName @shipper[:name]
154
- xml.CompanyName @shipper[:company]
155
- xml.PhoneNumber @shipper[:phone_number]
156
- }
157
- xml.Address {
158
- xml.StreetLines @shipper[:address]
159
- xml.City @shipper[:city]
160
- xml.StateOrProvinceCode @shipper[:state]
161
- xml.PostalCode @shipper[:postal_code]
162
- xml.CountryCode @shipper[:country_code]
163
- }
164
- }
165
- end
166
-
167
- # Add recipient to xml request
168
- def add_recipient(xml)
169
- xml.Recipient{
170
- xml.Contact{
171
- xml.PersonName @recipient[:name]
172
- xml.CompanyName @recipient[:company]
173
- xml.PhoneNumber @recipient[:phone_number]
174
- }
175
- xml.Address {
176
- xml.StreetLines @recipient[:address]
177
- xml.City @recipient[:city]
178
- xml.StateOrProvinceCode @recipient[:state]
179
- xml.PostalCode @recipient[:postal_code]
180
- xml.CountryCode @recipient[:country_code]
181
- xml.Residential @recipient[:residential]
182
- }
183
- }
184
- end
185
-
186
- # Add shipping charges to xml request
187
- def add_shipping_charges_payment(xml)
188
- xml.ShippingChargesPayment{
189
- xml.PaymentType "SENDER"
190
- xml.Payor{
191
- xml.AccountNumber @account_number
192
- xml.CountryCode @shipper[:country_code]
193
- }
194
- }
195
- end
196
-
197
- # Add packages to xml request
198
- def add_packages(xml)
199
- package_count = @packages.size
200
- xml.PackageCount package_count
201
- @packages.each do |package|
202
- xml.RequestedPackageLineItems{
203
- xml.GroupPackageCount 1
204
- xml.Weight{
205
- xml.Units package[:weight][:units]
206
- xml.Value package[:weight][:value]
207
- }
208
- xml.Dimensions{
209
- xml.Length package[:dimensions][:length]
210
- xml.Width package[:dimensions][:width]
211
- xml.Height package[:dimensions][:height]
212
- xml.Units package[:dimensions][:units]
213
- }
214
- }
215
- end
216
- end
217
-
218
- # Add customs clearance(for international shipments)
219
- def add_customs_clearance(xml)
220
- xml.CustomsClearanceDetail{
221
- customs_to_xml(xml, @customs_clearance)
222
- }
223
- end
224
-
225
- # Build nodes dinamically from the provided customs clearance hash
226
- def customs_to_xml(xml, hash)
227
- hash.each do |key, value|
228
- if value.is_a?(Hash)
229
- xml.send "#{camelize(key.to_s)}" do |x|
230
- customs_to_xml(x, value)
231
- end
232
- elsif value.is_a?(Array)
233
- node = key
234
- value.each do |v|
235
- xml.send "#{camelize(node.to_s)}" do |x|
236
- customs_to_xml(x, v)
237
- end
238
- end
239
- else
240
- xml.send "#{camelize(key.to_s)}", value unless key.is_a?(Hash)
241
- end
242
- end
243
- end
244
-
245
- # Parse response, convert keys to underscore symbols
246
- def parse_response(response)
247
- response = sanitize_response_keys(response)
25
+ # @param [String] filename, A location where the label will be saved
26
+ def label(options = {})
27
+ Request::Label.new(@credentials, options).process_request
248
28
  end
249
29
 
250
- # Recursively sanitizes the response object by clenaing up any hash keys.
251
- def sanitize_response_keys(response)
252
- if response.is_a?(Hash)
253
- response.inject({}) { |result, (key, value)| result[underscorize(key).to_sym] = sanitize_response_keys(value); result }
254
- elsif response.is_a?(Array)
255
- response.collect { |result| sanitize_response_keys(result) }
256
- else
257
- response
258
- end
30
+ # @param [Hash] shipper, A hash containing the shipper information
31
+ # @param [Hash] recipient, A hash containing the recipient information
32
+ # @param [Array] packages, An arrary including a hash for each package being shipped
33
+ # @param [String] service_type, A valid fedex service type, to view a complete list of services Fedex::Shipment::SERVICE_TYPES
34
+ def rate(options = {})
35
+ Request::Rate.new(@credentials, options).process_request
259
36
  end
260
37
 
261
- def underscorize(key) #:nodoc:
262
- 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
263
- end
264
-
265
- # Successful request
266
- def success?(response)
267
- (!response[:rate_reply].nil? and %w{SUCCESS WARNING NOTE}.include? response[:rate_reply][:highest_severity])
268
- end
269
-
270
- # Use GROUND_HOME_DELIVERY for shipments going to a residential address within the US.
271
- def service_type
272
- if @recipient[:residential].to_s =~ /true/i and @service_type =~ /GROUND/i and @recipient[:country_code] =~ /US/i
273
- "GROUND_HOME_DELIVERY"
274
- else
275
- @service_type
276
- end
277
- end
278
-
279
- # String to CamelCase
280
- def camelize(str)
281
- str.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
282
- end
283
-
284
38
  end
285
39
  end