active_shipping 0.12.6 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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