active_shipping 0.12.6 → 1.0.0.pre1

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +0 -0
  4. data/{CHANGELOG → CHANGELOG.md} +6 -2
  5. data/CONTRIBUTING.md +32 -0
  6. data/{README.markdown → README.md} +45 -61
  7. data/lib/active_shipping.rb +20 -28
  8. data/lib/active_shipping/carrier.rb +82 -0
  9. data/lib/active_shipping/carriers.rb +33 -0
  10. data/lib/active_shipping/carriers/benchmark_carrier.rb +31 -0
  11. data/lib/active_shipping/carriers/bogus_carrier.rb +12 -0
  12. data/lib/active_shipping/carriers/canada_post.rb +253 -0
  13. data/lib/active_shipping/carriers/canada_post_pws.rb +870 -0
  14. data/lib/active_shipping/carriers/fedex.rb +579 -0
  15. data/lib/active_shipping/carriers/kunaki.rb +164 -0
  16. data/lib/active_shipping/carriers/new_zealand_post.rb +262 -0
  17. data/lib/active_shipping/carriers/shipwire.rb +181 -0
  18. data/lib/active_shipping/carriers/stamps.rb +861 -0
  19. data/lib/active_shipping/carriers/ups.rb +648 -0
  20. data/lib/active_shipping/carriers/usps.rb +642 -0
  21. data/lib/active_shipping/errors.rb +7 -0
  22. data/lib/active_shipping/label_response.rb +23 -0
  23. data/lib/active_shipping/location.rb +149 -0
  24. data/lib/active_shipping/package.rb +241 -0
  25. data/lib/active_shipping/rate_estimate.rb +64 -0
  26. data/lib/active_shipping/rate_response.rb +13 -0
  27. data/lib/active_shipping/response.rb +41 -0
  28. data/lib/active_shipping/shipment_event.rb +17 -0
  29. data/lib/active_shipping/shipment_packer.rb +73 -0
  30. data/lib/active_shipping/shipping_response.rb +12 -0
  31. data/lib/active_shipping/tracking_response.rb +52 -0
  32. data/lib/active_shipping/version.rb +1 -1
  33. data/lib/vendor/quantified/test/length_test.rb +2 -2
  34. data/lib/vendor/xml_node/test/test_parsing.rb +1 -1
  35. metadata +58 -36
  36. metadata.gz.sig +0 -0
  37. data/lib/active_shipping/shipping/base.rb +0 -13
  38. data/lib/active_shipping/shipping/carrier.rb +0 -84
  39. data/lib/active_shipping/shipping/carriers.rb +0 -23
  40. data/lib/active_shipping/shipping/carriers/benchmark_carrier.rb +0 -33
  41. data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +0 -14
  42. data/lib/active_shipping/shipping/carriers/canada_post.rb +0 -257
  43. data/lib/active_shipping/shipping/carriers/canada_post_pws.rb +0 -874
  44. data/lib/active_shipping/shipping/carriers/fedex.rb +0 -581
  45. data/lib/active_shipping/shipping/carriers/kunaki.rb +0 -166
  46. data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +0 -262
  47. data/lib/active_shipping/shipping/carriers/shipwire.rb +0 -184
  48. data/lib/active_shipping/shipping/carriers/stamps.rb +0 -864
  49. data/lib/active_shipping/shipping/carriers/ups.rb +0 -650
  50. data/lib/active_shipping/shipping/carriers/usps.rb +0 -649
  51. data/lib/active_shipping/shipping/errors.rb +0 -9
  52. data/lib/active_shipping/shipping/label_response.rb +0 -25
  53. data/lib/active_shipping/shipping/location.rb +0 -152
  54. data/lib/active_shipping/shipping/package.rb +0 -243
  55. data/lib/active_shipping/shipping/rate_estimate.rb +0 -66
  56. data/lib/active_shipping/shipping/rate_response.rb +0 -15
  57. data/lib/active_shipping/shipping/response.rb +0 -43
  58. data/lib/active_shipping/shipping/shipment_event.rb +0 -19
  59. data/lib/active_shipping/shipping/shipment_packer.rb +0 -75
  60. data/lib/active_shipping/shipping/shipping_response.rb +0 -14
  61. data/lib/active_shipping/shipping/tracking_response.rb +0 -54
@@ -1,9 +0,0 @@
1
- module ActiveMerchant
2
- module Shipping
3
- class ResponseContentError < StandardError
4
- def initialize(exception, content_body)
5
- super("#{exception.message} \n\n#{content_body}")
6
- end
7
- end
8
- end
9
- end
@@ -1,25 +0,0 @@
1
- module ActiveMerchant #:nodoc:
2
- module Shipping
3
- # This is UPS specific for now; the hash is not at all generic
4
- # or common between carriers.
5
-
6
- class LabelResponse < Response
7
- attr :params # maybe?
8
-
9
- def initialize(success, message, params = {}, options = {})
10
- @params = params
11
- super
12
- end
13
-
14
- def labels
15
- return @labels if @labels
16
- packages = params["ShipmentResults"]["PackageResults"]
17
- packages = [packages] if Hash === packages
18
- @labels = packages.map do |package|
19
- { :tracking_number => package["TrackingNumber"],
20
- :image => package["LabelImage"] }
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,152 +0,0 @@
1
- module ActiveMerchant #:nodoc:
2
- module Shipping #:nodoc:
3
- class Location
4
- ADDRESS_TYPES = %w(residential commercial po_box)
5
-
6
- attr_reader :options,
7
- :country,
8
- :postal_code,
9
- :province,
10
- :city,
11
- :name,
12
- :address1,
13
- :address2,
14
- :address3,
15
- :phone,
16
- :fax,
17
- :address_type,
18
- :company_name
19
-
20
- alias_method :zip, :postal_code
21
- alias_method :postal, :postal_code
22
- alias_method :state, :province
23
- alias_method :territory, :province
24
- alias_method :region, :province
25
- alias_method :company, :company_name
26
-
27
- def initialize(options = {})
28
- @country = (options[:country].nil? or options[:country].is_a?(ActiveMerchant::Country)) ?
29
- options[:country] :
30
- ActiveMerchant::Country.find(options[:country])
31
- @postal_code = options[:postal_code] || options[:postal] || options[:zip]
32
- @province = options[:province] || options[:state] || options[:territory] || options[:region]
33
- @city = options[:city]
34
- @name = options[:name]
35
- @address1 = options[:address1]
36
- @address2 = options[:address2]
37
- @address3 = options[:address3]
38
- @phone = options[:phone]
39
- @fax = options[:fax]
40
- @company_name = options[:company_name] || options[:company]
41
-
42
- self.address_type = options[:address_type]
43
- end
44
-
45
- def self.from(object, options = {})
46
- return object if object.is_a? ActiveMerchant::Shipping::Location
47
- attr_mappings = {
48
- :name => [:name],
49
- :country => [:country_code, :country],
50
- :postal_code => [:postal_code, :zip, :postal],
51
- :province => [:province_code, :state_code, :territory_code, :region_code, :province, :state, :territory, :region],
52
- :city => [:city, :town],
53
- :address1 => [:address1, :address, :street],
54
- :address2 => [:address2],
55
- :address3 => [:address3],
56
- :phone => [:phone, :phone_number],
57
- :fax => [:fax, :fax_number],
58
- :address_type => [:address_type],
59
- :company_name => [:company, :company_name]
60
- }
61
- attributes = {}
62
- hash_access = begin
63
- object[:some_symbol]
64
- true
65
- rescue
66
- false
67
- end
68
- attr_mappings.each do |pair|
69
- pair[1].each do |sym|
70
- if value = (object[sym] if hash_access) || (object.send(sym) if object.respond_to?(sym) && (!hash_access || !Hash.public_instance_methods.include?(sym.to_s)))
71
- attributes[pair[0]] = value
72
- break
73
- end
74
- end
75
- end
76
- attributes.delete(:address_type) unless ADDRESS_TYPES.include?(attributes[:address_type].to_s)
77
- new(attributes.update(options))
78
- end
79
-
80
- def country_code(format = :alpha2)
81
- @country.nil? ? nil : @country.code(format).value
82
- end
83
-
84
- def residential?; @address_type == 'residential' end
85
- def commercial?; @address_type == 'commercial' end
86
- def po_box?; @address_type == 'po_box' end
87
- def unknown?; country_code == 'ZZ' end
88
-
89
- def address_type=(value)
90
- return unless value.present?
91
- raise ArgumentError.new("address_type must be one of #{ADDRESS_TYPES.join(', ')}") unless ADDRESS_TYPES.include?(value.to_s)
92
- @address_type = value.to_s
93
- end
94
-
95
- def to_hash
96
- {
97
- :country => country_code,
98
- :postal_code => postal_code,
99
- :province => province,
100
- :city => city,
101
- :name => name,
102
- :address1 => address1,
103
- :address2 => address2,
104
- :address3 => address3,
105
- :phone => phone,
106
- :fax => fax,
107
- :address_type => address_type,
108
- :company_name => company_name
109
- }
110
- end
111
-
112
- def to_xml(options = {})
113
- options[:root] ||= "location"
114
- to_hash.to_xml(options)
115
- end
116
-
117
- def to_s
118
- prettyprint.gsub(/\n/, ' ')
119
- end
120
-
121
- def prettyprint
122
- chunks = []
123
- chunks << [@name, @address1, @address2, @address3].reject(&:blank?).join("\n")
124
- chunks << [@city, @province, @postal_code].reject(&:blank?).join(', ')
125
- chunks << @country
126
- chunks.reject(&:blank?).join("\n")
127
- end
128
-
129
- def inspect
130
- string = prettyprint
131
- string << "\nPhone: #{@phone}" unless @phone.blank?
132
- string << "\nFax: #{@fax}" unless @fax.blank?
133
- string
134
- end
135
-
136
- # Returns the postal code as a properly formatted Zip+4 code, e.g. "77095-2233"
137
- def zip_plus_4
138
- if /(\d{5})(\d{4})/ =~ @postal_code
139
- return "#{$1}-#{$2}"
140
- elsif /\d{5}-\d{4}/ =~ @postal_code
141
- return @postal_code
142
- else
143
- nil
144
- end
145
- end
146
-
147
- def address2_and_3
148
- [address2, address3].reject(&:blank?).join(", ")
149
- end
150
- end
151
- end
152
- end
@@ -1,243 +0,0 @@
1
- module ActiveMerchant #:nodoc:
2
- module Shipping #:nodoc:
3
- # A package item is a unique item(s) that is physically in a package.
4
- # A single package can have many items. This is only required
5
- # for shipping methods (label creation) right now.
6
- class PackageItem
7
- include Quantified
8
-
9
- attr_reader :sku, :hs_code, :value, :name, :weight, :quantity, :options
10
-
11
- def initialize(name, grams_or_ounces, value, quantity, options = {})
12
- @name = name
13
-
14
- imperial = (options[:units] == :imperial) ||
15
- (grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
16
-
17
- @unit_system = imperial ? :imperial : :metric
18
-
19
- @weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, :grams, :ounces)
20
-
21
- @value = Package.cents_from(value)
22
- @quantity = quantity > 0 ? quantity : 1
23
-
24
- @sku = options[:sku]
25
- @hs_code = options[:hs_code]
26
- @options = options
27
- end
28
-
29
- def weight(options = {})
30
- case options[:type]
31
- when nil, :actual
32
- @weight
33
- when :volumetric, :dimensional
34
- @volumetric_weight ||= begin
35
- m = Mass.new((centimetres(:box_volume) / 6.0), :grams)
36
- @unit_system == :imperial ? m.in_ounces : m
37
- end
38
- when :billable
39
- [weight, weight(:type => :volumetric)].max
40
- end
41
- end
42
- alias_method :mass, :weight
43
-
44
- def ounces(options = {})
45
- weight(options).in_ounces.amount
46
- end
47
- alias_method :oz, :ounces
48
-
49
- def grams(options = {})
50
- weight(options).in_grams.amount
51
- end
52
- alias_method :g, :grams
53
-
54
- def pounds(options = {})
55
- weight(options).in_pounds.amount
56
- end
57
- alias_method :lb, :pounds
58
- alias_method :lbs, :pounds
59
-
60
- def kilograms(options = {})
61
- weight(options).in_kilograms.amount
62
- end
63
- alias_method :kg, :kilograms
64
- alias_method :kgs, :kilograms
65
-
66
- private
67
-
68
- def attribute_from_metric_or_imperial(obj, klass, metric_unit, imperial_unit)
69
- if obj.is_a?(klass)
70
- return value
71
- else
72
- return klass.new(obj, (@unit_system == :imperial ? imperial_unit : metric_unit))
73
- end
74
- end
75
- end
76
-
77
- class Package
78
- include Quantified
79
-
80
- cattr_accessor :default_options
81
- attr_reader :options, :value, :currency
82
-
83
- # Package.new(100, [10, 20, 30], :units => :metric)
84
- # Package.new(Mass.new(100, :grams), [10, 20, 30].map {|m| Length.new(m, :centimetres)})
85
- # Package.new(100.grams, [10, 20, 30].map(&:centimetres))
86
- def initialize(grams_or_ounces, dimensions, options = {})
87
- options = @@default_options.update(options) if @@default_options
88
- options.symbolize_keys!
89
- @options = options
90
-
91
- @dimensions = [dimensions].flatten.reject(&:nil?)
92
-
93
- imperial = (options[:units] == :imperial) ||
94
- ([grams_or_ounces, *dimensions].all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
95
-
96
- weight_imperial = dimensions_imperial = imperial if options.include?(:units)
97
-
98
- if options.include?(:weight_units)
99
- weight_imperial = (options[:weight_units] == :imperial) ||
100
- (grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
101
- end
102
-
103
- if options.include?(:dim_units)
104
- dimensions_imperial = (options[:dim_units] == :imperial) ||
105
- (dimensions && dimensions.all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
106
- end
107
-
108
- @weight_unit_system = weight_imperial ? :imperial : :metric
109
- @dimensions_unit_system = dimensions_imperial ? :imperial : :metric
110
-
111
- @weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, @weight_unit_system, :grams, :ounces)
112
-
113
- if @dimensions.blank?
114
- @dimensions = [Length.new(0, (dimensions_imperial ? :inches : :centimetres))] * 3
115
- else
116
- process_dimensions
117
- end
118
-
119
- @value = Package.cents_from(options[:value])
120
- @currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
121
- @cylinder = (options[:cylinder] || options[:tube]) ? true : false
122
- @gift = options[:gift] ? true : false
123
- @oversized = options[:oversized] ? true : false
124
- @unpackaged = options[:unpackaged] ? true : false
125
- end
126
-
127
- def unpackaged?
128
- @unpackaged
129
- end
130
-
131
- def oversized?
132
- @oversized
133
- end
134
-
135
- def cylinder?
136
- @cylinder
137
- end
138
- alias_method :tube?, :cylinder?
139
-
140
- def gift?; @gift end
141
-
142
- def ounces(options = {})
143
- weight(options).in_ounces.amount
144
- end
145
- alias_method :oz, :ounces
146
-
147
- def grams(options = {})
148
- weight(options).in_grams.amount
149
- end
150
- alias_method :g, :grams
151
-
152
- def pounds(options = {})
153
- weight(options).in_pounds.amount
154
- end
155
- alias_method :lb, :pounds
156
- alias_method :lbs, :pounds
157
-
158
- def kilograms(options = {})
159
- weight(options).in_kilograms.amount
160
- end
161
- alias_method :kg, :kilograms
162
- alias_method :kgs, :kilograms
163
-
164
- def inches(measurement = nil)
165
- @inches ||= @dimensions.map { |m| m.in_inches.amount }
166
- measurement.nil? ? @inches : measure(measurement, @inches)
167
- end
168
- alias_method :in, :inches
169
-
170
- def centimetres(measurement = nil)
171
- @centimetres ||= @dimensions.map { |m| m.in_centimetres.amount }
172
- measurement.nil? ? @centimetres : measure(measurement, @centimetres)
173
- end
174
- alias_method :cm, :centimetres
175
-
176
- def weight(options = {})
177
- case options[:type]
178
- when nil, :actual
179
- @weight
180
- when :volumetric, :dimensional
181
- @volumetric_weight ||= begin
182
- m = Mass.new((centimetres(:box_volume) / 6.0), :grams)
183
- @weight_unit_system == :imperial ? m.in_ounces : m
184
- end
185
- when :billable
186
- [weight, weight(:type => :volumetric)].max
187
- end
188
- end
189
- alias_method :mass, :weight
190
-
191
- def self.cents_from(money)
192
- return nil if money.nil?
193
- if money.respond_to?(:cents)
194
- return money.cents
195
- else
196
- case money
197
- when Float
198
- (money * 100).round
199
- when String
200
- money =~ /\./ ? (money.to_f * 100).round : money.to_i
201
- else
202
- money.to_i
203
- end
204
- end
205
- end
206
-
207
- private
208
-
209
- def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit)
210
- if obj.is_a?(klass)
211
- return obj
212
- else
213
- return klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit))
214
- end
215
- end
216
-
217
- def measure(measurement, ary)
218
- case measurement
219
- when Fixnum then ary[measurement]
220
- when :x, :max, :length, :long then ary[2]
221
- when :y, :mid, :width, :wide then ary[1]
222
- when :z, :min, :height, :depth, :high, :deep then ary[0]
223
- when :girth, :around, :circumference
224
- self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
225
- when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary)
226
- when :box_volume then ary[0] * ary[1] * ary[2]
227
- end
228
- end
229
-
230
- def process_dimensions
231
- @dimensions = @dimensions.map do |l|
232
- attribute_from_metric_or_imperial(l, Length, @dimensions_unit_system, :centimetres, :inches)
233
- end.sort
234
- # [1,2] => [1,1,2]
235
- # [5] => [5,5,5]
236
- # etc..
237
- 2.downto(@dimensions.length) do |_n|
238
- @dimensions.unshift(@dimensions[0])
239
- end
240
- end
241
- end
242
- end
243
- end
@@ -1,66 +0,0 @@
1
- module ActiveMerchant #:nodoc:
2
- module Shipping #:nodoc:
3
- class RateEstimate
4
- attr_reader :origin # Location objects
5
- attr_reader :destination
6
- attr_reader :package_rates # array of hashes in the form of {:package => <Package>, :rate => 500}
7
- attr_reader :carrier # Carrier.name ('USPS', 'FedEx', etc.)
8
- attr_reader :service_name # name of service ("First Class Ground", etc.)
9
- attr_reader :service_code
10
- attr_reader :currency # 'USD', 'CAD', etc.
11
- # http://en.wikipedia.org/wiki/ISO_4217
12
- attr_reader :shipping_date
13
- attr_reader :delivery_date # Usually only available for express shipments
14
- attr_reader :delivery_range # Min and max delivery estimate in days
15
- attr_reader :negotiated_rate
16
- attr_reader :insurance_price
17
-
18
- def initialize(origin, destination, carrier, service_name, options = {})
19
- @origin, @destination, @carrier, @service_name = origin, destination, carrier, service_name
20
- @service_code = options[:service_code]
21
- if options[:package_rates]
22
- @package_rates = options[:package_rates].map { |p| p.update(:rate => Package.cents_from(p[:rate])) }
23
- else
24
- @package_rates = Array(options[:packages]).map { |p| {:package => p} }
25
- end
26
- @total_price = Package.cents_from(options[:total_price])
27
- @negotiated_rate = options[:negotiated_rate] ? Package.cents_from(options[:negotiated_rate]) : nil
28
- @currency = ActiveMerchant::CurrencyCode.standardize(options[:currency])
29
- @delivery_range = options[:delivery_range] ? options[:delivery_range].map { |date| date_for(date) }.compact : []
30
- @shipping_date = date_for(options[:shipping_date])
31
- @delivery_date = @delivery_range.last
32
- @insurance_price = Package.cents_from(options[:insurance_price])
33
- end
34
-
35
- def total_price
36
- @total_price || @package_rates.sum { |p| p[:rate] }
37
- rescue NoMethodError
38
- raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
39
- end
40
- alias_method :price, :total_price
41
-
42
- def add(package, rate = nil)
43
- cents = Package.cents_from(rate)
44
- raise ArgumentError.new("New packages must have valid rate information since this RateEstimate has no total_price set.") if cents.nil? and total_price.nil?
45
- @package_rates << {:package => package, :rate => cents}
46
- self
47
- end
48
-
49
- def packages
50
- package_rates.map { |p| p[:package] }
51
- end
52
-
53
- def package_count
54
- package_rates.length
55
- end
56
-
57
- private
58
-
59
- def date_for(date)
60
- date && DateTime.strptime(date.to_s, "%Y-%m-%d")
61
- rescue ArgumentError
62
- nil
63
- end
64
- end
65
- end
66
- end