kschadeck-active_shipping 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +38 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.markdown +126 -0
  4. data/lib/active_shipping.rb +50 -0
  5. data/lib/active_shipping/shipping/base.rb +13 -0
  6. data/lib/active_shipping/shipping/carrier.rb +81 -0
  7. data/lib/active_shipping/shipping/carriers.rb +20 -0
  8. data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +16 -0
  9. data/lib/active_shipping/shipping/carriers/canada_post.rb +261 -0
  10. data/lib/active_shipping/shipping/carriers/fedex.rb +372 -0
  11. data/lib/active_shipping/shipping/carriers/kunaki.rb +165 -0
  12. data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +269 -0
  13. data/lib/active_shipping/shipping/carriers/shipwire.rb +178 -0
  14. data/lib/active_shipping/shipping/carriers/ups.rb +452 -0
  15. data/lib/active_shipping/shipping/carriers/usps.rb +441 -0
  16. data/lib/active_shipping/shipping/location.rb +149 -0
  17. data/lib/active_shipping/shipping/package.rb +147 -0
  18. data/lib/active_shipping/shipping/rate_estimate.rb +62 -0
  19. data/lib/active_shipping/shipping/rate_response.rb +19 -0
  20. data/lib/active_shipping/shipping/response.rb +46 -0
  21. data/lib/active_shipping/shipping/shipment_event.rb +14 -0
  22. data/lib/active_shipping/shipping/shipment_packer.rb +48 -0
  23. data/lib/active_shipping/shipping/tracking_response.rb +47 -0
  24. data/lib/active_shipping/version.rb +3 -0
  25. data/lib/certs/eParcel.dtd +111 -0
  26. data/lib/vendor/quantified/MIT-LICENSE +22 -0
  27. data/lib/vendor/quantified/README.markdown +49 -0
  28. data/lib/vendor/quantified/Rakefile +21 -0
  29. data/lib/vendor/quantified/init.rb +0 -0
  30. data/lib/vendor/quantified/lib/quantified.rb +6 -0
  31. data/lib/vendor/quantified/lib/quantified/attribute.rb +208 -0
  32. data/lib/vendor/quantified/lib/quantified/length.rb +20 -0
  33. data/lib/vendor/quantified/lib/quantified/mass.rb +19 -0
  34. data/lib/vendor/quantified/test/length_test.rb +92 -0
  35. data/lib/vendor/quantified/test/mass_test.rb +88 -0
  36. data/lib/vendor/quantified/test/test_helper.rb +10 -0
  37. data/lib/vendor/test_helper.rb +7 -0
  38. data/lib/vendor/xml_node/README +36 -0
  39. data/lib/vendor/xml_node/Rakefile +21 -0
  40. data/lib/vendor/xml_node/benchmark/bench_generation.rb +32 -0
  41. data/lib/vendor/xml_node/init.rb +1 -0
  42. data/lib/vendor/xml_node/lib/xml_node.rb +222 -0
  43. data/lib/vendor/xml_node/test/test_generating.rb +94 -0
  44. data/lib/vendor/xml_node/test/test_parsing.rb +43 -0
  45. metadata +233 -0
@@ -0,0 +1,147 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Shipping #:nodoc:
3
+ class Package
4
+ include Quantified
5
+
6
+ cattr_accessor :default_options
7
+ attr_reader :options, :value, :currency
8
+
9
+ # Package.new(100, [10, 20, 30], :units => :metric)
10
+ # Package.new(Mass.new(100, :grams), [10, 20, 30].map {|m| Length.new(m, :centimetres)})
11
+ # Package.new(100.grams, [10, 20, 30].map(&:centimetres))
12
+ def initialize(grams_or_ounces, dimensions, options = {})
13
+ options = @@default_options.update(options) if @@default_options
14
+ options.symbolize_keys!
15
+ @options = options
16
+
17
+ @dimensions = [dimensions].flatten.reject {|d| d.nil?}
18
+
19
+ imperial = (options[:units] == :imperial) ||
20
+ ([grams_or_ounces, *dimensions].all? {|m| m.respond_to?(:unit) && m.unit.to_sym == :imperial})
21
+
22
+ @unit_system = imperial ? :imperial : :metric
23
+
24
+ @weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, :grams, :ounces)
25
+
26
+ if @dimensions.blank?
27
+ @dimensions = [Length.new(0, (imperial ? :inches : :centimetres))] * 3
28
+ else
29
+ process_dimensions
30
+ end
31
+
32
+ @value = Package.cents_from(options[:value])
33
+ @currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
34
+ @cylinder = (options[:cylinder] || options[:tube]) ? true : false
35
+ @gift = options[:gift] ? true : false
36
+ end
37
+
38
+ def cylinder?
39
+ @cylinder
40
+ end
41
+ alias_method :tube?, :cylinder?
42
+
43
+ def gift?; @gift end
44
+
45
+ def ounces(options={})
46
+ weight(options).in_ounces.amount
47
+ end
48
+ alias_method :oz, :ounces
49
+
50
+ def grams(options={})
51
+ weight(options).in_grams.amount
52
+ end
53
+ alias_method :g, :grams
54
+
55
+ def pounds(options={})
56
+ weight(options).in_pounds.amount
57
+ end
58
+ alias_method :lb, :pounds
59
+ alias_method :lbs, :pounds
60
+
61
+ def kilograms(options={})
62
+ weight(options).in_kilograms.amount
63
+ end
64
+ alias_method :kg, :kilograms
65
+ alias_method :kgs, :kilograms
66
+
67
+ def inches(measurement=nil)
68
+ @inches ||= @dimensions.map {|m| m.in_inches.amount }
69
+ measurement.nil? ? @inches : measure(measurement, @inches)
70
+ end
71
+ alias_method :in, :inches
72
+
73
+ def centimetres(measurement=nil)
74
+ @centimetres ||= @dimensions.map {|m| m.in_centimetres.amount }
75
+ measurement.nil? ? @centimetres : measure(measurement, @centimetres)
76
+ end
77
+ alias_method :cm, :centimetres
78
+
79
+ def weight(options = {})
80
+ case options[:type]
81
+ when nil, :actual
82
+ @weight
83
+ when :volumetric, :dimensional
84
+ @volumetric_weight ||= begin
85
+ m = Mass.new((centimetres(:box_volume) / 6.0), :grams)
86
+ @unit_system == :imperial ? m.in_ounces : m
87
+ end
88
+ when :billable
89
+ [ weight, weight(:type => :volumetric) ].max
90
+ end
91
+ end
92
+ alias_method :mass, :weight
93
+
94
+ def self.cents_from(money)
95
+ return nil if money.nil?
96
+ if money.respond_to?(:cents)
97
+ return money.cents
98
+ else
99
+ case money
100
+ when Float
101
+ (money * 100).round
102
+ when String
103
+ money =~ /\./ ? (money.to_f * 100).round : money.to_i
104
+ else
105
+ money.to_i
106
+ end
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def attribute_from_metric_or_imperial(obj, klass, metric_unit, imperial_unit)
113
+ if obj.is_a?(klass)
114
+ return value
115
+ else
116
+ return klass.new(obj, (@unit_system == :imperial ? imperial_unit : metric_unit))
117
+ end
118
+ end
119
+
120
+ def measure(measurement, ary)
121
+ case measurement
122
+ when Fixnum then ary[measurement]
123
+ when :x, :max, :length, :long then ary[2]
124
+ when :y, :mid, :width, :wide then ary[1]
125
+ when :z, :min, :height,:depth,:high,:deep then ary[0]
126
+ when :girth, :around,:circumference
127
+ self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
128
+ when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume,ary)
129
+ when :box_volume then ary[0] * ary[1] * ary[2]
130
+ end
131
+ end
132
+
133
+ def process_dimensions
134
+ @dimensions = @dimensions.map do |l|
135
+ attribute_from_metric_or_imperial(l, Length, :centimetres, :inches)
136
+ end.sort
137
+ # [1,2] => [1,1,2]
138
+ # [5] => [5,5,5]
139
+ # etc..
140
+ 2.downto(@dimensions.length) do |n|
141
+ @dimensions.unshift(@dimensions[0])
142
+ end
143
+ end
144
+
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,62 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Shipping #:nodoc:
3
+
4
+ class RateEstimate
5
+ attr_reader :origin # Location objects
6
+ attr_reader :destination
7
+ attr_reader :package_rates # array of hashes in the form of {:package => <Package>, :rate => 500}
8
+ attr_reader :carrier # Carrier.name ('USPS', 'FedEx', etc.)
9
+ attr_reader :service_name # name of service ("First Class Ground", etc.)
10
+ attr_reader :service_code
11
+ attr_reader :currency # 'USD', 'CAD', etc.
12
+ # http://en.wikipedia.org/wiki/ISO_4217
13
+ attr_reader :delivery_date # Usually only available for express shipments
14
+ attr_reader :delivery_range # Min and max delivery estimate in days
15
+
16
+ def initialize(origin, destination, carrier, service_name, options={})
17
+ @origin, @destination, @carrier, @service_name = origin, destination, carrier, service_name
18
+ @service_code = options[:service_code]
19
+ if options[:package_rates]
20
+ @package_rates = options[:package_rates].map {|p| p.update({:rate => Package.cents_from(p[:rate])}) }
21
+ else
22
+ @package_rates = Array(options[:packages]).map {|p| {:package => p}}
23
+ end
24
+ @total_price = Package.cents_from(options[:total_price])
25
+ @currency = options[:currency]
26
+ @delivery_range = options[:delivery_range] ? options[:delivery_range].map { |date| date_for(date) }.compact : []
27
+ @delivery_date = @delivery_range.last
28
+ end
29
+
30
+ def total_price
31
+ begin
32
+ @total_price || @package_rates.sum {|p| p[:rate]}
33
+ rescue NoMethodError
34
+ raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
35
+ end
36
+ end
37
+ alias_method :price, :total_price
38
+
39
+ def add(package,rate=nil)
40
+ cents = Package.cents_from(rate)
41
+ 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?
42
+ @package_rates << {:package => package, :rate => cents}
43
+ self
44
+ end
45
+
46
+ def packages
47
+ package_rates.map {|p| p[:package]}
48
+ end
49
+
50
+ def package_count
51
+ package_rates.length
52
+ end
53
+
54
+ private
55
+ def date_for(date)
56
+ date && DateTime.strptime(date.to_s, "%Y-%m-%d")
57
+ rescue ArgumentError
58
+ nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Shipping
3
+
4
+ class RateResponse < Response
5
+
6
+ attr_reader :rates
7
+
8
+ def initialize(success, message, params = {}, options = {})
9
+ @rates = Array(options[:estimates] || options[:rates] || options[:rate_estimates])
10
+ super
11
+ end
12
+
13
+ alias_method :estimates, :rates
14
+ alias_method :rate_estimates, :rates
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveMerchant #:nodoc:
2
+
3
+ module Shipping #:nodoc:
4
+
5
+ class Error < ActiveMerchant::ActiveMerchantError
6
+ end
7
+
8
+ class ResponseError < Error
9
+ attr_reader :response
10
+
11
+ def initialize(response = nil)
12
+ if response.is_a? Response
13
+ super(response.message)
14
+ @response = response
15
+ else
16
+ super(response)
17
+ end
18
+ end
19
+ end
20
+
21
+ class Response
22
+
23
+ attr_reader :params
24
+ attr_reader :message
25
+ attr_reader :test
26
+ attr_reader :xml
27
+ attr_reader :request
28
+
29
+ def initialize(success, message, params = {}, options = {})
30
+ @success, @message, @params = success, message, params.stringify_keys
31
+ @test = options[:test] || false
32
+ @xml = options[:xml]
33
+ @request = options[:request]
34
+ raise ResponseError.new(self) unless success
35
+ end
36
+
37
+ def success?
38
+ @success ? true : false
39
+ end
40
+
41
+ def test?
42
+ @test ? true : false
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Shipping
3
+
4
+ class ShipmentEvent
5
+ attr_reader :name, :time, :location, :message
6
+
7
+ def initialize(name, time, location, message=nil)
8
+ @name, @time, @location, @message = name, time, location, message
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ module ActiveMerchant
2
+ module Shipping
3
+ class ShipmentPacker
4
+ class OverweightItem < StandardError
5
+ end
6
+
7
+ # items - array of hashes containing quantity, grams and price.
8
+ # ex. [{:quantity => 2, :price => 1.0, :grams => 50}]
9
+ # dimensions - [5.0, 15.0, 30.0]
10
+ # maximum_weight - maximum weight in grams
11
+ # currency - ISO currency code
12
+ def self.pack(items, dimensions, maximum_weight, currency)
13
+ items = items.map(&:symbolize_keys).map { |item| [item] * item[:quantity].to_i }.flatten
14
+ packages = []
15
+ state = :package_empty
16
+
17
+ while state != :packing_finished
18
+ case state
19
+ when :package_empty
20
+ package_weight, package_value = 0, 0
21
+ state = :filling_package
22
+ when :filling_package
23
+ item = items.shift
24
+ item_weight, item_price = item[:grams].to_i, Package.cents_from(item[:price])
25
+
26
+ if item_weight > maximum_weight
27
+ raise OverweightItem, "The item with weight of #{item_weight}g is heavier than the allowable package weight of #{maximum_weight}g"
28
+ end
29
+
30
+ if (package_weight + item_weight) <= maximum_weight
31
+ package_weight += item_weight
32
+ package_value += item_price
33
+ state = :package_full if items.empty?
34
+ else
35
+ items.unshift(item)
36
+ state = :package_full
37
+ end
38
+ when :package_full
39
+ packages << ActiveMerchant::Shipping::Package.new(package_weight, dimensions, :value => package_value, :currency => currency)
40
+ state = items.any? ? :package_empty : :packing_finished
41
+ end
42
+ end
43
+
44
+ packages
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Shipping
3
+
4
+ class TrackingResponse < Response
5
+ attr_reader :carrier # symbol
6
+ attr_reader :carrier_name # string
7
+ attr_reader :status # symbol
8
+ attr_reader :status_code # string
9
+ attr_reader :status_description #string
10
+ attr_reader :scheduled_delivery_date # time
11
+ attr_reader :tracking_number # string
12
+ attr_reader :shipment_events # array of ShipmentEvents in chronological order
13
+ attr_reader :origin, :destination
14
+
15
+ def initialize(success, message, params = {}, options = {})
16
+ @carrier = options[:carrier].parameterize.to_sym
17
+ @carrier_name = options[:carrier]
18
+ @status = options[:status]
19
+ @status_code = options[:status_code]
20
+ @status_description = options[:status_description]
21
+ @scheduled_delivery_date = options[:scheduled_delivery_date]
22
+ @tracking_number = options[:tracking_number]
23
+ @shipment_events = Array(options[:shipment_events])
24
+ @origin, @destination = options[:origin], options[:destination]
25
+ super
26
+ end
27
+
28
+ def latest_event
29
+ @shipment_events.last
30
+ end
31
+
32
+ def is_delivered?
33
+ @status == :delivered
34
+ end
35
+
36
+ def has_exception?
37
+ @status == :exception
38
+ end
39
+
40
+ alias_method(:exception_event, :latest_event)
41
+ alias_method(:delivered?, :is_delivered?)
42
+ alias_method(:exception?, :has_exception?)
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveShipping
2
+ VERSION = "0.9.15"
3
+ end
@@ -0,0 +1,111 @@
1
+ <!-- EVERY REQUEST CONTAIN THE eparcel TAG -->
2
+ <!ELEMENT eparcel (language?,
3
+ ( ratesAndServicesRequest |
4
+ ratesAndServicesResponse |
5
+ error
6
+ )+)>
7
+
8
+ <!ELEMENT language (#PCDATA)>
9
+ <!ELEMENT comment (#PCDATA)>
10
+
11
+
12
+ <!-- Standard request to ask for rates and services -->
13
+ <!ELEMENT ratesAndServicesRequest ( merchantCPCID,
14
+ fromPostalCode?,
15
+ turnAroundTime?,
16
+ itemsPrice?,
17
+ lineItems,
18
+ city?,
19
+ provOrState,
20
+ country,
21
+ postalCode)>
22
+ <!ELEMENT merchantID (#PCDATA)>
23
+ <!ELEMENT fromPostalCode (#PCDATA)>
24
+ <!ELEMENT turnAroundTime (#PCDATA)>
25
+ <!ELEMENT itemsPrice (#PCDATA)>
26
+ <!ELEMENT merchantCPCID (#PCDATA)>
27
+ <!ELEMENT lineItems (item)+>
28
+ <!ELEMENT item (quantity, weight, length, width, height, description, imageURL?, readyToShip)>
29
+ <!ELEMENT quantity (#PCDATA)>
30
+ <!ELEMENT weight (#PCDATA)>
31
+ <!ELEMENT length (#PCDATA)>
32
+ <!ELEMENT width (#PCDATA)>
33
+ <!ELEMENT height (#PCDATA)>
34
+ <!ELEMENT description (#PCDATA)>
35
+ <!ELEMENT imageURL (#PCDATA)>
36
+ <!ELEMENT readyToShip (#PCDATA)>
37
+ <!ELEMENT city (#PCDATA)>
38
+ <!ELEMENT provOrState (#PCDATA)>
39
+ <!ELEMENT country (#PCDATA)>
40
+ <!ELEMENT postalCode (#PCDATA)>
41
+
42
+ <!-- Standard response for request for rates and services -->
43
+ <!ELEMENT ratesAndServicesResponse (statusCode,
44
+ statusMessage+,
45
+ requestID,
46
+ handling,
47
+ language,
48
+ product+,
49
+ packing*,
50
+ emptySpace*,
51
+ shippingOptions,
52
+ comment,
53
+ nearestPostalOutlet*)>
54
+
55
+ <!ELEMENT statusCode (#PCDATA)>
56
+ <!ELEMENT statusMessage (#PCDATA)>
57
+ <!ELEMENT requestID (#PCDATA)>
58
+ <!ELEMENT handling (#PCDATA)>
59
+
60
+ <!ELEMENT product (name, rate, shippingDate, deliveryDate, deliveryDayOfWeek, nextDayAM?, packingID)>
61
+ <!ATTLIST product id CDATA #REQUIRED>
62
+ <!ATTLIST product sequence CDATA #REQUIRED>
63
+ <!ELEMENT name (#PCDATA)>
64
+ <!ELEMENT rate (#PCDATA)>
65
+ <!ELEMENT shippingDate (#PCDATA)>
66
+ <!ELEMENT deliveryDate (#PCDATA)>
67
+ <!ELEMENT deliveryDayOfWeek (#PCDATA)>
68
+ <!ELEMENT nextDayAM (#PCDATA)>
69
+ <!ELEMENT packingID (#PCDATA)>
70
+
71
+ <!ELEMENT packing (packingID, box+)>
72
+ <!ELEMENT box (name, weight, expediterWeight, length, width, height, packedItem+)>
73
+ <!ELEMENT expediterWeight (#PCDATA)>
74
+ <!ELEMENT packedItem (quantity, description)>
75
+
76
+ <!ELEMENT emptySpace (length, width, height, weight)>
77
+
78
+ <!ELEMENT shippingOptions (insurance, deliveryConfirmation, signature)>
79
+ <!ELEMENT insurance (#PCDATA)>
80
+ <!ELEMENT deliveryConfirmation (#PCDATA)>
81
+ <!ELEMENT signature (#PCDATA)>
82
+
83
+
84
+ <!-- ********************************************************* -->
85
+ <!-- * 'nearestPostalOutlet' is optional and is returned * -->
86
+ <!-- * only if the merchant profile has this option enabled * -->
87
+ <!-- ********************************************************* -->
88
+ <!ELEMENT nearestPostalOutlet (postalOutletSequenceNo,
89
+ distance,
90
+ outletName,
91
+ businessName ,
92
+ postalAddress,
93
+ phoneNumber,
94
+ businessHours+)>
95
+ <!ELEMENT postalOutletSequenceNo (#PCDATA)>
96
+ <!ELEMENT distance (#PCDATA)>
97
+ <!ELEMENT outletName (#PCDATA)>
98
+ <!ELEMENT businessName (#PCDATA)>
99
+ <!ELEMENT postalAddress (addressLine+, postalCode , municipality)>
100
+ <!ELEMENT addressLine (#PCDATA)>
101
+ <!ELEMENT municipality (#PCDATA)>
102
+ <!ELEMENT phoneNumber (#PCDATA)>
103
+ <!ELEMENT businessHours (dayId, dayOfWeek, time)>
104
+ <!ELEMENT dayId (#PCDATA)>
105
+ <!ELEMENT dayOfWeek (#PCDATA)>
106
+ <!ELEMENT time (#PCDATA)>
107
+
108
+
109
+ <!-- Standard error format returned -->
110
+ <!ELEMENT error (statusCode,statusMessage*)>
111
+