active_shipping 0.12.6 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +0 -0
  4. data/{CHANGELOG → CHANGELOG.md} +6 -2
  5. data/CONTRIBUTING.md +32 -0
  6. data/{README.markdown → README.md} +45 -61
  7. data/lib/active_shipping.rb +20 -28
  8. data/lib/active_shipping/carrier.rb +82 -0
  9. data/lib/active_shipping/carriers.rb +33 -0
  10. data/lib/active_shipping/carriers/benchmark_carrier.rb +31 -0
  11. data/lib/active_shipping/carriers/bogus_carrier.rb +12 -0
  12. data/lib/active_shipping/carriers/canada_post.rb +253 -0
  13. data/lib/active_shipping/carriers/canada_post_pws.rb +870 -0
  14. data/lib/active_shipping/carriers/fedex.rb +579 -0
  15. data/lib/active_shipping/carriers/kunaki.rb +164 -0
  16. data/lib/active_shipping/carriers/new_zealand_post.rb +262 -0
  17. data/lib/active_shipping/carriers/shipwire.rb +181 -0
  18. data/lib/active_shipping/carriers/stamps.rb +861 -0
  19. data/lib/active_shipping/carriers/ups.rb +648 -0
  20. data/lib/active_shipping/carriers/usps.rb +642 -0
  21. data/lib/active_shipping/errors.rb +7 -0
  22. data/lib/active_shipping/label_response.rb +23 -0
  23. data/lib/active_shipping/location.rb +149 -0
  24. data/lib/active_shipping/package.rb +241 -0
  25. data/lib/active_shipping/rate_estimate.rb +64 -0
  26. data/lib/active_shipping/rate_response.rb +13 -0
  27. data/lib/active_shipping/response.rb +41 -0
  28. data/lib/active_shipping/shipment_event.rb +17 -0
  29. data/lib/active_shipping/shipment_packer.rb +73 -0
  30. data/lib/active_shipping/shipping_response.rb +12 -0
  31. data/lib/active_shipping/tracking_response.rb +52 -0
  32. data/lib/active_shipping/version.rb +1 -1
  33. data/lib/vendor/quantified/test/length_test.rb +2 -2
  34. data/lib/vendor/xml_node/test/test_parsing.rb +1 -1
  35. metadata +58 -36
  36. metadata.gz.sig +0 -0
  37. data/lib/active_shipping/shipping/base.rb +0 -13
  38. data/lib/active_shipping/shipping/carrier.rb +0 -84
  39. data/lib/active_shipping/shipping/carriers.rb +0 -23
  40. data/lib/active_shipping/shipping/carriers/benchmark_carrier.rb +0 -33
  41. data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +0 -14
  42. data/lib/active_shipping/shipping/carriers/canada_post.rb +0 -257
  43. data/lib/active_shipping/shipping/carriers/canada_post_pws.rb +0 -874
  44. data/lib/active_shipping/shipping/carriers/fedex.rb +0 -581
  45. data/lib/active_shipping/shipping/carriers/kunaki.rb +0 -166
  46. data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +0 -262
  47. data/lib/active_shipping/shipping/carriers/shipwire.rb +0 -184
  48. data/lib/active_shipping/shipping/carriers/stamps.rb +0 -864
  49. data/lib/active_shipping/shipping/carriers/ups.rb +0 -650
  50. data/lib/active_shipping/shipping/carriers/usps.rb +0 -649
  51. data/lib/active_shipping/shipping/errors.rb +0 -9
  52. data/lib/active_shipping/shipping/label_response.rb +0 -25
  53. data/lib/active_shipping/shipping/location.rb +0 -152
  54. data/lib/active_shipping/shipping/package.rb +0 -243
  55. data/lib/active_shipping/shipping/rate_estimate.rb +0 -66
  56. data/lib/active_shipping/shipping/rate_response.rb +0 -15
  57. data/lib/active_shipping/shipping/response.rb +0 -43
  58. data/lib/active_shipping/shipping/shipment_event.rb +0 -19
  59. data/lib/active_shipping/shipping/shipment_packer.rb +0 -75
  60. data/lib/active_shipping/shipping/shipping_response.rb +0 -14
  61. data/lib/active_shipping/shipping/tracking_response.rb +0 -54
@@ -1,581 +0,0 @@
1
- # FedEx module by Jimmy Baker
2
- # http://github.com/jimmyebaker
3
-
4
- require 'date'
5
- module ActiveMerchant
6
- module Shipping
7
- # :key is your developer API key
8
- # :password is your API password
9
- # :account is your FedEx account number
10
- # :login is your meter number
11
- class FedEx < Carrier
12
- self.retry_safe = true
13
-
14
- cattr_reader :name
15
- @@name = "FedEx"
16
-
17
- TEST_URL = 'https://gatewaybeta.fedex.com:443/xml'
18
- LIVE_URL = 'https://gateway.fedex.com:443/xml'
19
-
20
- CARRIER_CODES = {
21
- "fedex_ground" => "FDXG",
22
- "fedex_express" => "FDXE"
23
- }
24
-
25
- DELIVERY_ADDRESS_NODE_NAMES = %w(DestinationAddress ActualDeliveryAddress)
26
- SHIPPER_ADDRESS_NODE_NAMES = %w(ShipperAddress)
27
-
28
- SERVICE_TYPES = {
29
- "PRIORITY_OVERNIGHT" => "FedEx Priority Overnight",
30
- "PRIORITY_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx Priority Overnight Saturday Delivery",
31
- "FEDEX_2_DAY" => "FedEx 2 Day",
32
- "FEDEX_2_DAY_SATURDAY_DELIVERY" => "FedEx 2 Day Saturday Delivery",
33
- "STANDARD_OVERNIGHT" => "FedEx Standard Overnight",
34
- "FIRST_OVERNIGHT" => "FedEx First Overnight",
35
- "FIRST_OVERNIGHT_SATURDAY_DELIVERY" => "FedEx First Overnight Saturday Delivery",
36
- "FEDEX_EXPRESS_SAVER" => "FedEx Express Saver",
37
- "FEDEX_1_DAY_FREIGHT" => "FedEx 1 Day Freight",
38
- "FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 1 Day Freight Saturday Delivery",
39
- "FEDEX_2_DAY_FREIGHT" => "FedEx 2 Day Freight",
40
- "FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 2 Day Freight Saturday Delivery",
41
- "FEDEX_3_DAY_FREIGHT" => "FedEx 3 Day Freight",
42
- "FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY" => "FedEx 3 Day Freight Saturday Delivery",
43
- "INTERNATIONAL_PRIORITY" => "FedEx International Priority",
44
- "INTERNATIONAL_PRIORITY_SATURDAY_DELIVERY" => "FedEx International Priority Saturday Delivery",
45
- "INTERNATIONAL_ECONOMY" => "FedEx International Economy",
46
- "INTERNATIONAL_FIRST" => "FedEx International First",
47
- "INTERNATIONAL_PRIORITY_FREIGHT" => "FedEx International Priority Freight",
48
- "INTERNATIONAL_ECONOMY_FREIGHT" => "FedEx International Economy Freight",
49
- "GROUND_HOME_DELIVERY" => "FedEx Ground Home Delivery",
50
- "FEDEX_GROUND" => "FedEx Ground",
51
- "INTERNATIONAL_GROUND" => "FedEx International Ground",
52
- "SMART_POST" => "FedEx SmartPost",
53
- "FEDEX_FREIGHT_PRIORITY" => "FedEx Freight Priority",
54
- "FEDEX_FREIGHT_ECONOMY" => "FedEx Freight Economy"
55
- }
56
-
57
- PACKAGE_TYPES = {
58
- "fedex_envelope" => "FEDEX_ENVELOPE",
59
- "fedex_pak" => "FEDEX_PAK",
60
- "fedex_box" => "FEDEX_BOX",
61
- "fedex_tube" => "FEDEX_TUBE",
62
- "fedex_10_kg_box" => "FEDEX_10KG_BOX",
63
- "fedex_25_kg_box" => "FEDEX_25KG_BOX",
64
- "your_packaging" => "YOUR_PACKAGING"
65
- }
66
-
67
- DROPOFF_TYPES = {
68
- 'regular_pickup' => 'REGULAR_PICKUP',
69
- 'request_courier' => 'REQUEST_COURIER',
70
- 'dropbox' => 'DROP_BOX',
71
- 'business_service_center' => 'BUSINESS_SERVICE_CENTER',
72
- 'station' => 'STATION'
73
- }
74
-
75
- PAYMENT_TYPES = {
76
- 'sender' => 'SENDER',
77
- 'recipient' => 'RECIPIENT',
78
- 'third_party' => 'THIRDPARTY',
79
- 'collect' => 'COLLECT'
80
- }
81
-
82
- PACKAGE_IDENTIFIER_TYPES = {
83
- 'tracking_number' => 'TRACKING_NUMBER_OR_DOORTAG',
84
- 'door_tag' => 'TRACKING_NUMBER_OR_DOORTAG',
85
- 'rma' => 'RMA',
86
- 'ground_shipment_id' => 'GROUND_SHIPMENT_ID',
87
- 'ground_invoice_number' => 'GROUND_INVOICE_NUMBER',
88
- 'ground_customer_reference' => 'GROUND_CUSTOMER_REFERENCE',
89
- 'ground_po' => 'GROUND_PO',
90
- 'express_reference' => 'EXPRESS_REFERENCE',
91
- 'express_mps_master' => 'EXPRESS_MPS_MASTER'
92
- }
93
-
94
- TRANSIT_TIMES = %w(UNKNOWN ONE_DAY TWO_DAYS THREE_DAYS FOUR_DAYS FIVE_DAYS SIX_DAYS SEVEN_DAYS EIGHT_DAYS NINE_DAYS TEN_DAYS ELEVEN_DAYS TWELVE_DAYS THIRTEEN_DAYS FOURTEEN_DAYS FIFTEEN_DAYS SIXTEEN_DAYS SEVENTEEN_DAYS EIGHTEEN_DAYS)
95
-
96
- # FedEx tracking codes as described in the FedEx Tracking Service WSDL Guide
97
- # All delays also have been marked as exceptions
98
- TRACKING_STATUS_CODES = HashWithIndifferentAccess.new(
99
- 'AA' => :at_airport,
100
- 'AD' => :at_delivery,
101
- 'AF' => :at_fedex_facility,
102
- 'AR' => :at_fedex_facility,
103
- 'AP' => :at_pickup,
104
- 'CA' => :canceled,
105
- 'CH' => :location_changed,
106
- 'DE' => :exception,
107
- 'DL' => :delivered,
108
- 'DP' => :departed_fedex_location,
109
- 'DR' => :vehicle_furnished_not_used,
110
- 'DS' => :vehicle_dispatched,
111
- 'DY' => :exception,
112
- 'EA' => :exception,
113
- 'ED' => :enroute_to_delivery,
114
- 'EO' => :enroute_to_origin_airport,
115
- 'EP' => :enroute_to_pickup,
116
- 'FD' => :at_fedex_destination,
117
- 'HL' => :held_at_location,
118
- 'IT' => :in_transit,
119
- 'LO' => :left_origin,
120
- 'OC' => :order_created,
121
- 'OD' => :out_for_delivery,
122
- 'PF' => :plane_in_flight,
123
- 'PL' => :plane_landed,
124
- 'PU' => :picked_up,
125
- 'RS' => :return_to_shipper,
126
- 'SE' => :exception,
127
- 'SF' => :at_sort_facility,
128
- 'SP' => :split_status,
129
- 'TR' => :transfer
130
- )
131
-
132
- def self.service_name_for_code(service_code)
133
- SERVICE_TYPES[service_code] || "FedEx #{service_code.titleize.sub(/Fedex /, '')}"
134
- end
135
-
136
- def requirements
137
- [:key, :password, :account, :login]
138
- end
139
-
140
- def find_rates(origin, destination, packages, options = {})
141
- options = @options.update(options)
142
- packages = Array(packages)
143
-
144
- rate_request = build_rate_request(origin, destination, packages, options)
145
-
146
- xml = commit(save_request(rate_request), (options[:test] || false))
147
- response = remove_version_prefix(xml)
148
-
149
- parse_rate_response(origin, destination, packages, response, options)
150
- end
151
-
152
- def find_tracking_info(tracking_number, options = {})
153
- options = @options.update(options)
154
-
155
- tracking_request = build_tracking_request(tracking_number, options)
156
- xml = commit(save_request(tracking_request), (options[:test] || false))
157
- response = remove_version_prefix(xml)
158
- parse_tracking_response(response, options)
159
- end
160
-
161
- protected
162
-
163
- def build_rate_request(origin, destination, packages, options = {})
164
- imperial = %w(US LR MM).include?(origin.country_code(:alpha2))
165
-
166
- xml_request = XmlNode.new('RateRequest', 'xmlns' => 'http://fedex.com/ws/rate/v13') do |root_node|
167
- root_node << build_request_header
168
- root_node << build_version_node
169
-
170
- # Returns delivery dates
171
- root_node << XmlNode.new('ReturnTransitAndCommit', true)
172
- # Returns saturday delivery shipping options when available
173
- root_node << XmlNode.new('VariableOptions', 'SATURDAY_DELIVERY')
174
-
175
- root_node << XmlNode.new('RequestedShipment') do |rs|
176
- rs << XmlNode.new('ShipTimestamp', ship_timestamp(options[:turn_around_time]))
177
-
178
- freight = has_freight?(options)
179
-
180
- unless freight
181
- # fedex api wants this up here otherwise request returns an error
182
- rs << XmlNode.new('DropoffType', options[:dropoff_type] || 'REGULAR_PICKUP')
183
- rs << XmlNode.new('PackagingType', options[:packaging_type] || 'YOUR_PACKAGING')
184
- end
185
-
186
- rs << build_location_node('Shipper', (options[:shipper] || origin))
187
- rs << build_location_node('Recipient', destination)
188
- if options[:shipper] and options[:shipper] != origin
189
- rs << build_location_node('Origin', origin)
190
- end
191
-
192
- if freight
193
- # build xml for freight rate requests
194
- freight_options = options[:freight]
195
- rs << build_shipping_charges_payment_node(freight_options)
196
- rs << build_freight_shipment_detail_node(freight_options, packages, imperial)
197
- rs << build_rate_request_types_node
198
- else
199
- # build xml for non-freight rate requests
200
- rs << XmlNode.new('SmartPostDetail') do |spd|
201
- spd << XmlNode.new('Indicia', options[:smart_post_indicia] || 'PARCEL_SELECT')
202
- spd << XmlNode.new('HubId', options[:smart_post_hub_id] || 5902) # default to LA
203
- end
204
-
205
- rs << build_rate_request_types_node
206
- rs << XmlNode.new('PackageCount', packages.size)
207
- rs << build_packages_nodes(packages, imperial)
208
-
209
- end
210
- end
211
- end
212
- xml_request.to_s
213
- end
214
-
215
- def build_packages_nodes(packages, imperial)
216
- packages.map do |pkg|
217
- XmlNode.new('RequestedPackageLineItems') do |rps|
218
- rps << XmlNode.new('GroupPackageCount', 1)
219
- rps << build_package_weight_node(pkg, imperial)
220
- rps << build_package_dimensions_node(pkg, imperial)
221
- end
222
- end
223
- end
224
-
225
- def build_shipping_charges_payment_node(freight_options)
226
- XmlNode.new('ShippingChargesPayment') do |shipping_charges_payment|
227
- shipping_charges_payment << XmlNode.new('PaymentType', freight_options[:payment_type])
228
- shipping_charges_payment << XmlNode.new('Payor') do |payor|
229
- payor << XmlNode.new('ResponsibleParty') do |responsible_party|
230
- # TODO: case of different freight account numbers?
231
- responsible_party << XmlNode.new('AccountNumber', freight_options[:account])
232
- end
233
- end
234
- end
235
- end
236
-
237
- def build_freight_shipment_detail_node(freight_options, packages, imperial)
238
- XmlNode.new('FreightShipmentDetail') do |freight_shipment_detail|
239
- # TODO: case of different freight account numbers?
240
- freight_shipment_detail << XmlNode.new('FedExFreightAccountNumber', freight_options[:account])
241
- freight_shipment_detail << build_location_node('FedExFreightBillingContactAndAddress', freight_options[:billing_location])
242
- freight_shipment_detail << XmlNode.new('Role', freight_options[:role])
243
-
244
- packages.each do |pkg|
245
- freight_shipment_detail << XmlNode.new('LineItems') do |line_items|
246
- line_items << XmlNode.new('FreightClass', freight_options[:freight_class])
247
- line_items << XmlNode.new('Packaging', freight_options[:packaging])
248
- line_items << build_package_weight_node(pkg, imperial)
249
- line_items << build_package_dimensions_node(pkg, imperial)
250
- end
251
- end
252
- end
253
- end
254
-
255
- def has_freight?(options)
256
- options[:freight] && options[:freight].present?
257
- end
258
-
259
- def build_package_weight_node(pkg, imperial)
260
- XmlNode.new('Weight') do |tw|
261
- tw << XmlNode.new('Units', imperial ? 'LB' : 'KG')
262
- tw << XmlNode.new('Value', [((imperial ? pkg.lbs : pkg.kgs).to_f * 1000).round / 1000.0, 0.1].max)
263
- end
264
- end
265
-
266
- def build_version_node
267
- XmlNode.new('Version') do |version_node|
268
- version_node << XmlNode.new('ServiceId', 'crs')
269
- version_node << XmlNode.new('Major', '13')
270
- version_node << XmlNode.new('Intermediate', '0')
271
- version_node << XmlNode.new('Minor', '0')
272
- end
273
- end
274
-
275
- def build_package_dimensions_node(pkg, imperial)
276
- XmlNode.new('Dimensions') do |dimensions|
277
- [:length, :width, :height].each do |axis|
278
- value = ((imperial ? pkg.inches(axis) : pkg.cm(axis)).to_f * 1000).round / 1000.0 # 3 decimals
279
- dimensions << XmlNode.new(axis.to_s.capitalize, value.ceil)
280
- end
281
- dimensions << XmlNode.new('Units', imperial ? 'IN' : 'CM')
282
- end
283
- end
284
-
285
- def build_rate_request_types_node(type = 'ACCOUNT')
286
- XmlNode.new('RateRequestTypes', type)
287
- end
288
-
289
- def build_tracking_request(tracking_number, options = {})
290
- xml_request = XmlNode.new('TrackRequest', 'xmlns' => 'http://fedex.com/ws/track/v3') do |root_node|
291
- root_node << build_request_header
292
-
293
- # Version
294
- root_node << XmlNode.new('Version') do |version_node|
295
- version_node << XmlNode.new('ServiceId', 'trck')
296
- version_node << XmlNode.new('Major', '3')
297
- version_node << XmlNode.new('Intermediate', '0')
298
- version_node << XmlNode.new('Minor', '0')
299
- end
300
-
301
- root_node << XmlNode.new('PackageIdentifier') do |package_node|
302
- package_node << XmlNode.new('Value', tracking_number)
303
- package_node << XmlNode.new('Type', PACKAGE_IDENTIFIER_TYPES[options['package_identifier_type'] || 'tracking_number'])
304
- end
305
-
306
- root_node << XmlNode.new('ShipDateRangeBegin', options['ship_date_range_begin']) if options['ship_date_range_begin']
307
- root_node << XmlNode.new('ShipDateRangeEnd', options['ship_date_range_end']) if options['ship_date_range_end']
308
- root_node << XmlNode.new('IncludeDetailedScans', 1)
309
- end
310
- xml_request.to_s
311
- end
312
-
313
- def build_request_header
314
- web_authentication_detail = XmlNode.new('WebAuthenticationDetail') do |wad|
315
- wad << XmlNode.new('UserCredential') do |uc|
316
- uc << XmlNode.new('Key', @options[:key])
317
- uc << XmlNode.new('Password', @options[:password])
318
- end
319
- end
320
-
321
- client_detail = XmlNode.new('ClientDetail') do |cd|
322
- cd << XmlNode.new('AccountNumber', @options[:account])
323
- cd << XmlNode.new('MeterNumber', @options[:login])
324
- end
325
-
326
- trasaction_detail = XmlNode.new('TransactionDetail') do |td|
327
- td << XmlNode.new('CustomerTransactionId', @options[:transaction_id] || 'ActiveShipping') # TODO: Need to do something better with this..
328
- end
329
-
330
- [web_authentication_detail, client_detail, trasaction_detail]
331
- end
332
-
333
- def build_location_node(name, location)
334
- XmlNode.new(name) do |xml_node|
335
- xml_node << XmlNode.new('Address') do |address_node|
336
- address_node << XmlNode.new('StreetLines', location.address1) if location.address1
337
- address_node << XmlNode.new('StreetLines', location.address2) if location.address2
338
- address_node << XmlNode.new('City', location.city) if location.city
339
- address_node << XmlNode.new('PostalCode', location.postal_code)
340
- address_node << XmlNode.new("CountryCode", location.country_code(:alpha2))
341
-
342
- address_node << XmlNode.new("Residential", true) unless location.commercial?
343
- end
344
- end
345
- end
346
-
347
- def parse_rate_response(origin, destination, packages, response, options)
348
- rate_estimates = []
349
-
350
- xml = build_document(response)
351
- root_node = xml.elements['RateReply']
352
-
353
- success = response_success?(xml)
354
- message = response_message(xml)
355
-
356
- raise ActiveMerchant::Shipping::ResponseContentError.new(StandardError.new('Invalid document'), xml) unless root_node
357
-
358
- root_node.elements.each('RateReplyDetails') do |rated_shipment|
359
- service_code = rated_shipment.get_text('ServiceType').to_s
360
- is_saturday_delivery = rated_shipment.get_text('AppliedOptions').to_s == 'SATURDAY_DELIVERY'
361
- service_type = is_saturday_delivery ? "#{service_code}_SATURDAY_DELIVERY" : service_code
362
-
363
- transit_time = rated_shipment.get_text('TransitTime').to_s if service_code == "FEDEX_GROUND"
364
- max_transit_time = rated_shipment.get_text('MaximumTransitTime').to_s if service_code == "FEDEX_GROUND"
365
-
366
- delivery_timestamp = rated_shipment.get_text('DeliveryTimestamp').to_s
367
-
368
- delivery_range = delivery_range_from(transit_time, max_transit_time, delivery_timestamp, options)
369
-
370
- currency = rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Currency').to_s
371
- rate_estimates << RateEstimate.new(origin, destination, @@name,
372
- self.class.service_name_for_code(service_type),
373
- :service_code => service_code,
374
- :total_price => rated_shipment.get_text('RatedShipmentDetails/ShipmentRateDetail/TotalNetCharge/Amount').to_s.to_f,
375
- :currency => currency,
376
- :packages => packages,
377
- :delivery_range => delivery_range)
378
- end
379
-
380
- if rate_estimates.empty?
381
- success = false
382
- message = "No shipping rates could be found for the destination address" if message.blank?
383
- end
384
-
385
- RateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :request => last_request, :log_xml => options[:log_xml])
386
- end
387
-
388
- def delivery_range_from(transit_time, max_transit_time, delivery_timestamp, options)
389
- delivery_range = [delivery_timestamp, delivery_timestamp]
390
-
391
- # if there's no delivery timestamp but we do have a transit time, use it
392
- if delivery_timestamp.blank? && transit_time.present?
393
- transit_range = parse_transit_times([transit_time, max_transit_time.presence || transit_time])
394
- delivery_range = transit_range.map { |days| business_days_from(ship_date(options[:turn_around_time]), days) }
395
- end
396
-
397
- delivery_range
398
- end
399
-
400
- def business_days_from(date, days)
401
- future_date = date
402
- count = 0
403
-
404
- while count < days
405
- future_date += 1.day
406
- count += 1 if business_day?(future_date)
407
- end
408
-
409
- future_date
410
- end
411
-
412
- def business_day?(date)
413
- (1..5).include?(date.wday)
414
- end
415
-
416
- def parse_tracking_response(response, options)
417
- xml = build_document(response)
418
- root_node = xml.elements['TrackReply']
419
-
420
- success = response_success?(xml)
421
- message = response_message(xml)
422
-
423
- if success
424
- origin = nil
425
- delivery_signature = nil
426
- shipment_events = []
427
-
428
- tracking_details = root_node.elements['TrackDetails']
429
- tracking_number = tracking_details.get_text('TrackingNumber').to_s
430
- status_code = tracking_details.get_text('StatusCode').to_s
431
- status_description = tracking_details.get_text('StatusDescription').to_s
432
- status = TRACKING_STATUS_CODES[status_code]
433
-
434
- if status_code == 'DL' && tracking_details.get_text('SignatureProofOfDeliveryAvailable').to_s == 'true'
435
- delivery_signature = tracking_details.get_text('DeliverySignatureName').to_s
436
- end
437
-
438
- origin_node = tracking_details.elements['OriginLocationAddress']
439
-
440
- if origin_node
441
- origin = Location.new(
442
- :country => origin_node.get_text('CountryCode').to_s,
443
- :province => origin_node.get_text('StateOrProvinceCode').to_s,
444
- :city => origin_node.get_text('City').to_s
445
- )
446
- end
447
-
448
- destination = extract_address(tracking_details, DELIVERY_ADDRESS_NODE_NAMES)
449
- shipper_address = extract_address(tracking_details, SHIPPER_ADDRESS_NODE_NAMES)
450
-
451
- ship_time = extract_timestamp(tracking_details, 'ShipTimestamp')
452
- actual_delivery_time = extract_timestamp(tracking_details, 'ActualDeliveryTimestamp')
453
- scheduled_delivery_time = extract_timestamp(tracking_details, 'EstimatedDeliveryTimestamp')
454
-
455
- tracking_details.elements.each('Events') do |event|
456
- address = event.elements['Address']
457
-
458
- city = address.get_text('City').to_s
459
- state = address.get_text('StateOrProvinceCode').to_s
460
- zip_code = address.get_text('PostalCode').to_s
461
- country = address.get_text('CountryCode').to_s
462
- next if country.blank?
463
-
464
- location = Location.new(:city => city, :state => state, :postal_code => zip_code, :country => country)
465
- description = event.get_text('EventDescription').to_s
466
-
467
- time = Time.parse("#{event.get_text('Timestamp').to_s}")
468
- zoneless_time = time.utc
469
-
470
- shipment_events << ShipmentEvent.new(description, zoneless_time, location)
471
- end
472
- shipment_events = shipment_events.sort_by(&:time)
473
-
474
- end
475
-
476
- TrackingResponse.new(success, message, Hash.from_xml(response),
477
- :carrier => @@name,
478
- :xml => response,
479
- :request => last_request,
480
- :status => status,
481
- :status_code => status_code,
482
- :status_description => status_description,
483
- :ship_time => ship_time,
484
- :scheduled_delivery_date => scheduled_delivery_time,
485
- :actual_delivery_date => actual_delivery_time,
486
- :delivery_signature => delivery_signature,
487
- :shipment_events => shipment_events,
488
- :shipper_address => (shipper_address.nil? || shipper_address.unknown?) ? nil : shipper_address,
489
- :origin => origin,
490
- :destination => destination,
491
- :tracking_number => tracking_number
492
- )
493
- end
494
-
495
- def ship_timestamp(delay_in_hours)
496
- delay_in_hours ||= 0
497
- Time.now + delay_in_hours.hours
498
- end
499
-
500
- def ship_date(delay_in_hours)
501
- delay_in_hours ||= 0
502
- (Time.now + delay_in_hours.hours).to_date
503
- end
504
-
505
- def response_status_node(document)
506
- document.elements['/*/Notifications/']
507
- end
508
-
509
- def response_success?(document)
510
- response_node = response_status_node(document)
511
- return false if response_node.nil?
512
-
513
- %w(SUCCESS WARNING NOTE).include? response_node.get_text('Severity').to_s
514
- end
515
-
516
- def response_message(document)
517
- response_node = response_status_node(document)
518
- return "" if response_node.nil?
519
-
520
- "#{response_node.get_text('Severity')} - #{response_node.get_text('Code')}: #{response_node.get_text('Message')}"
521
- end
522
-
523
- def commit(request, test = false)
524
- ssl_post(test ? TEST_URL : LIVE_URL, request.gsub("\n", ''))
525
- end
526
-
527
- def parse_transit_times(times)
528
- results = []
529
- times.each do |day_count|
530
- days = TRANSIT_TIMES.index(day_count.to_s.chomp)
531
- results << days.to_i
532
- end
533
- results
534
- end
535
-
536
- def extract_address(document, possible_node_names)
537
- node = nil
538
- possible_node_names.each do |name|
539
- node ||= document.elements[name]
540
- break if node
541
- end
542
-
543
- args = if node && node.elements['CountryCode']
544
- {
545
- :country => node.get_text('CountryCode').to_s,
546
- :province => node.get_text('StateOrProvinceCode').to_s,
547
- :city => node.get_text('City').to_s
548
- }
549
- else
550
- {
551
- :country => ActiveMerchant::Country.new(:alpha2 => 'ZZ', :name => 'Unknown or Invalid Territory', :alpha3 => 'ZZZ', :numeric => '999'),
552
- :province => 'unknown',
553
- :city => 'unknown'
554
- }
555
- end
556
-
557
- Location.new(args)
558
- end
559
-
560
- def extract_timestamp(document, node_name)
561
- if timestamp_node = document.elements[node_name]
562
- Time.parse(timestamp_node.to_s).utc
563
- end
564
- end
565
-
566
- def remove_version_prefix(xml)
567
- if xml =~ /xmlns:v[0-9]/
568
- xml.gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
569
- else
570
- xml
571
- end
572
- end
573
-
574
- def build_document(xml)
575
- REXML::Document.new(xml)
576
- rescue REXML::ParseException => e
577
- raise ActiveMerchant::Shipping::ResponseContentError.new(e, xml)
578
- end
579
- end
580
- end
581
- end