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,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