active_shipping 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -79,7 +79,7 @@ Active Shipping is currently being used and improved in a production environment
79
79
 
80
80
  ### Track a FedEx package
81
81
 
82
- fedex = FedEx.new(:login => '999999999', :password => '7777777')
82
+ fedex = FedEx.new(:login => '999999999', :password => '7777777', key: '1BXXXXXXXXXxrcB', account: '51XXXXX20')
83
83
  tracking_info = fedex.find_tracking_info('tracking-number', :carrier_code => 'fedex_ground') # Ground package
84
84
 
85
85
  tracking_info.shipment_events.each do |event|
@@ -46,7 +46,8 @@ module ActiveMerchant
46
46
  "INTERNATIONAL_ECONOMY_FREIGHT" => "FedEx International Economy Freight",
47
47
  "GROUND_HOME_DELIVERY" => "FedEx Ground Home Delivery",
48
48
  "FEDEX_GROUND" => "FedEx Ground",
49
- "INTERNATIONAL_GROUND" => "FedEx International Ground"
49
+ "INTERNATIONAL_GROUND" => "FedEx International Ground",
50
+ "SMART_POST" => "FedEx SmartPost"
50
51
  }
51
52
 
52
53
  PackageTypes = {
@@ -156,13 +157,13 @@ module ActiveMerchant
156
157
  def build_rate_request(origin, destination, packages, options={})
157
158
  imperial = ['US','LR','MM'].include?(origin.country_code(:alpha2))
158
159
 
159
- xml_request = XmlNode.new('RateRequest', 'xmlns' => 'http://fedex.com/ws/rate/v6') do |root_node|
160
+ xml_request = XmlNode.new('RateRequest', 'xmlns' => 'http://fedex.com/ws/rate/v13') do |root_node|
160
161
  root_node << build_request_header
161
162
 
162
163
  # Version
163
164
  root_node << XmlNode.new('Version') do |version_node|
164
165
  version_node << XmlNode.new('ServiceId', 'crs')
165
- version_node << XmlNode.new('Major', '6')
166
+ version_node << XmlNode.new('Major', '13')
166
167
  version_node << XmlNode.new('Intermediate', '0')
167
168
  version_node << XmlNode.new('Minor', '0')
168
169
  end
@@ -175,6 +176,7 @@ module ActiveMerchant
175
176
  root_node << XmlNode.new('RequestedShipment') do |rs|
176
177
  rs << XmlNode.new('ShipTimestamp', ship_timestamp(options[:turn_around_time]))
177
178
  rs << XmlNode.new('DropoffType', options[:dropoff_type] || 'REGULAR_PICKUP')
179
+ #rs << XmlNode.new('ServiceType', 'SMART_POST') # use this to test responses for specific services.
178
180
  rs << XmlNode.new('PackagingType', options[:packaging_type] || 'YOUR_PACKAGING')
179
181
 
180
182
  rs << build_location_node('Shipper', (options[:shipper] || origin))
@@ -182,11 +184,18 @@ module ActiveMerchant
182
184
  if options[:shipper] and options[:shipper] != origin
183
185
  rs << build_location_node('Origin', origin)
184
186
  end
185
-
187
+
188
+ rs << XmlNode.new('SmartPostDetail') do |spd|
189
+ spd << XmlNode.new('Indicia', options[:smart_post_indicia] || 'PARCEL_SELECT')
190
+ spd << XmlNode.new('HubId', options[:smart_post_hub_id] || 5902) # default to LA
191
+ end
192
+
186
193
  rs << XmlNode.new('RateRequestTypes', 'ACCOUNT')
194
+
187
195
  rs << XmlNode.new('PackageCount', packages.size)
188
196
  packages.each do |pkg|
189
- rs << XmlNode.new('RequestedPackages') do |rps|
197
+ rs << XmlNode.new('RequestedPackageLineItems') do |rps|
198
+ rps << XmlNode.new('GroupPackageCount', 1)
190
199
  rps << XmlNode.new('Weight') do |tw|
191
200
  tw << XmlNode.new('Units', imperial ? 'LB' : 'KG')
192
201
  tw << XmlNode.new('Value', [((imperial ? pkg.lbs : pkg.kgs).to_f*1000).round/1000.0, 0.1].max)
@@ -3,10 +3,10 @@ require 'cgi'
3
3
 
4
4
  module ActiveMerchant
5
5
  module Shipping
6
-
6
+
7
7
  # After getting an API login from USPS (looks like '123YOURNAME456'),
8
8
  # run the following test:
9
- #
9
+ #
10
10
  # usps = USPS.new(:login => '123YOURNAME456', :test => true)
11
11
  # usps.valid_credentials?
12
12
  #
@@ -14,20 +14,20 @@ module ActiveMerchant
14
14
  # to do before they put your API key in production mode.
15
15
  class USPS < Carrier
16
16
  self.retry_safe = true
17
-
17
+
18
18
  cattr_reader :name
19
19
  @@name = "USPS"
20
-
20
+
21
21
  LIVE_DOMAIN = 'production.shippingapis.com'
22
22
  LIVE_RESOURCE = 'ShippingAPI.dll'
23
-
23
+
24
24
  TEST_DOMAINS = { #indexed by security; e.g. TEST_DOMAINS[USE_SSL[:rates]]
25
25
  true => 'secure.shippingapis.com',
26
26
  false => 'testing.shippingapis.com'
27
27
  }
28
-
28
+
29
29
  TEST_RESOURCE = 'ShippingAPITest.dll'
30
-
30
+
31
31
  API_CODES = {
32
32
  :us_rates => 'RateV4',
33
33
  :world_rates => 'IntlRateV2',
@@ -85,10 +85,10 @@ module ActiveMerchant
85
85
  :post_card => 'POSTCARD',
86
86
  :package_service => 'PACKAGESERVICE'
87
87
  }
88
-
88
+
89
89
  # Array of U.S. possessions according to USPS: https://www.usps.com/ship/official-abbreviations.htm
90
90
  US_POSSESSIONS = ["AS", "FM", "GU", "MH", "MP", "PW", "PR", "VI"]
91
-
91
+
92
92
  # TODO: figure out how USPS likes to say "Ivory Coast"
93
93
  #
94
94
  # Country names:
@@ -126,7 +126,6 @@ module ActiveMerchant
126
126
  }
127
127
 
128
128
  STATUS_NODE_PATTERNS = %w(
129
- */*/TrackSummary
130
129
  Error/Description
131
130
  */TrackInfo/Error/Description
132
131
  )
@@ -138,7 +137,7 @@ module ActiveMerchant
138
137
  ]
139
138
 
140
139
  def find_tracking_info(tracking_number, options={})
141
- options = @options.update(options)
140
+ options = @options.update(options)
142
141
  tracking_request = build_tracking_request(tracking_number, options)
143
142
  response = commit(:track, tracking_request, (options[:test] || false))
144
143
  parse_tracking_response(response, options)
@@ -151,9 +150,9 @@ module ActiveMerchant
151
150
  'LARGE'
152
151
  end
153
152
  end
154
-
153
+
155
154
  # from info at http://www.usps.com/businessmail101/mailcharacteristics/parcels.htm
156
- #
155
+ #
157
156
  # package.options[:books] -- 25 lb. limit instead of 35 for books or other printed matter.
158
157
  # Defaults to false.
159
158
  def self.package_machinable?(package, options={})
@@ -167,22 +166,22 @@ module ActiveMerchant
167
166
  package.pounds <= (package.options[:books] ? 25.0 : 35.0)
168
167
  at_least_minimum && at_most_maximum
169
168
  end
170
-
169
+
171
170
  def requirements
172
171
  [:login]
173
172
  end
174
-
173
+
175
174
  def find_rates(origin, destination, packages, options = {})
176
175
  options = @options.merge(options)
177
-
176
+
178
177
  origin = Location.from(origin)
179
178
  destination = Location.from(destination)
180
179
  packages = Array(packages)
181
-
180
+
182
181
  #raise ArgumentError.new("USPS packages must originate in the U.S.") unless ['US',nil].include?(origin.country_code(:alpha2))
183
-
182
+
184
183
  # domestic or international?
185
-
184
+
186
185
  domestic_codes = US_POSSESSIONS + ['US', nil]
187
186
  response = if domestic_codes.include?(destination.country_code(:alpha2))
188
187
  us_rates(origin, destination, packages, options)
@@ -190,89 +189,17 @@ module ActiveMerchant
190
189
  world_rates(origin, destination, packages, options)
191
190
  end
192
191
  end
193
-
192
+
194
193
  def valid_credentials?
195
194
  # Cannot test with find_rates because USPS doesn't allow that in test mode
196
195
  test_mode? ? canned_address_verification_works? : super
197
196
  end
198
-
197
+
199
198
  def maximum_weight
200
199
  Mass.new(70, :pounds)
201
200
  end
202
-
203
- protected
204
- def response_success?(xml)
205
- xml.get_text('/*/Response/ResponseStatusCode').to_s == '1'
206
- end
207
-
208
- def response_message(xml)
209
- xml.get_text('/*/Response/Error/ErrorDescription | /*/Response/ResponseStatusDescription').to_s
210
- end
211
-
212
- def parse_tracking_response(response, options={})
213
- xml = REXML::Document.new(response)
214
- success = response_success?(xml)
215
- message = response_message(xml)
216
-
217
- if success
218
- tracking_number, origin, destination = nil
219
- shipment_events = []
220
-
221
- first_shipment = xml.elements['/*/Shipment']
222
- first_package = first_shipment.elements['Package']
223
- tracking_number = first_shipment.get_text('ShipmentIdentificationNumber | Package/TrackingNumber').to_s
224
-
225
- origin, destination = %w{Shipper ShipTo}.map do |location|
226
- location_from_address_node(first_shipment.elements["#{location}/Address"])
227
- end
228
-
229
- activities = first_package.get_elements('Activity')
230
- unless activities.empty?
231
- shipment_events = activities.map do |activity|
232
- description = activity.get_text('Status/StatusType/Description').to_s
233
- zoneless_time = if (time = activity.get_text('Time')) &&
234
- (date = activity.get_text('Date'))
235
- time, date = time.to_s, date.to_s
236
- hour, minute, second = time.scan(/\d{2}/)
237
- year, month, day = date[0..3], date[4..5], date[6..7]
238
- Time.utc(year, month, day, hour, minute, second)
239
- end
240
- location = location_from_address_node(activity.elements['ActivityLocation/Address'])
241
- ShipmentEvent.new(description, zoneless_time, location)
242
- end
243
-
244
- shipment_events = shipment_events.sort_by(&:time)
245
-
246
- if origin
247
- first_event = shipment_events[0]
248
- same_country = origin.country_code(:alpha2) == first_event.location.country_code(:alpha2)
249
- same_or_blank_city = first_event.location.city.blank? or first_event.location.city == origin.city
250
- origin_event = ShipmentEvent.new(first_event.name, first_event.time, origin)
251
- if same_country and same_or_blank_city
252
- shipment_events[0] = origin_event
253
- else
254
- shipment_events.unshift(origin_event)
255
- end
256
- end
257
- if shipment_events.last.name.downcase == 'delivered'
258
- shipment_events[-1] = ShipmentEvent.new(shipment_events.last.name, shipment_events.last.time, destination)
259
- end
260
- end
261
-
262
- end
263
- TrackingResponse.new(success, message, Hash.from_xml(response).values.first,
264
- :xml => response,
265
- :request => last_request,
266
- :shipment_events => shipment_events,
267
- :origin => origin,
268
- :destination => destination,
269
- :tracking_number => tracking_number)
270
- end
271
-
272
-
273
-
274
-
275
201
 
202
+ protected
276
203
 
277
204
  def build_tracking_request(tracking_number, options={})
278
205
  xml_request = XmlNode.new('TrackRequest', 'USERID' => @options[:login]) do |root_node|
@@ -286,13 +213,13 @@ module ActiveMerchant
286
213
  # never use test mode; rate requests just won't work on test servers
287
214
  parse_rate_response origin, destination, packages, commit(:us_rates,request,false), options
288
215
  end
289
-
216
+
290
217
  def world_rates(origin, destination, packages, options={})
291
218
  request = build_world_rate_request(packages, destination)
292
219
  # never use test mode; rate requests just won't work on test servers
293
220
  parse_rate_response origin, destination, packages, commit(:world_rates,request,false), options
294
221
  end
295
-
222
+
296
223
  # Once the address verification API is implemented, remove this and have valid_credentials? build the request using that instead.
297
224
  def canned_address_verification_works?
298
225
  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"
@@ -301,7 +228,7 @@ module ActiveMerchant
301
228
  xml.get_text('/CarrierPickupAvailabilityResponse/City').to_s == 'HOUSTON' &&
302
229
  xml.get_text('/CarrierPickupAvailabilityResponse/Address2').to_s == '1390 Market Street'
303
230
  end
304
-
231
+
305
232
  # options[:service] -- One of [:first_class, :priority, :express, :bpm, :parcel,
306
233
  # :media, :library, :all]. defaults to :all.
307
234
  # options[:container] -- One of [:envelope, :box]. defaults to neither (this field has
@@ -345,14 +272,14 @@ module ActiveMerchant
345
272
  end
346
273
  URI.encode(save_request(request.to_s))
347
274
  end
348
-
275
+
349
276
  # important difference with international rate requests:
350
277
  # * services are not given in the request
351
278
  # * package sizes are not given in the request
352
279
  # * services are returned in the response along with restrictions of size
353
280
  # * the size restrictions are returned AS AN ENGLISH SENTENCE (!?)
354
281
  #
355
- #
282
+ #
356
283
  # package.options[:mail_type] -- one of [:package, :postcard, :matter_for_the_blind, :envelope].
357
284
  # Defaults to :package.
358
285
  def build_world_rate_request(packages, destination)
@@ -389,14 +316,14 @@ module ActiveMerchant
389
316
  end
390
317
  URI.encode(save_request(request.to_s))
391
318
  end
392
-
319
+
393
320
  def parse_rate_response(origin, destination, packages, response, options={})
394
321
  success = true
395
322
  message = ''
396
323
  rate_hash = {}
397
-
324
+
398
325
  xml = REXML::Document.new(response)
399
-
326
+
400
327
  if error = xml.elements['/Error']
401
328
  success = false
402
329
  message = error.elements['Description'].text
@@ -408,7 +335,7 @@ module ActiveMerchant
408
335
  break
409
336
  end
410
337
  end
411
-
338
+
412
339
  if success
413
340
  rate_hash = rates_from_response_node(xml, packages)
414
341
  unless rate_hash
@@ -416,9 +343,9 @@ module ActiveMerchant
416
343
  message = "Unknown root node in XML response: '#{xml.root.name}'"
417
344
  end
418
345
  end
419
-
346
+
420
347
  end
421
-
348
+
422
349
  if success
423
350
  rate_estimates = rate_hash.keys.map do |service_name|
424
351
  RateEstimate.new(origin,destination,@@name,"USPS #{service_name}",
@@ -429,15 +356,15 @@ module ActiveMerchant
429
356
  rate_estimates.reject! {|e| e.package_count != packages.length}
430
357
  rate_estimates = rate_estimates.sort_by(&:total_price)
431
358
  end
432
-
359
+
433
360
  RateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :request => last_request)
434
361
  end
435
-
362
+
436
363
  def rates_from_response_node(response_node, packages)
437
364
  rate_hash = {}
438
365
  return false unless (root_node = response_node.elements['/IntlRateV2Response | /RateV4Response'])
439
366
  domestic = (root_node.name == 'RateV4Response')
440
-
367
+
441
368
  if @options[:commercial_base]
442
369
  domestic_elements = ['Postage', 'CLASSID', 'MailService', 'CommercialRate']
443
370
  international_elements = ['Service', 'ID', 'SvcDescription', 'CommercialPostage']
@@ -446,10 +373,10 @@ module ActiveMerchant
446
373
  international_elements = ['Service', 'ID', 'SvcDescription', 'Postage']
447
374
  end
448
375
  service_node, service_code_node, service_name_node, rate_node = domestic ? domestic_elements : international_elements
449
-
376
+
450
377
  root_node.each_element('Package') do |package_node|
451
378
  this_package = packages[package_node.attributes['ID'].to_i]
452
-
379
+
453
380
  package_node.each_element(service_node) do |service_response_node|
454
381
  service_name = service_response_node.get_text(service_name_node).to_s
455
382
 
@@ -470,18 +397,18 @@ module ActiveMerchant
470
397
  package_rates = this_service[:package_rates] ||= []
471
398
  this_package_rate = {:package => this_package,
472
399
  :rate => Package.cents_from(service_response_node.get_text(rate_node).to_s.to_f)}
473
-
400
+
474
401
  package_rates << this_package_rate if package_valid_for_service(this_package,service_response_node)
475
402
  end
476
403
  end
477
404
  rate_hash
478
405
  end
479
-
406
+
480
407
  def package_valid_for_service(package, service_node)
481
408
  return true if service_node.elements['MaxWeight'].nil?
482
409
  max_weight = service_node.get_text('MaxWeight').to_s.to_f
483
410
  name = service_node.get_text('SvcDescription | MailService').to_s.downcase
484
-
411
+
485
412
  if name =~ /flat.rate.box/ #domestic or international flat rate box
486
413
  # flat rate dimensions from http://www.usps.com/shipping/flatrate.htm
487
414
  return (package_valid_for_max_dimensions(package,
@@ -507,7 +434,7 @@ module ActiveMerchant
507
434
  #
508
435
  # 'Max. length 46", width 35", height 46" and max. length plus girth 108"'
509
436
  # 'Max. length 24", Max. length, height, depth combined 36"'
510
- #
437
+ #
511
438
  sentence = CGI.unescapeHTML(service_node.get_text('MaxDimensions').to_s)
512
439
  tokens = sentence.downcase.split(/[^\d]*"/).reject {|t| t.empty?}
513
440
  max_dimensions = {:weight => max_weight}
@@ -515,7 +442,7 @@ module ActiveMerchant
515
442
  tokens.each do |token|
516
443
  axis_sum = [/length/,/width/,/height/,/depth/].sum {|regex| (token =~ regex) ? 1 : 0}
517
444
  unless axis_sum == 0
518
- value = token[/\d+$/].to_f
445
+ value = token[/\d+$/].to_f
519
446
  if axis_sum == 3
520
447
  max_dimensions[:length_plus_width_plus_height] = value
521
448
  elsif token =~ /girth/ and axis_sum == 1
@@ -532,7 +459,7 @@ module ActiveMerchant
532
459
  return package_valid_for_max_dimensions(package, max_dimensions)
533
460
  end
534
461
  end
535
-
462
+
536
463
  def package_valid_for_max_dimensions(package,dimensions)
537
464
  valid = ((not ([:length,:width,:height].map {|dim| dimensions[dim].nil? || dimensions[dim].to_f >= package.inches(dim).to_f}.include?(false))) and
538
465
  (dimensions[:weight].nil? || dimensions[:weight] >= package.pounds) and
@@ -549,18 +476,18 @@ module ActiveMerchant
549
476
  def parse_tracking_response(response, options)
550
477
  xml = REXML::Document.new(response)
551
478
  root_node = xml.elements['TrackResponse']
552
-
479
+
553
480
  success = response_success?(xml)
554
481
  message = response_message(xml)
555
-
482
+
556
483
  if success
557
484
  tracking_number, origin, destination = nil
558
485
  shipment_events = []
559
486
  tracking_details = xml.elements.collect('*/*/TrackDetail'){ |e| e }
560
-
487
+
561
488
  tracking_summary = xml.elements.collect('*/*/TrackSummary'){ |e| e }.first
562
489
  tracking_details << tracking_summary
563
-
490
+
564
491
  tracking_number = root_node.elements['TrackInfo'].attributes['ID'].to_s
565
492
 
566
493
  tracking_details.each do |event|
@@ -584,7 +511,7 @@ module ActiveMerchant
584
511
  end
585
512
  shipment_events = shipment_events.sort_by(&:time)
586
513
  end
587
-
514
+
588
515
  TrackingResponse.new(success, message, Hash.from_xml(response),
589
516
  :carrier => @@name,
590
517
  :xml => response,
@@ -594,40 +521,66 @@ module ActiveMerchant
594
521
  :tracking_number => tracking_number
595
522
  )
596
523
  end
597
-
598
- def response_status_node(document)
524
+
525
+ def track_summary_node(document)
526
+ document.elements['*/*/TrackSummary']
527
+ end
528
+
529
+ def error_description_node(document)
599
530
  STATUS_NODE_PATTERNS.each do |pattern|
600
531
  if node = document.elements[pattern]
601
532
  return node
602
533
  end
603
534
  end
604
535
  end
605
-
536
+
537
+ def response_status_node(document)
538
+ track_summary_node(document) || error_description_node(document)
539
+ end
540
+
541
+ def has_error?(document)
542
+ !!document.elements['Error']
543
+ end
544
+
545
+ def no_record?(document)
546
+ summary_node = track_summary_node(document)
547
+ if summary_node
548
+ summary = summary_node.get_text.to_s
549
+ RESPONSE_ERROR_MESSAGES.detect { |re| summary =~ re }
550
+ summary =~ /There is no record of that mail item/ || summary =~ /This Information has not been included in this Test Server\./
551
+ else
552
+ false
553
+ end
554
+ end
555
+
556
+ def tracking_info_error?(document)
557
+ document.elements['*/TrackInfo/Error']
558
+ end
559
+
606
560
  def response_success?(document)
607
- summary = response_status_node(document).get_text.to_s
608
- !RESPONSE_ERROR_MESSAGES.detect { |re| summary =~ re }
561
+ !(has_error?(document) || no_record?(document) || tracking_info_error?(document))
609
562
  end
610
-
563
+
611
564
  def response_message(document)
612
565
  response_node = response_status_node(document)
613
- response_status_node(document).get_text.to_s
566
+ response_node.get_text.to_s
614
567
  end
615
-
568
+
616
569
  def commit(action, request, test = false)
617
570
  ssl_get(request_url(action, request, test))
618
571
  end
619
-
572
+
620
573
  def request_url(action, request, test)
621
574
  scheme = USE_SSL[action] ? 'https://' : 'http://'
622
575
  host = test ? TEST_DOMAINS[USE_SSL[action]] : LIVE_DOMAIN
623
576
  resource = test ? TEST_RESOURCE : LIVE_RESOURCE
624
577
  "#{scheme}#{host}/#{resource}?API=#{API_CODES[action]}&XML=#{request}"
625
578
  end
626
-
579
+
627
580
  def strip_zip(zip)
628
581
  zip.to_s.scan(/\d{5}/).first || zip
629
582
  end
630
-
583
+
631
584
  end
632
585
  end
633
586
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveShipping
2
- VERSION = "0.10.1"
2
+ VERSION = "0.11.0"
3
3
  end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_shipping
3
3
  version: !ruby/object:Gem::Version
4
+ version: 0.11.0
4
5
  prerelease:
5
- version: 0.10.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - James MacAulay
@@ -12,168 +12,168 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-08-23 00:00:00.000000000 Z
15
+ date: 2013-09-15 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
- prerelease: false
19
18
  name: activesupport
20
- type: :runtime
21
- version_requirements: !ruby/object:Gem::Requirement
19
+ requirement: !ruby/object:Gem::Requirement
20
+ none: false
22
21
  requirements:
23
22
  - - ! '>='
24
23
  - !ruby/object:Gem::Version
25
24
  version: 2.3.5
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
26
28
  none: false
27
- requirement: !ruby/object:Gem::Requirement
28
29
  requirements:
29
30
  - - ! '>='
30
31
  - !ruby/object:Gem::Version
31
32
  version: 2.3.5
32
- none: false
33
33
  - !ruby/object:Gem::Dependency
34
- prerelease: false
35
34
  name: i18n
36
- type: :runtime
37
- version_requirements: !ruby/object:Gem::Requirement
35
+ requirement: !ruby/object:Gem::Requirement
36
+ none: false
38
37
  requirements:
39
38
  - - ! '>='
40
39
  - !ruby/object:Gem::Version
41
40
  version: '0'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
42
44
  none: false
43
- requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
46
  - - ! '>='
46
47
  - !ruby/object:Gem::Version
47
48
  version: '0'
48
- none: false
49
49
  - !ruby/object:Gem::Dependency
50
- prerelease: false
51
50
  name: active_utils
52
- type: :runtime
53
- version_requirements: !ruby/object:Gem::Requirement
51
+ requirement: !ruby/object:Gem::Requirement
52
+ none: false
54
53
  requirements:
55
54
  - - ! '>='
56
55
  - !ruby/object:Gem::Version
57
56
  version: 1.0.1
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
58
60
  none: false
59
- requirement: !ruby/object:Gem::Requirement
60
61
  requirements:
61
62
  - - ! '>='
62
63
  - !ruby/object:Gem::Version
63
64
  version: 1.0.1
64
- none: false
65
65
  - !ruby/object:Gem::Dependency
66
- prerelease: false
67
66
  name: builder
68
- type: :runtime
69
- version_requirements: !ruby/object:Gem::Requirement
67
+ requirement: !ruby/object:Gem::Requirement
68
+ none: false
70
69
  requirements:
71
70
  - - ! '>='
72
71
  - !ruby/object:Gem::Version
73
72
  version: '0'
73
+ type: :runtime
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
74
76
  none: false
75
- requirement: !ruby/object:Gem::Requirement
76
77
  requirements:
77
78
  - - ! '>='
78
79
  - !ruby/object:Gem::Version
79
80
  version: '0'
80
- none: false
81
81
  - !ruby/object:Gem::Dependency
82
- prerelease: false
83
82
  name: json
84
- type: :runtime
85
- version_requirements: !ruby/object:Gem::Requirement
83
+ requirement: !ruby/object:Gem::Requirement
84
+ none: false
86
85
  requirements:
87
86
  - - ! '>='
88
87
  - !ruby/object:Gem::Version
89
88
  version: 1.5.1
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
90
92
  none: false
91
- requirement: !ruby/object:Gem::Requirement
92
93
  requirements:
93
94
  - - ! '>='
94
95
  - !ruby/object:Gem::Version
95
96
  version: 1.5.1
96
- none: false
97
97
  - !ruby/object:Gem::Dependency
98
- prerelease: false
99
98
  name: minitest
100
- type: :development
101
- version_requirements: !ruby/object:Gem::Requirement
99
+ requirement: !ruby/object:Gem::Requirement
100
+ none: false
102
101
  requirements:
103
102
  - - ~>
104
103
  - !ruby/object:Gem::Version
105
104
  version: 4.7.5
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
106
108
  none: false
107
- requirement: !ruby/object:Gem::Requirement
108
109
  requirements:
109
110
  - - ~>
110
111
  - !ruby/object:Gem::Version
111
112
  version: 4.7.5
112
- none: false
113
113
  - !ruby/object:Gem::Dependency
114
- prerelease: false
115
114
  name: rake
116
- type: :development
117
- version_requirements: !ruby/object:Gem::Requirement
115
+ requirement: !ruby/object:Gem::Requirement
116
+ none: false
118
117
  requirements:
119
118
  - - ! '>='
120
119
  - !ruby/object:Gem::Version
121
120
  version: '0'
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: !ruby/object:Gem::Requirement
122
124
  none: false
123
- requirement: !ruby/object:Gem::Requirement
124
125
  requirements:
125
126
  - - ! '>='
126
127
  - !ruby/object:Gem::Version
127
128
  version: '0'
128
- none: false
129
129
  - !ruby/object:Gem::Dependency
130
- prerelease: false
131
130
  name: mocha
132
- type: :development
133
- version_requirements: !ruby/object:Gem::Requirement
131
+ requirement: !ruby/object:Gem::Requirement
132
+ none: false
134
133
  requirements:
135
134
  - - ~>
136
135
  - !ruby/object:Gem::Version
137
136
  version: 0.14.0
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
138
140
  none: false
139
- requirement: !ruby/object:Gem::Requirement
140
141
  requirements:
141
142
  - - ~>
142
143
  - !ruby/object:Gem::Version
143
144
  version: 0.14.0
144
- none: false
145
145
  - !ruby/object:Gem::Dependency
146
- prerelease: false
147
146
  name: timecop
148
- type: :development
149
- version_requirements: !ruby/object:Gem::Requirement
147
+ requirement: !ruby/object:Gem::Requirement
148
+ none: false
150
149
  requirements:
151
150
  - - ! '>='
152
151
  - !ruby/object:Gem::Version
153
152
  version: '0'
153
+ type: :development
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
154
156
  none: false
155
- requirement: !ruby/object:Gem::Requirement
156
157
  requirements:
157
158
  - - ! '>='
158
159
  - !ruby/object:Gem::Version
159
160
  version: '0'
160
- none: false
161
161
  - !ruby/object:Gem::Dependency
162
- prerelease: false
163
162
  name: nokogiri
164
- type: :development
165
- version_requirements: !ruby/object:Gem::Requirement
163
+ requirement: !ruby/object:Gem::Requirement
164
+ none: false
166
165
  requirements:
167
166
  - - ! '>='
168
167
  - !ruby/object:Gem::Version
169
168
  version: '0'
169
+ type: :development
170
+ prerelease: false
171
+ version_requirements: !ruby/object:Gem::Requirement
170
172
  none: false
171
- requirement: !ruby/object:Gem::Requirement
172
173
  requirements:
173
174
  - - ! '>='
174
175
  - !ruby/object:Gem::Version
175
176
  version: '0'
176
- none: false
177
177
  description: Get rates and tracking info from various shipping carriers.
178
178
  email:
179
179
  - james@shopify.com
@@ -237,20 +237,20 @@ rdoc_options: []
237
237
  require_paths:
238
238
  - lib
239
239
  required_ruby_version: !ruby/object:Gem::Requirement
240
+ none: false
240
241
  requirements:
241
242
  - - ! '>='
242
243
  - !ruby/object:Gem::Version
244
+ version: '0'
243
245
  segments:
244
246
  - 0
245
- hash: 4495588430750097686
246
- version: '0'
247
- none: false
247
+ hash: -2134793660123218894
248
248
  required_rubygems_version: !ruby/object:Gem::Requirement
249
+ none: false
249
250
  requirements:
250
251
  - - ! '>='
251
252
  - !ruby/object:Gem::Version
252
253
  version: 1.3.6
253
- none: false
254
254
  requirements: []
255
255
  rubyforge_project: active_shipping
256
256
  rubygems_version: 1.8.23