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,166 +0,0 @@
1
- require 'builder'
2
-
3
- module ActiveMerchant
4
- module Shipping
5
- class Kunaki < Carrier
6
- self.retry_safe = true
7
-
8
- cattr_reader :name
9
- @@name = "Kunaki"
10
-
11
- URL = 'https://Kunaki.com/XMLService.ASP'
12
-
13
- CARRIERS = ["UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL"]
14
-
15
- COUNTRIES = {
16
- 'AR' => 'Argentina',
17
- 'AU' => 'Australia',
18
- 'AT' => 'Austria',
19
- 'BE' => 'Belgium',
20
- 'BR' => 'Brazil',
21
- 'BG' => 'Bulgaria',
22
- 'CA' => 'Canada',
23
- 'CN' => 'China',
24
- 'CY' => 'Cyprus',
25
- 'CZ' => 'Czech Republic',
26
- 'DK' => 'Denmark',
27
- 'EE' => 'Estonia',
28
- 'FI' => 'Finland',
29
- 'FR' => 'France',
30
- 'DE' => 'Germany',
31
- 'GI' => 'Gibraltar',
32
- 'GR' => 'Greece',
33
- 'GL' => 'Greenland',
34
- 'HK' => 'Hong Kong',
35
- 'HU' => 'Hungary',
36
- 'IS' => 'Iceland',
37
- 'IE' => 'Ireland',
38
- 'IL' => 'Israel',
39
- 'IT' => 'Italy',
40
- 'JP' => 'Japan',
41
- 'LV' => 'Latvia',
42
- 'LI' => 'Liechtenstein',
43
- 'LT' => 'Lithuania',
44
- 'LU' => 'Luxembourg',
45
- 'MX' => 'Mexico',
46
- 'NL' => 'Netherlands',
47
- 'NZ' => 'New Zealand',
48
- 'NO' => 'Norway',
49
- 'PL' => 'Poland',
50
- 'PT' => 'Portugal',
51
- 'RO' => 'Romania',
52
- 'RU' => 'Russia',
53
- 'SG' => 'Singapore',
54
- 'SK' => 'Slovakia',
55
- 'SI' => 'Slovenia',
56
- 'ES' => 'Spain',
57
- 'SE' => 'Sweden',
58
- 'CH' => 'Switzerland',
59
- 'TW' => 'Taiwan',
60
- 'TR' => 'Turkey',
61
- 'UA' => 'Ukraine',
62
- 'GB' => 'United Kingdom',
63
- 'US' => 'United States',
64
- 'VA' => 'Vatican City',
65
- 'RS' => 'Yugoslavia',
66
- 'ME' => 'Yugoslavia'
67
- }
68
-
69
- def find_rates(origin, destination, packages, options = {})
70
- requires!(options, :items)
71
- commit(origin, destination, options)
72
- end
73
-
74
- def valid_credentials?
75
- true
76
- end
77
-
78
- private
79
-
80
- def build_request(destination, options)
81
- xml = Builder::XmlMarkup.new
82
- xml.instruct!
83
- xml.tag! 'ShippingOptions' do
84
- xml.tag! 'AddressInfo' do
85
- xml.tag! 'Country', COUNTRIES[destination.country_code]
86
-
87
- state = %w(US CA).include?(destination.country_code.to_s) ? destination.state : ''
88
-
89
- xml.tag! 'State_Province', state
90
- xml.tag! 'PostalCode', destination.zip
91
- end
92
-
93
- options[:items].each do |item|
94
- xml.tag! 'Product' do
95
- xml.tag! 'ProductId', item[:sku]
96
- xml.tag! 'Quantity', item[:quantity]
97
- end
98
- end
99
- end
100
- xml.target!
101
- end
102
-
103
- def commit(origin, destination, options)
104
- request = build_request(destination, options)
105
-
106
- response = parse( ssl_post(URL, request, "Content-Type" => "text/xml") )
107
-
108
- RateResponse.new(success?(response), message_from(response), response,
109
- :rates => build_rate_estimates(response, origin, destination)
110
- )
111
- end
112
-
113
- def build_rate_estimates(response, origin, destination)
114
- response["Options"].collect do |quote|
115
- RateEstimate.new(origin, destination, carrier_for(quote["Description"]), quote["Description"],
116
- :total_price => quote["Price"],
117
- :currency => "USD"
118
- )
119
- end
120
- end
121
-
122
- def carrier_for(service)
123
- CARRIERS.dup.find { |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
124
- end
125
-
126
- def parse(xml)
127
- response = {}
128
- response["Options"] = []
129
-
130
- document = REXML::Document.new(sanitize(xml))
131
-
132
- response["ErrorCode"] = parse_child_text(document.root, "ErrorCode")
133
- response["ErrorText"] = parse_child_text(document.root, "ErrorText")
134
-
135
- document.root.elements.each("Option") do |e|
136
- rate = {}
137
- rate["Description"] = parse_child_text(e, "Description")
138
- rate["Price"] = parse_child_text(e, "Price")
139
- response["Options"] << rate
140
- end
141
- response
142
- end
143
-
144
- def sanitize(response)
145
- result = response.to_s
146
- result.gsub!("\r\n", "")
147
- result.gsub!(/<(\/)?(BODY|HTML)>/, '')
148
- result
149
- end
150
-
151
- def parse_child_text(parent, name)
152
- if element = parent.elements[name]
153
- element.text
154
- end
155
- end
156
-
157
- def success?(response)
158
- response["ErrorCode"] == "0"
159
- end
160
-
161
- def message_from(response)
162
- response["ErrorText"]
163
- end
164
- end
165
- end
166
- end
@@ -1,262 +0,0 @@
1
- module ActiveMerchant
2
- module Shipping
3
- class NewZealandPost < Carrier
4
- cattr_reader :name
5
- @@name = "New Zealand Post"
6
-
7
- URL = "http://api.nzpost.co.nz/ratefinder"
8
-
9
- def requirements
10
- [:key]
11
- end
12
-
13
- def find_rates(origin, destination, packages, options = {})
14
- options = @options.merge(options)
15
- request = RateRequest.from(origin, destination, packages, options)
16
- request.raw_responses = commit(request.urls) if request.new_zealand_origin?
17
- request.rate_response
18
- end
19
-
20
- protected
21
-
22
- def commit(urls)
23
- save_request(urls).map { |url| ssl_get(url) }
24
- end
25
-
26
- def self.default_location
27
- Location.new(
28
- :country => "NZ",
29
- :city => "Wellington",
30
- :address1 => "22 Waterloo Quay",
31
- :address2 => "Pipitea",
32
- :postal_code => "6011"
33
- )
34
- end
35
-
36
- class NewZealandPostRateResponse < RateResponse
37
- attr_reader :raw_responses
38
-
39
- def initialize(success, message, params = {}, options = {})
40
- @raw_responses = options[:raw_responses]
41
- super
42
- end
43
- end
44
-
45
- class RateRequest
46
- attr_reader :urls
47
- attr_writer :raw_responses
48
-
49
- def self.from(*args)
50
- return International.new(*args) unless domestic?(args[0..1])
51
- Domestic.new(*args)
52
- end
53
-
54
- def initialize(origin, destination, packages, options)
55
- @origin = Location.from(origin)
56
- @destination = Location.from(destination)
57
- @packages = Array(packages).map { |package| NewZealandPostPackage.new(package, api) }
58
- @params = { :format => "json", :api_key => options[:key] }
59
- @test = options[:test]
60
- @rates = @responses = @raw_responses = []
61
- @urls = @packages.map { |package| url(package) }
62
- end
63
-
64
- def rate_response
65
- @rates = rates
66
- NewZealandPostRateResponse.new(true, "success", response_params, response_options)
67
- rescue => error
68
- NewZealandPostRateResponse.new(false, error.message, response_params, response_options)
69
- end
70
-
71
- def new_zealand_origin?
72
- self.class.new_zealand?(@origin)
73
- end
74
-
75
- protected
76
-
77
- def self.new_zealand?(location)
78
- ['NZ', nil].include?(Location.from(location).country_code)
79
- end
80
-
81
- def self.domestic?(locations)
82
- locations.select { |location| new_zealand?(location) }.size == 2
83
- end
84
-
85
- def response_options
86
- {
87
- :rates => @rates,
88
- :raw_responses => @raw_responses,
89
- :request => @urls,
90
- :test => @test
91
- }
92
- end
93
-
94
- def response_params
95
- { :responses => @responses }
96
- end
97
-
98
- def rate_options(products)
99
- {
100
- :total_price => products.sum { |product| price(product) },
101
- :currency => "NZD",
102
- :service_code => products.first["code"]
103
- }
104
- end
105
-
106
- def rates
107
- rates_hash.map do |service, products|
108
- RateEstimate.new(@origin, @destination, NewZealandPost.name, service, rate_options(products))
109
- end
110
- end
111
-
112
- def rates_hash
113
- products_hash.select { |_service, products| products.size == @packages.size }
114
- end
115
-
116
- def products_hash
117
- product_arrays.flatten.group_by { |product| service_name(product) }
118
- end
119
-
120
- def product_arrays
121
- responses.map do |response|
122
- raise(response["message"]) unless response["status"] == "success"
123
- response["products"]
124
- end
125
- end
126
-
127
- def responses
128
- @responses = @raw_responses.map { |response| parse_response(response) }
129
- end
130
-
131
- def parse_response(response)
132
- JSON.parse(response)
133
- end
134
-
135
- def url(package)
136
- "#{URL}/#{api}?#{params(package).to_query}"
137
- end
138
-
139
- def params(package)
140
- @params.merge(api_params).merge(package.params)
141
- end
142
- end
143
-
144
- class Domestic < RateRequest
145
- def service_name(product)
146
- [product["service_group_description"], product["description"]].join(" ")
147
- end
148
-
149
- def api
150
- :domestic
151
- end
152
-
153
- def api_params
154
- {
155
- :postcode_src => @origin.postal_code,
156
- :postcode_dest => @destination.postal_code,
157
- :carrier => "all"
158
- }
159
- end
160
-
161
- def price(product)
162
- product["cost"].to_f
163
- end
164
- end
165
-
166
- class International < RateRequest
167
- def rates
168
- raise "New Zealand Post packages must originate in New Zealand" unless new_zealand_origin?
169
- super
170
- end
171
-
172
- def service_name(product)
173
- [product["group"], product["name"]].join(" ")
174
- end
175
-
176
- def api
177
- :international
178
- end
179
-
180
- def api_params
181
- { :country_code => @destination.country_code }
182
- end
183
-
184
- def price(product)
185
- product["price"].to_f
186
- end
187
- end
188
-
189
- class NewZealandPostPackage
190
- def initialize(package, api)
191
- @package = package
192
- @api = api
193
- @params = { :weight => weight, :length => length }
194
- end
195
-
196
- def params
197
- @params.merge(api_params).merge(shape_params)
198
- end
199
-
200
- protected
201
-
202
- def weight
203
- @package.kg
204
- end
205
-
206
- def length
207
- mm(:length)
208
- end
209
-
210
- def height
211
- mm(:height)
212
- end
213
-
214
- def width
215
- mm(:width)
216
- end
217
-
218
- def shape
219
- return :cylinder if @package.cylinder?
220
- :cuboid
221
- end
222
-
223
- def api_params
224
- send("#{@api}_params")
225
- end
226
-
227
- def international_params
228
- { :value => value }
229
- end
230
-
231
- def domestic_params
232
- {}
233
- end
234
-
235
- def shape_params
236
- send("#{shape}_params")
237
- end
238
-
239
- def cuboid_params
240
- { :height => height, :thickness => width }
241
- end
242
-
243
- def cylinder_params
244
- { :diameter => width }
245
- end
246
-
247
- def mm(measurement)
248
- @package.cm(measurement) * 10
249
- end
250
-
251
- def value
252
- return 0 unless @package.value && currency == "NZD"
253
- @package.value / 100
254
- end
255
-
256
- def currency
257
- @package.currency || "NZD"
258
- end
259
- end
260
- end
261
- end
262
- end
@@ -1,184 +0,0 @@
1
- require 'cgi'
2
- require 'builder'
3
-
4
- module ActiveMerchant
5
- module Shipping
6
- class Shipwire < Carrier
7
- self.retry_safe = true
8
-
9
- cattr_reader :name
10
- @@name = "Shipwire"
11
-
12
- URL = 'https://api.shipwire.com/exec/RateServices.php'
13
- SCHEMA_URL = 'http://www.shipwire.com/exec/download/RateRequest.dtd'
14
- WAREHOUSES = { 'CHI' => 'Chicago',
15
- 'LAX' => 'Los Angeles',
16
- 'REN' => 'Reno',
17
- 'VAN' => 'Vancouver',
18
- 'TOR' => 'Toronto',
19
- 'UK' => 'United Kingdom'
20
- }
21
-
22
- CARRIERS = ["UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL"]
23
-
24
- SUCCESS = "OK"
25
- SUCCESS_MESSAGE = "Successfully received the shipping rates"
26
- NO_RATES_MESSAGE = "No shipping rates could be found for the destination address"
27
- REQUIRED_OPTIONS = [:login, :password].freeze
28
-
29
- def find_rates(origin, destination, packages, options = {})
30
- requires!(options, :items)
31
- commit(origin, destination, options)
32
- end
33
-
34
- def valid_credentials?
35
- location = self.class.default_location
36
- find_rates(location, location, Package.new(100, [5, 15, 30]),
37
- :items => [{ :sku => '', :quantity => 1 }]
38
- )
39
- rescue ActiveMerchant::Shipping::ResponseError
40
- true
41
- rescue ActiveMerchant::ResponseError => e
42
- e.response.code != '401'
43
- end
44
-
45
- private
46
-
47
- def requirements
48
- REQUIRED_OPTIONS
49
- end
50
-
51
- def build_request(destination, options)
52
- xml = Builder::XmlMarkup.new
53
- xml.instruct!
54
- xml.declare! :DOCTYPE, :RateRequest, :SYSTEM, SCHEMA_URL
55
- xml.tag! 'RateRequest' do
56
- add_credentials(xml)
57
- add_order(xml, destination, options)
58
- end
59
- xml.target!
60
- end
61
-
62
- def add_credentials(xml)
63
- xml.tag! 'EmailAddress', @options[:login]
64
- xml.tag! 'Password', @options[:password]
65
- end
66
-
67
- def add_order(xml, destination, options)
68
- xml.tag! 'Order', :id => options[:order_id] do
69
- xml.tag! 'Warehouse', options[:warehouse] || '00'
70
-
71
- add_address(xml, destination)
72
- Array(options[:items]).each_with_index do |line_item, index|
73
- add_item(xml, line_item, index)
74
- end
75
- end
76
- end
77
-
78
- def add_address(xml, destination)
79
- xml.tag! 'AddressInfo', :type => 'Ship' do
80
- if destination.name.present?
81
- xml.tag! 'Name' do
82
- xml.tag! 'Full', destination.name
83
- end
84
- end
85
- xml.tag! 'Address1', destination.address1
86
- xml.tag! 'Address2', destination.address2 unless destination.address2.blank?
87
- xml.tag! 'Address3', destination.address3 unless destination.address3.blank?
88
- xml.tag! 'Company', destination.company unless destination.company.blank?
89
- xml.tag! 'City', destination.city
90
- xml.tag! 'State', destination.state unless destination.state.blank?
91
- xml.tag! 'Country', destination.country_code
92
- xml.tag! 'Zip', destination.zip unless destination.zip.blank?
93
- end
94
- end
95
-
96
- # Code is limited to 12 characters
97
- def add_item(xml, item, index)
98
- xml.tag! 'Item', :num => index do
99
- xml.tag! 'Code', item[:sku]
100
- xml.tag! 'Quantity', item[:quantity]
101
- end
102
- end
103
-
104
- def commit(origin, destination, options)
105
- request = build_request(destination, options)
106
- save_request(request)
107
-
108
- response = parse( ssl_post(URL, "RateRequestXML=#{CGI.escape(request)}") )
109
-
110
- RateResponse.new(response["success"], response["message"], response,
111
- :xml => response,
112
- :rates => build_rate_estimates(response, origin, destination),
113
- :request => last_request
114
- )
115
- end
116
-
117
- def build_rate_estimates(response, origin, destination)
118
- response["rates"].collect do |quote|
119
- RateEstimate.new(origin, destination, carrier_for(quote["service"]), quote["service"],
120
- :service_code => quote["method"],
121
- :total_price => quote["cost"],
122
- :currency => quote["currency"],
123
- :delivery_range => [timestamp_from_business_day(quote["delivery_min"]),
124
- timestamp_from_business_day(quote["delivery_max"])]
125
- )
126
- end
127
- end
128
-
129
- def carrier_for(service)
130
- CARRIERS.dup.find { |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
131
- end
132
-
133
- def parse(xml)
134
- response = {}
135
- response["rates"] = []
136
-
137
- document = REXML::Document.new(xml)
138
-
139
- response["status"] = parse_child_text(document.root, "Status")
140
-
141
- document.root.elements.each("Order/Quotes/Quote") do |e|
142
- rate = {}
143
- rate["method"] = e.attributes["method"]
144
- rate["warehouse"] = parse_child_text(e, "Warehouse")
145
- rate["service"] = parse_child_text(e, "Service")
146
- rate["cost"] = parse_child_text(e, "Cost")
147
- rate["currency"] = parse_child_attribute(e, "Cost", "currency")
148
- if delivery_estimate = e.elements["DeliveryEstimate"]
149
- rate["delivery_min"] = parse_child_text(delivery_estimate, "Minimum").to_i
150
- rate["delivery_max"] = parse_child_text(delivery_estimate, "Maximum").to_i
151
- end
152
- response["rates"] << rate
153
- end
154
-
155
- if response["status"] == SUCCESS && response["rates"].any?
156
- response["success"] = true
157
- response["message"] = SUCCESS_MESSAGE
158
- elsif response["status"] == SUCCESS && response["rates"].empty?
159
- response["success"] = false
160
- response["message"] = NO_RATES_MESSAGE
161
- else
162
- response["success"] = false
163
- response["message"] = parse_child_text(document.root, "ErrorMessage")
164
- end
165
-
166
- response
167
- rescue NoMethodError => e
168
- raise ActiveMerchant::Shipping::ResponseContentError.new(e, xml)
169
- end
170
-
171
- def parse_child_text(parent, name)
172
- if element = parent.elements[name]
173
- element.text
174
- end
175
- end
176
-
177
- def parse_child_attribute(parent, name, attribute)
178
- if element = parent.elements[name]
179
- element.attributes[attribute]
180
- end
181
- end
182
- end
183
- end
184
- end