active_shipping 0.10.1 → 0.11.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.
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