active_shipping 0.11.2 → 0.12.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.
@@ -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