active_shipping 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -337,11 +337,17 @@ module ActiveMerchant
337
337
  tracking_number = first_shipment.get_text('ShipmentIdentificationNumber | Package/TrackingNumber').to_s
338
338
 
339
339
  # Build status hash
340
- status_node = first_package.elements['Activity/Status/StatusType']
340
+ status_nodes = first_package.elements.to_a('Activity/Status/StatusType')
341
+
342
+ # Prefer a delivery node
343
+ status_node = status_nodes.detect { |x| x.get_text('Code').to_s == 'D' }
344
+ status_node ||= status_nodes.first
345
+
341
346
  status_code = status_node.get_text('Code').to_s
342
347
  status_description = status_node.get_text('Description').to_s
343
348
  status = TRACKING_STATUS_CODES[status_code]
344
349
 
350
+
345
351
  if status_description =~ /out.*delivery/i
346
352
  status = :out_for_delivery
347
353
  end
@@ -241,6 +241,7 @@ module ActiveMerchant
241
241
 
242
242
  # Once the address verification API is implemented, remove this and have valid_credentials? build the request using that instead.
243
243
  def canned_address_verification_works?
244
+ return false unless @options[:login]
244
245
  request = "%3CCarrierPickupAvailabilityRequest%20USERID=%22#{URI.encode(@options[:login])}%22%3E%20%0A%3CFirmName%3EABC%20Corp.%3C/FirmName%3E%20%0A%3CSuiteOrApt%3ESuite%20777%3C/SuiteOrApt%3E%20%0A%3CAddress2%3E1390%20Market%20Street%3C/Address2%3E%20%0A%3CUrbanization%3E%3C/Urbanization%3E%20%0A%3CCity%3EHouston%3C/City%3E%20%0A%3CState%3ETX%3C/State%3E%20%0A%3CZIP5%3E77058%3C/ZIP5%3E%20%0A%3CZIP4%3E1234%3C/ZIP4%3E%20%0A%3C/CarrierPickupAvailabilityRequest%3E%0A"
245
246
  # expected_hash = {"CarrierPickupAvailabilityResponse"=>{"City"=>"HOUSTON", "Address2"=>"1390 Market Street", "FirmName"=>"ABC Corp.", "State"=>"TX", "Date"=>"3/1/2004", "DayOfWeek"=>"Monday", "Urbanization"=>nil, "ZIP4"=>"1234", "ZIP5"=>"77058", "CarrierRoute"=>"C", "SuiteOrApt"=>"Suite 777"}}
246
247
  xml = REXML::Document.new(commit(:test, request, true))
@@ -26,7 +26,7 @@ module ActiveMerchant #:nodoc:
26
26
  end
27
27
  @total_price = Package.cents_from(options[:total_price])
28
28
  @negotiated_rate = options[:negotiated_rate] ? Package.cents_from(options[:negotiated_rate]) : nil
29
- @currency = options[:currency]
29
+ @currency = ActiveMerchant::CurrencyCode.standardize(options[:currency])
30
30
  @delivery_range = options[:delivery_range] ? options[:delivery_range].map { |date| date_for(date) }.compact : []
31
31
  @shipping_date = date_for(options[:shipping_date])
32
32
  @delivery_date = @delivery_range.last
@@ -1,3 +1,3 @@
1
1
  module ActiveShipping
2
- VERSION = "0.11.2"
2
+ VERSION = "0.12.0"
3
3
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_shipping
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
5
- prerelease:
4
+ version: 0.12.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - James MacAulay
@@ -12,166 +11,146 @@ authors:
12
11
  autorequire:
13
12
  bindir: bin
14
13
  cert_chain: []
15
- date: 2014-01-20 00:00:00.000000000 Z
14
+ date: 2014-05-12 00:00:00.000000000 Z
16
15
  dependencies:
17
16
  - !ruby/object:Gem::Dependency
18
17
  name: activesupport
19
18
  requirement: !ruby/object:Gem::Requirement
20
- none: false
21
19
  requirements:
22
- - - ! '>='
20
+ - - ">="
23
21
  - !ruby/object:Gem::Version
24
22
  version: 2.3.5
25
23
  type: :runtime
26
24
  prerelease: false
27
25
  version_requirements: !ruby/object:Gem::Requirement
28
- none: false
29
26
  requirements:
30
- - - ! '>='
27
+ - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: 2.3.5
33
30
  - !ruby/object:Gem::Dependency
34
31
  name: i18n
35
32
  requirement: !ruby/object:Gem::Requirement
36
- none: false
37
33
  requirements:
38
- - - ! '>='
34
+ - - ">="
39
35
  - !ruby/object:Gem::Version
40
36
  version: '0'
41
37
  type: :runtime
42
38
  prerelease: false
43
39
  version_requirements: !ruby/object:Gem::Requirement
44
- none: false
45
40
  requirements:
46
- - - ! '>='
41
+ - - ">="
47
42
  - !ruby/object:Gem::Version
48
43
  version: '0'
49
44
  - !ruby/object:Gem::Dependency
50
45
  name: active_utils
51
46
  requirement: !ruby/object:Gem::Requirement
52
- none: false
53
47
  requirements:
54
- - - ! '>='
48
+ - - "~>"
55
49
  - !ruby/object:Gem::Version
56
- version: 1.0.1
50
+ version: 2.1.1
57
51
  type: :runtime
58
52
  prerelease: false
59
53
  version_requirements: !ruby/object:Gem::Requirement
60
- none: false
61
54
  requirements:
62
- - - ! '>='
55
+ - - "~>"
63
56
  - !ruby/object:Gem::Version
64
- version: 1.0.1
57
+ version: 2.1.1
65
58
  - !ruby/object:Gem::Dependency
66
59
  name: builder
67
60
  requirement: !ruby/object:Gem::Requirement
68
- none: false
69
61
  requirements:
70
- - - ! '>='
62
+ - - ">="
71
63
  - !ruby/object:Gem::Version
72
64
  version: '0'
73
65
  type: :runtime
74
66
  prerelease: false
75
67
  version_requirements: !ruby/object:Gem::Requirement
76
- none: false
77
68
  requirements:
78
- - - ! '>='
69
+ - - ">="
79
70
  - !ruby/object:Gem::Version
80
71
  version: '0'
81
72
  - !ruby/object:Gem::Dependency
82
73
  name: json
83
74
  requirement: !ruby/object:Gem::Requirement
84
- none: false
85
75
  requirements:
86
- - - ! '>='
76
+ - - ">="
87
77
  - !ruby/object:Gem::Version
88
78
  version: 1.5.1
89
79
  type: :runtime
90
80
  prerelease: false
91
81
  version_requirements: !ruby/object:Gem::Requirement
92
- none: false
93
82
  requirements:
94
- - - ! '>='
83
+ - - ">="
95
84
  - !ruby/object:Gem::Version
96
85
  version: 1.5.1
97
86
  - !ruby/object:Gem::Dependency
98
87
  name: minitest
99
88
  requirement: !ruby/object:Gem::Requirement
100
- none: false
101
89
  requirements:
102
- - - ~>
90
+ - - "~>"
103
91
  - !ruby/object:Gem::Version
104
92
  version: 4.7.5
105
93
  type: :development
106
94
  prerelease: false
107
95
  version_requirements: !ruby/object:Gem::Requirement
108
- none: false
109
96
  requirements:
110
- - - ~>
97
+ - - "~>"
111
98
  - !ruby/object:Gem::Version
112
99
  version: 4.7.5
113
100
  - !ruby/object:Gem::Dependency
114
101
  name: rake
115
102
  requirement: !ruby/object:Gem::Requirement
116
- none: false
117
103
  requirements:
118
- - - ! '>='
104
+ - - ">="
119
105
  - !ruby/object:Gem::Version
120
106
  version: '0'
121
107
  type: :development
122
108
  prerelease: false
123
109
  version_requirements: !ruby/object:Gem::Requirement
124
- none: false
125
110
  requirements:
126
- - - ! '>='
111
+ - - ">="
127
112
  - !ruby/object:Gem::Version
128
113
  version: '0'
129
114
  - !ruby/object:Gem::Dependency
130
115
  name: mocha
131
116
  requirement: !ruby/object:Gem::Requirement
132
- none: false
133
117
  requirements:
134
- - - ~>
118
+ - - "~>"
135
119
  - !ruby/object:Gem::Version
136
120
  version: 0.14.0
137
121
  type: :development
138
122
  prerelease: false
139
123
  version_requirements: !ruby/object:Gem::Requirement
140
- none: false
141
124
  requirements:
142
- - - ~>
125
+ - - "~>"
143
126
  - !ruby/object:Gem::Version
144
127
  version: 0.14.0
145
128
  - !ruby/object:Gem::Dependency
146
129
  name: timecop
147
130
  requirement: !ruby/object:Gem::Requirement
148
- none: false
149
131
  requirements:
150
- - - ! '>='
132
+ - - ">="
151
133
  - !ruby/object:Gem::Version
152
134
  version: '0'
153
135
  type: :development
154
136
  prerelease: false
155
137
  version_requirements: !ruby/object:Gem::Requirement
156
- none: false
157
138
  requirements:
158
- - - ! '>='
139
+ - - ">="
159
140
  - !ruby/object:Gem::Version
160
141
  version: '0'
161
142
  - !ruby/object:Gem::Dependency
162
143
  name: nokogiri
163
144
  requirement: !ruby/object:Gem::Requirement
164
- none: false
165
145
  requirements:
166
- - - ! '>='
146
+ - - ">="
167
147
  - !ruby/object:Gem::Version
168
148
  version: '0'
169
149
  type: :development
170
150
  prerelease: false
171
151
  version_requirements: !ruby/object:Gem::Requirement
172
- none: false
173
152
  requirements:
174
- - - ! '>='
153
+ - - ">="
175
154
  - !ruby/object:Gem::Version
176
155
  version: '0'
177
156
  description: Get rates and tracking info from various shipping carriers.
@@ -181,8 +160,13 @@ executables: []
181
160
  extensions: []
182
161
  extra_rdoc_files: []
183
162
  files:
163
+ - CHANGELOG
164
+ - MIT-LICENSE
165
+ - README.markdown
166
+ - lib/active_shipping.rb
184
167
  - lib/active_shipping/shipping/base.rb
185
168
  - lib/active_shipping/shipping/carrier.rb
169
+ - lib/active_shipping/shipping/carriers.rb
186
170
  - lib/active_shipping/shipping/carriers/benchmark_carrier.rb
187
171
  - lib/active_shipping/shipping/carriers/bogus_carrier.rb
188
172
  - lib/active_shipping/shipping/carriers/canada_post.rb
@@ -191,11 +175,9 @@ files:
191
175
  - lib/active_shipping/shipping/carriers/kunaki.rb
192
176
  - lib/active_shipping/shipping/carriers/new_zealand_post.rb
193
177
  - lib/active_shipping/shipping/carriers/shipwire.rb
178
+ - lib/active_shipping/shipping/carriers/stamps.rb
194
179
  - lib/active_shipping/shipping/carriers/ups.rb
195
- - lib/active_shipping/shipping/carriers/ups.rb.orig
196
180
  - lib/active_shipping/shipping/carriers/usps.rb
197
- - lib/active_shipping/shipping/carriers/usps.rb.orig
198
- - lib/active_shipping/shipping/carriers.rb
199
181
  - lib/active_shipping/shipping/errors.rb
200
182
  - lib/active_shipping/shipping/location.rb
201
183
  - lib/active_shipping/shipping/package.rb
@@ -207,55 +189,47 @@ files:
207
189
  - lib/active_shipping/shipping/shipping_response.rb
208
190
  - lib/active_shipping/shipping/tracking_response.rb
209
191
  - lib/active_shipping/version.rb
210
- - lib/active_shipping.rb
211
192
  - lib/certs/eParcel.dtd
193
+ - lib/vendor/quantified/MIT-LICENSE
194
+ - lib/vendor/quantified/README.markdown
195
+ - lib/vendor/quantified/Rakefile
212
196
  - lib/vendor/quantified/init.rb
197
+ - lib/vendor/quantified/lib/quantified.rb
213
198
  - lib/vendor/quantified/lib/quantified/attribute.rb
214
199
  - lib/vendor/quantified/lib/quantified/length.rb
215
200
  - lib/vendor/quantified/lib/quantified/mass.rb
216
- - lib/vendor/quantified/lib/quantified.rb
217
- - lib/vendor/quantified/MIT-LICENSE
218
- - lib/vendor/quantified/Rakefile
219
- - lib/vendor/quantified/README.markdown
220
201
  - lib/vendor/quantified/test/length_test.rb
221
202
  - lib/vendor/quantified/test/mass_test.rb
222
203
  - lib/vendor/quantified/test/test_helper.rb
223
204
  - lib/vendor/test_helper.rb
205
+ - lib/vendor/xml_node/README
206
+ - lib/vendor/xml_node/Rakefile
224
207
  - lib/vendor/xml_node/benchmark/bench_generation.rb
225
208
  - lib/vendor/xml_node/init.rb
226
209
  - lib/vendor/xml_node/lib/xml_node.rb
227
- - lib/vendor/xml_node/Rakefile
228
- - lib/vendor/xml_node/README
229
210
  - lib/vendor/xml_node/test/test_generating.rb
230
211
  - lib/vendor/xml_node/test/test_parsing.rb
231
- - MIT-LICENSE
232
- - README.markdown
233
- - CHANGELOG
234
212
  homepage: http://github.com/shopify/active_shipping
235
213
  licenses: []
214
+ metadata: {}
236
215
  post_install_message:
237
216
  rdoc_options: []
238
217
  require_paths:
239
218
  - lib
240
219
  required_ruby_version: !ruby/object:Gem::Requirement
241
- none: false
242
220
  requirements:
243
- - - ! '>='
221
+ - - ">="
244
222
  - !ruby/object:Gem::Version
245
223
  version: '0'
246
- segments:
247
- - 0
248
- hash: 3769244612129544255
249
224
  required_rubygems_version: !ruby/object:Gem::Requirement
250
- none: false
251
225
  requirements:
252
- - - ! '>='
226
+ - - ">="
253
227
  - !ruby/object:Gem::Version
254
228
  version: 1.3.6
255
229
  requirements: []
256
230
  rubyforge_project: active_shipping
257
- rubygems_version: 1.8.23
231
+ rubygems_version: 2.2.2
258
232
  signing_key:
259
- specification_version: 3
233
+ specification_version: 4
260
234
  summary: Shipping API extension for Active Merchant
261
235
  test_files: []
@@ -1,456 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- module ActiveMerchant
4
- module Shipping
5
- class UPS < Carrier
6
- self.retry_safe = true
7
-
8
- cattr_accessor :default_options
9
- cattr_reader :name
10
- @@name = "UPS"
11
-
12
- TEST_URL = 'https://wwwcie.ups.com'
13
- LIVE_URL = 'https://onlinetools.ups.com'
14
-
15
- RESOURCES = {
16
- :rates => 'ups.app/xml/Rate',
17
- :track => 'ups.app/xml/Track'
18
- }
19
-
20
- PICKUP_CODES = HashWithIndifferentAccess.new({
21
- :daily_pickup => "01",
22
- :customer_counter => "03",
23
- :one_time_pickup => "06",
24
- :on_call_air => "07",
25
- :suggested_retail_rates => "11",
26
- :letter_center => "19",
27
- :air_service_center => "20"
28
- })
29
-
30
- CUSTOMER_CLASSIFICATIONS = HashWithIndifferentAccess.new({
31
- :wholesale => "01",
32
- :occasional => "03",
33
- :retail => "04"
34
- })
35
-
36
- # these are the defaults described in the UPS API docs,
37
- # but they don't seem to apply them under all circumstances,
38
- # so we need to take matters into our own hands
39
- DEFAULT_CUSTOMER_CLASSIFICATIONS = Hash.new do |hash,key|
40
- hash[key] = case key.to_sym
41
- when :daily_pickup then :wholesale
42
- when :customer_counter then :retail
43
- else
44
- :occasional
45
- end
46
- end
47
-
48
- DEFAULT_SERVICES = {
49
- "01" => "UPS Next Day Air",
50
- "02" => "UPS Second Day Air",
51
- "03" => "UPS Ground",
52
- "07" => "UPS Worldwide Express",
53
- "08" => "UPS Worldwide Expedited",
54
- "11" => "UPS Standard",
55
- "12" => "UPS Three-Day Select",
56
- "13" => "UPS Next Day Air Saver",
57
- "14" => "UPS Next Day Air Early A.M.",
58
- "54" => "UPS Worldwide Express Plus",
59
- "59" => "UPS Second Day Air A.M.",
60
- "65" => "UPS Saver",
61
- "82" => "UPS Today Standard",
62
- "83" => "UPS Today Dedicated Courier",
63
- "84" => "UPS Today Intercity",
64
- "85" => "UPS Today Express",
65
- "86" => "UPS Today Express Saver"
66
- }
67
-
68
- CANADA_ORIGIN_SERVICES = {
69
- "01" => "UPS Express",
70
- "02" => "UPS Expedited",
71
- "14" => "UPS Express Early A.M."
72
- }
73
-
74
- MEXICO_ORIGIN_SERVICES = {
75
- "07" => "UPS Express",
76
- "08" => "UPS Expedited",
77
- "54" => "UPS Express Plus"
78
- }
79
-
80
- EU_ORIGIN_SERVICES = {
81
- "07" => "UPS Express",
82
- "08" => "UPS Expedited"
83
- }
84
-
85
- OTHER_NON_US_ORIGIN_SERVICES = {
86
- "07" => "UPS Express"
87
- }
88
-
89
- TRACKING_STATUS_CODES = HashWithIndifferentAccess.new({
90
- 'I' => :in_transit,
91
- 'D' => :delivered,
92
- 'X' => :exception,
93
- 'P' => :pickup,
94
- 'M' => :manifest_pickup
95
- })
96
-
97
- # From http://en.wikipedia.org/w/index.php?title=European_Union&oldid=174718707 (Current as of November 30, 2007)
98
- EU_COUNTRY_CODES = ["GB", "AT", "BE", "BG", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE"]
99
-
100
- US_TERRITORIES_TREATED_AS_COUNTRIES = ["AS", "FM", "GU", "MH", "MP", "PW", "PR", "VI"]
101
-
102
- def requirements
103
- [:key, :login, :password]
104
- end
105
-
106
- def find_rates(origin, destination, packages, options={})
107
- origin, destination = upsified_location(origin), upsified_location(destination)
108
- options = @options.merge(options)
109
- packages = Array(packages)
110
- access_request = build_access_request
111
- rate_request = build_rate_request(origin, destination, packages, options)
112
- response = commit(:rates, save_request(access_request + rate_request), (options[:test] || false))
113
- parse_rate_response(origin, destination, packages, response, options)
114
- end
115
-
116
- def find_tracking_info(tracking_number, options={})
117
- options = @options.update(options)
118
- access_request = build_access_request
119
- tracking_request = build_tracking_request(tracking_number, options)
120
- response = commit(:track, save_request(access_request + tracking_request), (options[:test] || false))
121
- parse_tracking_response(response, options)
122
- end
123
-
124
- protected
125
-
126
- def upsified_location(location)
127
- if location.country_code == 'US' && US_TERRITORIES_TREATED_AS_COUNTRIES.include?(location.state)
128
- atts = {:country => location.state}
129
- [:zip, :city, :address1, :address2, :address3, :phone, :fax, :address_type].each do |att|
130
- atts[att] = location.send(att)
131
- end
132
- Location.new(atts)
133
- else
134
- location
135
- end
136
- end
137
-
138
- def build_access_request
139
- xml_request = XmlNode.new('AccessRequest') do |access_request|
140
- access_request << XmlNode.new('AccessLicenseNumber', @options[:key])
141
- access_request << XmlNode.new('UserId', @options[:login])
142
- access_request << XmlNode.new('Password', @options[:password])
143
- end
144
- xml_request.to_s
145
- end
146
-
147
- def build_rate_request(origin, destination, packages, options={})
148
- packages = Array(packages)
149
- xml_request = XmlNode.new('RatingServiceSelectionRequest') do |root_node|
150
- root_node << XmlNode.new('Request') do |request|
151
- request << XmlNode.new('RequestAction', 'Rate')
152
- request << XmlNode.new('RequestOption', 'Shop')
153
- # not implemented: 'Rate' RequestOption to specify a single service query
154
- # request << XmlNode.new('RequestOption', ((options[:service].nil? or options[:service] == :all) ? 'Shop' : 'Rate'))
155
- end
156
-
157
- pickup_type = options[:pickup_type] || :daily_pickup
158
-
159
- root_node << XmlNode.new('PickupType') do |pickup_type_node|
160
- pickup_type_node << XmlNode.new('Code', PICKUP_CODES[pickup_type])
161
- # not implemented: PickupType/PickupDetails element
162
- end
163
- cc = options[:customer_classification] || DEFAULT_CUSTOMER_CLASSIFICATIONS[pickup_type]
164
- root_node << XmlNode.new('CustomerClassification') do |cc_node|
165
- cc_node << XmlNode.new('Code', CUSTOMER_CLASSIFICATIONS[cc])
166
- end
167
-
168
- root_node << XmlNode.new('Shipment') do |shipment|
169
- # not implemented: Shipment/Description element
170
- shipment << build_location_node('Shipper', (options[:shipper] || origin), options)
171
- shipment << build_location_node('ShipTo', destination, options)
172
- if options[:shipper] and options[:shipper] != origin
173
- shipment << build_location_node('ShipFrom', origin, options)
174
- end
175
-
176
- # not implemented: * Shipment/ShipmentWeight element
177
- # * Shipment/ReferenceNumber element
178
- # * Shipment/Service element
179
- # * Shipment/PickupDate element
180
- # * Shipment/ScheduledDeliveryDate element
181
- # * Shipment/ScheduledDeliveryTime element
182
- # * Shipment/AlternateDeliveryTime element
183
- # * Shipment/DocumentsOnly element
184
-
185
- packages.each do |package|
186
- imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
187
-
188
- shipment << XmlNode.new("Package") do |package_node|
189
-
190
- # not implemented: * Shipment/Package/PackagingType element
191
- # * Shipment/Package/Description element
192
-
193
- package_node << XmlNode.new("PackagingType") do |packaging_type|
194
- packaging_type << XmlNode.new("Code", '02')
195
- end
196
-
197
- package_node << XmlNode.new("Dimensions") do |dimensions|
198
- dimensions << XmlNode.new("UnitOfMeasurement") do |units|
199
- units << XmlNode.new("Code", imperial ? 'IN' : 'CM')
200
- end
201
- [:length,:width,:height].each do |axis|
202
- value = ((imperial ? package.inches(axis) : package.cm(axis)).to_f*1000).round/1000.0 # 3 decimals
203
- dimensions << XmlNode.new(axis.to_s.capitalize, [value,0.1].max)
204
- end
205
- end
206
-
207
- package_node << XmlNode.new("PackageWeight") do |package_weight|
208
- package_weight << XmlNode.new("UnitOfMeasurement") do |units|
209
- units << XmlNode.new("Code", imperial ? 'LBS' : 'KGS')
210
- end
211
-
212
- value = ((imperial ? package.lbs : package.kgs).to_f*1000).round/1000.0 # 3 decimals
213
- package_weight << XmlNode.new("Weight", [value,0.1].max)
214
- end
215
-
216
- # not implemented: * Shipment/Package/LargePackageIndicator element
217
- # * Shipment/Package/ReferenceNumber element
218
- # * Shipment/Package/PackageServiceOptions element
219
- # * Shipment/Package/AdditionalHandling element
220
- end
221
-
222
- end
223
-
224
- # not implemented: * Shipment/ShipmentServiceOptions element
225
- if options[:origin_account]
226
- shipment << XmlNode.new("RateInformation") do |rate_info_node|
227
- rate_info_node << XmlNode.new("NegotiatedRatesIndicator")
228
- end
229
- end
230
-
231
- end
232
-
233
- end
234
- xml_request.to_s
235
- end
236
-
237
- def build_tracking_request(tracking_number, options={})
238
- xml_request = XmlNode.new('TrackRequest') do |root_node|
239
- root_node << XmlNode.new('Request') do |request|
240
- request << XmlNode.new('RequestAction', 'Track')
241
- request << XmlNode.new('RequestOption', '1')
242
- end
243
- root_node << XmlNode.new('TrackingNumber', tracking_number.to_s)
244
- end
245
- xml_request.to_s
246
- end
247
-
248
- def build_location_node(name,location,options={})
249
- # not implemented: * Shipment/Shipper/Name element
250
- # * Shipment/(ShipTo|ShipFrom)/CompanyName element
251
- # * Shipment/(Shipper|ShipTo|ShipFrom)/AttentionName element
252
- # * Shipment/(Shipper|ShipTo|ShipFrom)/TaxIdentificationNumber element
253
- location_node = XmlNode.new(name) do |location_node|
254
- location_node << XmlNode.new('PhoneNumber', location.phone.gsub(/[^\d]/,'')) unless location.phone.blank?
255
- location_node << XmlNode.new('FaxNumber', location.fax.gsub(/[^\d]/,'')) unless location.fax.blank?
256
-
257
- if name == 'Shipper' and (origin_account = @options[:origin_account] || options[:origin_account])
258
- location_node << XmlNode.new('ShipperNumber', origin_account)
259
- elsif name == 'ShipTo' and (destination_account = @options[:destination_account] || options[:destination_account])
260
- location_node << XmlNode.new('ShipperAssignedIdentificationNumber', destination_account)
261
- end
262
-
263
- location_node << XmlNode.new('Address') do |address|
264
- address << XmlNode.new("AddressLine1", location.address1) unless location.address1.blank?
265
- address << XmlNode.new("AddressLine2", location.address2) unless location.address2.blank?
266
- address << XmlNode.new("AddressLine3", location.address3) unless location.address3.blank?
267
- address << XmlNode.new("City", location.city) unless location.city.blank?
268
- address << XmlNode.new("StateProvinceCode", location.province) unless location.province.blank?
269
- # StateProvinceCode required for negotiated rates but not otherwise, for some reason
270
- address << XmlNode.new("PostalCode", location.postal_code) unless location.postal_code.blank?
271
- address << XmlNode.new("CountryCode", location.country_code(:alpha2)) unless location.country_code(:alpha2).blank?
272
- address << XmlNode.new("ResidentialAddressIndicator", true) unless location.commercial? # the default should be that UPS returns residential rates for destinations that it doesn't know about
273
- # not implemented: Shipment/(Shipper|ShipTo|ShipFrom)/Address/ResidentialAddressIndicator element
274
- end
275
- end
276
- end
277
-
278
- def parse_rate_response(origin, destination, packages, response, options={})
279
- rates = []
280
- File.open('/Users/timmy/code/active_shipping/foo.txt', 'w') {|f| f.write(response) }
281
- xml = REXML::Document.new(response)
282
- success = response_success?(xml)
283
- message = response_message(xml)
284
-
285
- if success
286
- rate_estimates = []
287
-
288
- xml.elements.each('/*/RatedShipment') do |rated_shipment|
289
- service_code = rated_shipment.get_text('Service/Code').to_s
290
- days_to_delivery = rated_shipment.get_text('GuaranteedDaysToDelivery').to_s.to_i
291
- delivery_date = days_to_delivery >= 1 ? days_to_delivery.days.from_now.strftime("%Y-%m-%d") : nil
292
- rate_estimates << RateEstimate.new(origin, destination, @@name,
293
- service_name_for(origin, service_code),
294
- :total_price => rated_shipment.get_text('TotalCharges/MonetaryValue').to_s.to_f,
295
- :currency => rated_shipment.get_text('TotalCharges/CurrencyCode').to_s,
296
- :service_code => service_code,
297
- :packages => packages,
298
- :delivery_range => [delivery_date],
299
- :negotiated_rate => rated_shipment.get_text('NegotiatedRates/NetSummaryCharges/GrandTotal/MonetaryValue').to_s.to_f)
300
- end
301
- end
302
- RateResponse.new(success, message, Hash.from_xml(response).values.first, :rates => rate_estimates, :xml => response, :request => last_request)
303
- end
304
-
305
- def parse_tracking_response(response, options={})
306
- xml = REXML::Document.new(response)
307
- success = response_success?(xml)
308
- message = response_message(xml)
309
-
310
- if success
311
- tracking_number, origin, destination, status_code, status_description = nil
312
- delivered, exception = false
313
- exception_event = nil
314
- shipment_events = []
315
- status = {}
316
- scheduled_delivery_date = nil
317
-
318
- first_shipment = xml.elements['/*/Shipment']
319
- first_package = first_shipment.elements['Package']
320
- tracking_number = first_shipment.get_text('ShipmentIdentificationNumber | Package/TrackingNumber').to_s
321
-
322
- # Build status hash
323
- status_node = first_package.elements['Activity/Status/StatusType']
324
- status_code = status_node.get_text('Code').to_s
325
- status_description = status_node.get_text('Description').to_s
326
- status = TRACKING_STATUS_CODES[status_code]
327
-
328
- if status_description =~ /out.*delivery/i
329
- status = :out_for_delivery
330
- end
331
-
332
- origin, destination = %w{Shipper ShipTo}.map do |location|
333
- location_from_address_node(first_shipment.elements["#{location}/Address"])
334
- end
335
-
336
- # Get scheduled delivery date
337
- unless status == :delivered
338
- scheduled_delivery_date = parse_ups_datetime({
339
- :date => first_shipment.get_text('ScheduledDeliveryDate'),
340
- :time => nil
341
- })
342
- end
343
-
344
- activities = first_package.get_elements('Activity')
345
- unless activities.empty?
346
- shipment_events = activities.map do |activity|
347
- description = activity.get_text('Status/StatusType/Description').to_s
348
- zoneless_time = if (time = activity.get_text('Time')) &&
349
- (date = activity.get_text('Date'))
350
- time, date = time.to_s, date.to_s
351
- hour, minute, second = time.scan(/\d{2}/)
352
- year, month, day = date[0..3], date[4..5], date[6..7]
353
- Time.utc(year, month, day, hour, minute, second)
354
- end
355
- location = location_from_address_node(activity.elements['ActivityLocation/Address'])
356
- ShipmentEvent.new(description, zoneless_time, location)
357
- end
358
-
359
- shipment_events = shipment_events.sort_by(&:time)
360
-
361
- # UPS will sometimes archive a shipment, stripping all shipment activity except for the delivery
362
- # event (see test/fixtures/xml/delivered_shipment_without_events_tracking_response.xml for an example).
363
- # This adds an origin event to the shipment activity in such cases.
364
- if origin && !(shipment_events.count == 1 && status == :delivered)
365
- first_event = shipment_events[0]
366
- same_country = origin.country_code(:alpha2) == first_event.location.country_code(:alpha2)
367
- same_or_blank_city = first_event.location.city.blank? or first_event.location.city == origin.city
368
- origin_event = ShipmentEvent.new(first_event.name, first_event.time, origin)
369
- if same_country and same_or_blank_city
370
- shipment_events[0] = origin_event
371
- else
372
- shipment_events.unshift(origin_event)
373
- end
374
- end
375
-
376
- # Has the shipment been delivered?
377
- if status == :delivered
378
- if !destination
379
- destination = shipment_events[-1].location
380
- end
381
- shipment_events[-1] = ShipmentEvent.new(shipment_events.last.name, shipment_events.last.time, destination)
382
- end
383
- end
384
-
385
- end
386
- TrackingResponse.new(success, message, Hash.from_xml(response).values.first,
387
- :carrier => @@name,
388
- :xml => response,
389
- :request => last_request,
390
- :status => status,
391
- :status_code => status_code,
392
- :status_description => status_description,
393
- :scheduled_delivery_date => scheduled_delivery_date,
394
- :shipment_events => shipment_events,
395
- :delivered => delivered,
396
- :exception => exception,
397
- :exception_event => exception_event,
398
- :origin => origin,
399
- :destination => destination,
400
- :tracking_number => tracking_number)
401
- end
402
-
403
- def location_from_address_node(address)
404
- return nil unless address
405
- Location.new(
406
- :country => node_text_or_nil(address.elements['CountryCode']),
407
- :postal_code => node_text_or_nil(address.elements['PostalCode']),
408
- :province => node_text_or_nil(address.elements['StateProvinceCode']),
409
- :city => node_text_or_nil(address.elements['City']),
410
- :address1 => node_text_or_nil(address.elements['AddressLine1']),
411
- :address2 => node_text_or_nil(address.elements['AddressLine2']),
412
- :address3 => node_text_or_nil(address.elements['AddressLine3'])
413
- )
414
- end
415
-
416
- def parse_ups_datetime(options = {})
417
- time, date = options[:time].to_s, options[:date].to_s
418
- if time.nil?
419
- hour, minute, second = 0
420
- else
421
- hour, minute, second = time.scan(/\d{2}/)
422
- end
423
- year, month, day = date[0..3], date[4..5], date[6..7]
424
-
425
- Time.utc(year, month, day, hour, minute, second)
426
- end
427
-
428
- def response_success?(xml)
429
- xml.get_text('/*/Response/ResponseStatusCode').to_s == '1'
430
- end
431
-
432
- def response_message(xml)
433
- xml.get_text('/*/Response/Error/ErrorDescription | /*/Response/ResponseStatusDescription').to_s
434
- end
435
-
436
- def commit(action, request, test = false)
437
- ssl_post("#{test ? TEST_URL : LIVE_URL}/#{RESOURCES[action]}", request)
438
- end
439
-
440
-
441
- def service_name_for(origin, code)
442
- origin = origin.country_code(:alpha2)
443
-
444
- name = case origin
445
- when "CA" then CANADA_ORIGIN_SERVICES[code]
446
- when "MX" then MEXICO_ORIGIN_SERVICES[code]
447
- when *EU_COUNTRY_CODES then EU_ORIGIN_SERVICES[code]
448
- end
449
-
450
- name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
451
- name ||= DEFAULT_SERVICES[code]
452
- end
453
-
454
- end
455
- end
456
- end