fedex_ship 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/fedex_ship-0.1.0.iml +22 -0
  5. data/.idea/misc.xml +7 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.idea/workspace.xml +56 -0
  9. data/.rspec +2 -0
  10. data/Gemfile +5 -0
  11. data/Rakefile +7 -0
  12. data/Readme.md +496 -0
  13. data/fedex_ship.gemspec +28 -0
  14. data/lib/fedex_ship.rb +55 -0
  15. data/lib/fedex_ship/address.rb +31 -0
  16. data/lib/fedex_ship/credentials.rb +26 -0
  17. data/lib/fedex_ship/document.rb +51 -0
  18. data/lib/fedex_ship/ground_manifest.rb +25 -0
  19. data/lib/fedex_ship/helpers.rb +20 -0
  20. data/lib/fedex_ship/label.rb +71 -0
  21. data/lib/fedex_ship/rate.rb +38 -0
  22. data/lib/fedex_ship/request/address.rb +97 -0
  23. data/lib/fedex_ship/request/base.rb +443 -0
  24. data/lib/fedex_ship/request/delete.rb +76 -0
  25. data/lib/fedex_ship/request/document.rb +45 -0
  26. data/lib/fedex_ship/request/ground_close.rb +73 -0
  27. data/lib/fedex_ship/request/label.rb +29 -0
  28. data/lib/fedex_ship/request/logs_fedex.rb +74 -0
  29. data/lib/fedex_ship/request/pickup.rb +135 -0
  30. data/lib/fedex_ship/request/pickup_availability.rb +102 -0
  31. data/lib/fedex_ship/request/rate.rb +94 -0
  32. data/lib/fedex_ship/request/service_availability.rb +86 -0
  33. data/lib/fedex_ship/request/shipment.rb +249 -0
  34. data/lib/fedex_ship/request/tracking_information.rb +119 -0
  35. data/lib/fedex_ship/shipment.rb +115 -0
  36. data/lib/fedex_ship/tracking_information.rb +54 -0
  37. data/lib/fedex_ship/tracking_information/event.rb +24 -0
  38. data/lib/fedex_ship/version.rb +6 -0
  39. data/spec/config/fedex_credentials.example.yml +13 -0
  40. data/spec/lib/fedex_ship/address_spec.rb +59 -0
  41. data/spec/lib/fedex_ship/delete_spec.rb +26 -0
  42. data/spec/lib/fedex_ship/document_spec.rb +177 -0
  43. data/spec/lib/fedex_ship/ground_close_spec.rb +42 -0
  44. data/spec/lib/fedex_ship/label_spec.rb +73 -0
  45. data/spec/lib/fedex_ship/pickup_availability_spec.rb +19 -0
  46. data/spec/lib/fedex_ship/pickup_spec.rb +32 -0
  47. data/spec/lib/fedex_ship/rate_spec.rb +216 -0
  48. data/spec/lib/fedex_ship/service_availability_spec.rb +20 -0
  49. data/spec/lib/fedex_ship/shipment_spec.rb +86 -0
  50. data/spec/lib/fedex_ship/track_spec.rb +67 -0
  51. data/spec/spec_helper.rb +12 -0
  52. data/spec/support/credentials.rb +15 -0
  53. data/spec/support/vcr.rb +14 -0
  54. metadata +193 -0
@@ -0,0 +1,94 @@
1
+ require 'fedex_ship/request/base'
2
+ require 'fedex_ship/request/logs_fedex'
3
+
4
+ module FedexShip
5
+ module Request
6
+ class Rate < LogsFedex
7
+ # Sends post request to Fedex web service and parse the response, a Rate object is created if the response is successful
8
+ def process_request
9
+ @build_xml = build_xml
10
+ rate_serv_log('Final XML Request : ' + @build_xml.to_s)
11
+ api_url_srv = api_url + "/rate"
12
+ rate_serv_log('URL for API : ' + api_url_srv.to_s)
13
+ api_response = self.class.post(api_url_srv, :body => @build_xml)
14
+ rate_serv_log('API Response : ' + api_response.to_s)
15
+ puts api_response if @debug
16
+ response = parse_response(api_response)
17
+ if success?(response)
18
+ rate_serv_log('Successfully Done : ' + response.to_s)
19
+ rate_reply_details = response[:envelope][:body][:rate_reply][:rate_reply_details] || []
20
+ rate_reply_details = [rate_reply_details] if rate_reply_details.is_a?(Hash)
21
+
22
+ rate_reply_details.map do |rate_reply|
23
+ rate_details = [rate_reply[:rated_shipment_details]].flatten.first[:shipment_rate_detail]
24
+ rate_details.merge!(service_type: rate_reply[:service_type])
25
+ rate_details.merge!(transit_time: rate_reply[:delivery_timestamp])
26
+ FedexShip::Rate.new(rate_details)
27
+ end
28
+ else
29
+ error_message = if response[:envelope][:body][:rate_reply]
30
+ [response[:envelope][:body][:rate_reply][:notifications]].flatten.first[:message]
31
+ else
32
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n--#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
33
+ end rescue $1
34
+ raise RateError, error_message
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Add information for shipments
41
+ def add_requested_shipment(xml)
42
+ xml.RequestedShipment {
43
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
44
+ xml.ServiceType service_type if service_type
45
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
46
+ xml.PreferredCurrency @preferred_currency
47
+ add_shipper(xml)
48
+ add_recipient(xml)
49
+ add_shipping_charges_payment(xml)
50
+ add_customs_clearance(xml) if @customs_clearance_detail
51
+ xml.RateRequestTypes "ACCOUNT"
52
+ add_packages(xml)
53
+ }
54
+ end
55
+
56
+ # Add transite time options
57
+ def add_transit_time(xml)
58
+ xml.ReturnTransitAndCommit true
59
+ end
60
+
61
+ # Build xml Fedex Web Service request
62
+ def build_xml
63
+ ns = "http://fedex.com/ws/rate/v#{service[:version]}"
64
+ builder = Nokogiri::XML::Builder.new do |xml|
65
+ xml.Envelope("xmlns" => "http://fedex.com/ws/rate/v13") {
66
+ xml.parent.namespace = xml.parent.add_namespace_definition("soapenv", "http://schemas.xmlsoap.org/soap/envelope/")
67
+ xml['soapenv'].Header {}
68
+ xml['soapenv'].Body {
69
+ xml.RateRequest(:xmlns => ns) {
70
+ add_web_authentication_detail(xml)
71
+ add_client_detail(xml)
72
+ add_version(xml)
73
+ add_transit_time(xml)
74
+ add_requested_shipment(xml)
75
+ }
76
+ }
77
+ }
78
+ end
79
+ return builder.doc.root.to_xml
80
+ end
81
+
82
+ def service
83
+ {:id => 'crs', :version => "13"}
84
+ end
85
+
86
+ # Successful request
87
+ def success?(response)
88
+ response[:envelope][:body][:rate_reply] &&
89
+ %w{SUCCESS}.include?(response[:envelope][:body][:rate_reply][:highest_severity])
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,86 @@
1
+ require 'fedex_ship/request/base'
2
+
3
+ module FedexShip
4
+ module Request
5
+ class ServiceAvailability < Base
6
+ def initialize(credentials, options={})
7
+ requires!(options, :origin, :destination, :ship_date, :carrier_code)
8
+
9
+ @credentials = credentials
10
+ @origin = options[:origin]
11
+ @destination = options[:destination]
12
+ @ship_date = options[:ship_date]
13
+ @carrier_code = options[:carrier_code]
14
+ end
15
+
16
+ def process_request
17
+ api_response = self.class.post api_url, :body => build_xml
18
+ puts api_response if @debug
19
+ response = parse_response(api_response)
20
+ if success?(response)
21
+ success_response(api_response, response)
22
+ else
23
+ failure_response(api_response, response)
24
+ end
25
+ end
26
+
27
+ def build_xml
28
+ builder = Nokogiri::XML::Builder.new do |xml|
29
+ xml.ServiceAvailabilityRequest(:xmlns => "http://fedex.com/ws/packagemovementinformationservice/v#{service[:version]}"){
30
+ add_web_authentication_detail(xml)
31
+ add_client_detail(xml)
32
+ add_version(xml)
33
+ add_origin(xml)
34
+ add_destination(xml)
35
+ add_other_details(xml)
36
+ }
37
+ end
38
+ builder.doc.root.to_xml
39
+ end
40
+
41
+ def add_origin(xml)
42
+ xml.Origin{
43
+ xml.PostalCode @origin[:postal_code]
44
+ xml.CountryCode @origin[:country_code]
45
+ }
46
+ end
47
+
48
+ def add_destination(xml)
49
+ xml.Destination{
50
+ xml.PostalCode @destination[:postal_code]
51
+ xml.CountryCode @destination[:country_code]
52
+ }
53
+ end
54
+
55
+ def add_other_details(xml)
56
+ xml.ShipDate @ship_date
57
+ xml.CarrierCode @carrier_code
58
+ end
59
+
60
+ # Callback used after a failed shipment response.
61
+ def failure_response(api_response, response)
62
+ error_message = if response[:service_availability_reply]
63
+ [response[:service_availability_reply][:notifications]].flatten.first[:message]
64
+ else
65
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n--#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
66
+ end rescue $1
67
+ raise RateError, error_message
68
+ end
69
+
70
+ # Callback used after a successful shipment response.
71
+ def success_response(api_response, response)
72
+ @response_details = response[:service_availability_reply]
73
+ end
74
+
75
+ def service
76
+ { :id => 'pmis', :version => FedexShip::SERVICE_AVAILABILITY_API_VERSION }
77
+ end
78
+
79
+ # Successful request
80
+ def success?(response)
81
+ response[:service_availability_reply] &&
82
+ %w{SUCCESS WARNING NOTE}.include?(response[:service_availability_reply][:highest_severity])
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,249 @@
1
+ require 'fedex_ship/request/base'
2
+ require 'fedex_ship/request/logs_fedex'
3
+
4
+ module FedexShip
5
+ module Request
6
+ class Shipment < LogsFedex
7
+ attr_reader :response_details
8
+
9
+ def initialize(credentials, options = {})
10
+ super
11
+ requires!(options, :service_type)
12
+ # Label specification is required even if we're not using it.
13
+ @label_specification = {
14
+ :label_format_type => 'COMMON2D',
15
+ :image_type => 'PDF',
16
+ :label_stock_type => 'PAPER_LETTER'
17
+ }
18
+ @label_specification.merge! options[:label_specification] if options[:label_specification]
19
+ @customer_specified_detail = options[:customer_specified_detail] if options[:customer_specified_detail]
20
+ end
21
+
22
+ # Sends post request to Fedex web service and parse the response.
23
+ # A label file is created with the label at the specified location.
24
+ # The parsed Fedex response is available in #response_details
25
+ # e.g. response_details[:completed_shipment_detail][:completed_package_details][:tracking_ids][:tracking_number]
26
+ def process_request
27
+ @build_xml = build_xml
28
+ ship_serv_log('Final XML Request : ' + @build_xml.to_s)
29
+ api_url_srv = api_url + "/ship"
30
+ ship_serv_log('URL for API : ' + api_url_srv.to_s)
31
+ api_response = self.class.post(api_url_srv, :body => @build_xml)
32
+ ship_serv_log('API Response : ' + api_response.to_s)
33
+ puts api_response if @debug
34
+ response = parse_response(api_response)
35
+ if success?(response)
36
+ ship_serv_log('Successfully Done : ' + response.to_s)
37
+ success_response(api_response, response)
38
+ else
39
+ failure_response(api_response, response)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Add information for shipments
46
+ def add_requested_shipment(xml)
47
+ xml.RequestedShipment {
48
+ xml.ShipTimestamp @shipping_options[:ship_timestamp] ||= Time.now.utc.iso8601(2)
49
+ xml.DropoffType @shipping_options[:drop_off_type] ||= "REGULAR_PICKUP"
50
+ xml.ServiceType service_type
51
+ xml.PackagingType @shipping_options[:packaging_type] ||= "YOUR_PACKAGING"
52
+ add_total_weight(xml) if @mps.has_key? :total_weight
53
+ add_total_insured_value(xml) if @total_insured_value.has_key? :amount
54
+ add_shipper(xml)
55
+ add_origin(xml) if @origin
56
+ add_recipient(xml)
57
+ add_shipping_charges_payment(xml)
58
+ add_special_services(xml) if @shipping_options[:return_reason] || @shipping_options[:cod] || @shipping_options[:event_notification] || @shipping_options[:saturday_delivery]
59
+ add_customs_clearance(xml) if @customs_clearance_detail
60
+ add_custom_components(xml)
61
+ add_shipping_document_specification(xml)
62
+ xml.RateRequestTypes "ACCOUNT"
63
+ add_packages(xml)
64
+ }
65
+ end
66
+
67
+ def add_total_weight(xml)
68
+ if @mps.has_key? :total_weight
69
+ xml.TotalWeight {
70
+ xml.Units @packages[0][:weight][:units]
71
+ xml.Value @mps[:total_weight]
72
+ }
73
+ end
74
+ end
75
+
76
+ def add_total_insured_value(xml)
77
+ if @total_insured_value[:amount] && @total_insured_value[:currency]
78
+ xml.TotalInsuredValue {
79
+ xml.Currency @total_insured_value[:currency]
80
+ xml.Amount @total_insured_value[:amount]
81
+ }
82
+ end
83
+ end
84
+
85
+ # Hook that can be used to add custom parts.
86
+ def add_custom_components(xml)
87
+ add_label_specification xml
88
+ end
89
+
90
+ # Add the label specification
91
+ def add_label_specification(xml)
92
+ xml.LabelSpecification {
93
+ xml.LabelFormatType @label_specification[:label_format_type]
94
+ xml.ImageType @label_specification[:image_type]
95
+ xml.LabelStockType @label_specification[:label_stock_type]
96
+ xml.CustomerSpecifiedDetail {hash_to_xml(xml, @customer_specified_detail)} if @customer_specified_detail
97
+
98
+ if @label_specification[:printed_label_origin] && @label_specification[:printed_label_origin][:address]
99
+ xml.PrintedLabelOrigin {
100
+ xml.Contact {
101
+ xml.PersonName @label_specification[:printed_label_origin][:address][:name]
102
+ xml.CompanyName @label_specification[:printed_label_origin][:address][:company]
103
+ xml.PhoneNumber @label_specification[:printed_label_origin][:address][:phone_number]
104
+ }
105
+ xml.Address {
106
+ Array(@label_specification[:printed_label_origin][:address][:address]).each do |address_line|
107
+ xml.StreetLines address_line
108
+ end
109
+ xml.City @label_specification[:printed_label_origin][:address][:city]
110
+ xml.StateOrProvinceCode @label_specification[:printed_label_origin][:address][:state]
111
+ xml.PostalCode @label_specification[:printed_label_origin][:address][:postal_code]
112
+ xml.CountryCode @label_specification[:printed_label_origin][:address][:country_code]
113
+ }
114
+ }
115
+ end
116
+ }
117
+ end
118
+
119
+ def add_shipping_document_specification(xml)
120
+ xml.ShippingDocumentSpecification {
121
+ xml.ShippingDocumentTypes "COMMERCIAL_INVOICE"
122
+ xml.CommercialInvoiceDetail {
123
+ xml.Format {
124
+ xml.ImageType @commercial_invoice_options[:commercial_invoice_detail][:format][:image_type]
125
+ xml.StockType @commercial_invoice_options[:commercial_invoice_detail][:format][:stock_type]
126
+ xml.ProvideInstructions "1"
127
+ }
128
+ }
129
+ }
130
+ end
131
+
132
+ def add_special_services(xml)
133
+ xml.SpecialServicesRequested {
134
+
135
+ @shipping_options[:special_services].each do |service|
136
+ xml.SpecialServiceTypes service
137
+ end
138
+
139
+ =begin
140
+ if @shipping_options[:return_reason]
141
+ xml.SpecialServiceTypes "RETURN_SHIPMENT"
142
+ elsif @shipping_options[:cod]
143
+ xml.SpecialServiceTypes "COD"
144
+ elsif @shipping_options[:event_notification]
145
+ xml.SpecialServiceTypes "EVENT_NOTIFICATION"
146
+ elsif @shipping_options[:saturday_delivery]
147
+ xml.SpecialServiceTypes "SATURDAY_DELIVERY"
148
+ end
149
+ =end
150
+
151
+ if @shipping_options[:return_reason]
152
+ xml.ReturnShipmentDetail {
153
+ xml.ReturnType "PRINT_RETURN_LABEL"
154
+ xml.Rma {
155
+ xml.Reason "#{@shipping_options[:return_reason]}"
156
+ }
157
+ }
158
+ end
159
+
160
+ if @shipping_options[:cod]
161
+ xml.CodDetail {
162
+ xml.CodCollectionAmount {
163
+ xml.Currency @shipping_options[:cod][:currency].upcase if @shipping_options[:cod][:currency]
164
+ xml.Amount @shipping_options[:cod][:amount] if @shipping_options[:cod][:amount]
165
+ }
166
+ xml.CollectionType @shipping_options[:cod][:collection_type] if @shipping_options[:cod][:collection_type]
167
+ }
168
+ # add_shipping_document_specification
169
+ end
170
+
171
+ if @shipping_options[:event_notification]
172
+ xml.EventNotificationDetail {
173
+ xml.AggregationType "PER_SHIPMENT"
174
+ xml.PersonalMessage @shipping_options[:event_notification][:personal_message]
175
+ @shipping_options[:event_notification][:email_address].each_with_index do |email, index|
176
+ xml.EventNotifications {
177
+ xml.Role @shipping_options[:event_notification][:role][index].upcase
178
+ @shipping_options[:event_notification][:events].each do |event|
179
+ xml.Events event
180
+ end
181
+ xml.NotificationDetail {
182
+ xml.NotificationType "EMAIL"
183
+ xml.EmailDetail {
184
+ xml.EmailAddress email
185
+ }
186
+ xml.Localization {
187
+ xml.LanguageCode "EN"
188
+ }
189
+ }
190
+ xml.FormatSpecification {
191
+ xml.Type @shipping_options[:event_notification][:type]
192
+ }
193
+ }
194
+ end
195
+ }
196
+ # add_shipping_document_specification
197
+ end
198
+
199
+ }
200
+ end
201
+
202
+ # Callback used after a failed shipment response.
203
+ def failure_response(api_response, response)
204
+ error_message = if response[:envelope][:body][:process_shipment_reply]
205
+ [response[:envelope][:body][:process_shipment_reply][:notifications]].flatten.first[:message]
206
+ else
207
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n--#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
208
+ end rescue $1
209
+ raise RateError, error_message
210
+ end
211
+
212
+ # Callback used after a successful shipment response.
213
+ def success_response(api_response, response)
214
+ @response_details = response[:envelope][:body][:process_shipment_reply]
215
+ end
216
+
217
+ # Build xml Fedex Web Service request
218
+ def build_xml
219
+ ns = "http://fedex.com/ws/ship/v21"
220
+ builder = Nokogiri::XML::Builder.new do |xml|
221
+ xml.Envelope("xmlns" => "http://fedex.com/ws/ship/v21") {
222
+ xml.parent.namespace = xml.parent.add_namespace_definition("soapenv", "http://schemas.xmlsoap.org/soap/envelope/")
223
+ xml['soapenv'].Header {}
224
+ xml['soapenv'].Body {
225
+ xml.ProcessShipmentRequest() {
226
+ add_web_authentication_detail(xml)
227
+ add_client_detail(xml)
228
+ add_version(xml)
229
+ add_requested_shipment(xml)
230
+ }
231
+ }
232
+ }
233
+ end
234
+ builder.doc.root.to_xml
235
+ end
236
+
237
+ def service
238
+ {:id => 'ship', :version => "21"}
239
+ end
240
+
241
+ # Successful request
242
+ def success?(response)
243
+ response[:envelope][:body][:process_shipment_reply] &&
244
+ %w{SUCCESS WARNING NOTE}.include?(response[:envelope][:body][:process_shipment_reply][:highest_severity])
245
+ end
246
+
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,119 @@
1
+ require 'fedex_ship/request/base'
2
+ require 'fedex_ship/tracking_information'
3
+ require 'fedex_ship/request/logs_fedex'
4
+
5
+ module FedexShip
6
+ module Request
7
+ class TrackingInformation < LogsFedex
8
+
9
+ attr_reader :package_type, :package_id
10
+
11
+ def initialize(credentials, options = {})
12
+ requires!(options, :package_type, :package_id) unless options.has_key?(:tracking_number)
13
+
14
+ @package_id = options[:package_id] || options.delete(:tracking_number)
15
+ @package_type = options[:package_type] || "TRACKING_NUMBER_OR_DOORTAG"
16
+ @credentials = credentials
17
+
18
+ # Optional
19
+ @include_detailed_scans = options[:include_detailed_scans] || true
20
+ @uuid = options[:uuid]
21
+ @paging_token = options[:paging_token]
22
+
23
+ unless package_type_valid?
24
+ raise "Unknown package type '#{package_type}'"
25
+ end
26
+ end
27
+
28
+ def process_request
29
+ @build_xml = build_xml
30
+ track_serv_log('Final XML Request : ' + @build_xml.to_s)
31
+ api_url_srv = api_url + "/track"
32
+ track_serv_log('URL for API : ' + api_url_srv.to_s)
33
+ api_response = self.class.post(api_url_srv, :body => build_xml)
34
+ track_serv_log('API Response : ' + api_response.to_s)
35
+ puts api_response if @debug == true
36
+ response = parse_response(api_response)
37
+
38
+ if success?(response)
39
+ track_serv_log('Successfully Done : ' + response.to_s)
40
+ options = response[:envelope][:body][:track_reply][:completed_track_details][:track_details]
41
+
42
+ if response[:envelope][:body][:track_reply][:completed_track_details][:duplicate_waybill].downcase == 'true'
43
+ shipments = []
44
+ [options].flatten.map do |details|
45
+ options = {:tracking_number => @package_id, :uuid => details[:tracking_number_unique_identifier]}
46
+ shipments << Request::TrackingInformation.new(@credentials, options).process_request
47
+ end
48
+ shipments.flatten
49
+ elsif [options[:notification]].flatten.first[:severity] == "SUCCESS"
50
+ [options].flatten.map do |details|
51
+ FedexShip::TrackingInformation.new(details)
52
+ end
53
+ else
54
+ error_message = if response[:envelope][:body][:track_reply]
55
+ [options[:notification]].flatten.first[:message]
56
+ end
57
+ raise RateError, error_message
58
+ end
59
+ else
60
+ error_message = if response[:envelope][:body][:track_reply]
61
+ response[:envelope][:body][:track_reply][:notifications][:message]
62
+ else
63
+ "#{api_response["Fault"]["detail"]["fault"]["reason"]}\n--#{api_response["Fault"]["detail"]["fault"]["details"]["ValidationFailureDetail"]["message"].join("\n--")}"
64
+ end rescue $1
65
+ raise RateError, error_message
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Build xml Fedex Web Service request
72
+ def build_xml
73
+ ns = "http://fedex.com/ws/track/v16"
74
+ builder = Nokogiri::XML::Builder.new do |xml|
75
+ xml.Envelope("xmlns" => "http://fedex.com/ws/track/v16") {
76
+ xml.parent.namespace = xml.parent.add_namespace_definition("soapenv", "http://schemas.xmlsoap.org/soap/envelope/")
77
+ xml['soapenv'].Header {}
78
+ xml['soapenv'].Body {
79
+ xml.TrackRequest() {
80
+ add_web_authentication_detail(xml)
81
+ add_client_detail(xml)
82
+ add_version(xml)
83
+ add_package_identifier(xml)
84
+ xml.TrackingNumberUniqueIdentifier @uuid if @uuid
85
+ xml.PagingToken @paging_token if @paging_token
86
+ }
87
+ }
88
+ }
89
+ end
90
+ builder.doc.root.to_xml
91
+ end
92
+
93
+ def service
94
+ {:id => 'trck', :version => "16"}
95
+ end
96
+
97
+ def add_package_identifier(xml)
98
+ xml.SelectionDetails {
99
+ xml.CarrierCode "FDXE"
100
+ xml.PackageIdentifier {
101
+ xml.Type package_type
102
+ xml.Value package_id
103
+ }
104
+ }
105
+ end
106
+
107
+ # Successful request
108
+ def success?(response)
109
+ response[:envelope][:body][:track_reply] &&
110
+ %w{SUCCESS WARNING NOTE}.include?(response[:envelope][:body][:track_reply][:highest_severity])
111
+ end
112
+
113
+ def package_type_valid?
114
+ FedexShip::TrackingInformation::PACKAGE_IDENTIFIER_TYPES.include? package_type
115
+ end
116
+
117
+ end
118
+ end
119
+ end