active_fulfillment 3.0.0.pre8 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81e4d46fc9e29790dedd89eeab465416f9bc2b8e
4
- data.tar.gz: f8b1cb46b86a27625bb50fc3a68e4c301eed1315
3
+ metadata.gz: edee633e1484a24673765631ab0a8a85cb114f4d
4
+ data.tar.gz: 1b3c6f2099542ca7f988a0a7ac9ee9f3309cc809
5
5
  SHA512:
6
- metadata.gz: faff4e5e9c15dda5586ead4afcb10a973d78ddbf56f4bac42ae7403e7a90ff1f2558287ee23e0878c08dc0bea01259ad03d28a47ce12fcdc63a3c8a7b28285e5
7
- data.tar.gz: 06bbeab03d5155dd553af4ffe72fae585aeb06764e904218f5310e75c6f04933e90b0adeb71a95da7289b7efd9953ef40ed0f10ee01f5bbb72c12488268e1393
6
+ metadata.gz: 21c48116701f5281c5a4deacc5a445126e12b6e407551fc097709d579231a31d74944e88eb404b476bc2667d073686717dbce7c79388608600f73445b10a2538
7
+ data.tar.gz: 90872e2cce274d7bc42593f93da678bf8a234fed5c4b12d3bf4e83d8ae665ab046cf22c7ded2e26fb8f1425aa0a108d256c61ca7060f3434268e1c713bc81b31
data/CHANGELOG.md CHANGED
@@ -1,7 +1,13 @@
1
1
  # ActiveFulfillment changelog
2
2
 
3
- ## Unreleased
3
+ ### Version 3.0.1 (January 2015)
4
4
 
5
+ - Use Nokogiri for all xml handling.
6
+ - Ruby 2.3.0 support.
7
+ - Refactor Amazon MWS calls/parsing.
8
+ - Freeze constants and hashes.
9
+
10
+ ### Version 2.1.8
5
11
  - Update dependencies
6
12
  - Remove old Amazon fulfillment service (use amazon_aws instead)
7
13
  - Add contributing guidelines
@@ -36,7 +36,7 @@ end
36
36
  require 'builder'
37
37
  require 'cgi'
38
38
  require 'net/https'
39
- require 'rexml/document'
39
+ require 'nokogiri'
40
40
  require 'active_utils'
41
41
 
42
42
  require 'active_fulfillment/version'
@@ -44,3 +44,4 @@ require 'active_fulfillment/base'
44
44
  require 'active_fulfillment/response'
45
45
  require 'active_fulfillment/service'
46
46
  require 'active_fulfillment/services'
47
+ require 'active_fulfillment/parsing'
@@ -0,0 +1,15 @@
1
+ module ActiveFulfillment
2
+ module Parsing
3
+ module_function
4
+
5
+ def with_xml_document(xml, response = {})
6
+ begin
7
+ document = Nokogiri::XML(xml)
8
+ rescue Nokogiri::XML::SyntaxError
9
+ return response
10
+ end
11
+
12
+ yield document, response
13
+ end
14
+ end
15
+ end
@@ -6,15 +6,16 @@ require 'active_support/core_ext/hash/except'
6
6
  module ActiveFulfillment
7
7
  class AmazonMarketplaceWebService < Service
8
8
 
9
- APPLICATION_IDENTIFIER = "active_merchant_mws/0.01 (Language=ruby)"
9
+ APPLICATION_IDENTIFIER = 'active_merchant_mws/0.01 (Language=ruby)'.freeze
10
10
 
11
- REGISTRATION_URI = URI.parse("https://sellercentral.amazon.com/gp/mws/registration/register.html")
11
+ REGISTRATION_URI = URI.parse('https://sellercentral.amazon.com/gp/mws/registration/register.html').freeze
12
12
 
13
13
  SIGNATURE_VERSION = 2
14
- SIGNATURE_METHOD = "SHA256"
15
- VERSION = "2010-10-01"
14
+ SIGNATURE_METHOD = 'SHA256'.freeze
15
+ VERSION = '2010-10-01'.freeze
16
16
 
17
- SUCCESS, FAILURE, ERROR = 'Accepted', 'Failure', 'Error'
17
+ SUCCESS, FAILURE, ERROR = 'Accepted'.freeze, 'Failure'.freeze, 'Error'.freeze
18
+ XML_FAILURE_RESPONSE = { :success => FAILURE }.freeze
18
19
 
19
20
  ENDPOINTS = {
20
21
  :ca => 'mws.amazonservices.ca',
@@ -26,7 +27,7 @@ module ActiveFulfillment
26
27
  :jp => 'mws.amazonservices.jp',
27
28
  :uk => 'mws-eu.amazonservices.ca',
28
29
  :us => 'mws.amazonservices.com'
29
- }
30
+ }.freeze
30
31
 
31
32
  LOOKUPS = {
32
33
  :destination_address => {
@@ -53,18 +54,20 @@ module ActiveFulfillment
53
54
  :list_inventory => {
54
55
  :sku => "SellerSkus.member.%d"
55
56
  }
56
- }
57
+ }.freeze
58
+
59
+ SHIPPING_METHODS = {
60
+ 'Standard Shipping' => 'Standard',
61
+ 'Expedited Shipping' => 'Expedited',
62
+ 'Priority Shipping' => 'Priority'
63
+ }.freeze
57
64
 
58
65
  # The first is the label, and the last is the code
59
66
  # Standard: 3-5 business days
60
67
  # Expedited: 2 business days
61
68
  # Priority: 1 business day
62
69
  def self.shipping_methods
63
- [
64
- [ 'Standard Shipping', 'Standard' ],
65
- [ 'Expedited Shipping', 'Expedited' ],
66
- [ 'Priority Shipping', 'Priority' ]
67
- ].inject({}){|h, (k,v)| h[k] = v; h}
70
+ SHIPPING_METHODS
68
71
  end
69
72
 
70
73
  def initialize(options = {})
@@ -86,7 +89,7 @@ module ActiveFulfillment
86
89
  requires!(options, :order_date, :shipping_method)
87
90
  with_error_handling do
88
91
  data = commit :post, 'FulfillmentOutboundShipment', build_fulfillment_request(order_id, shipping_address, line_items, options)
89
- parse_fulfillment_response(parse_document(data), 'Successfully submitted the order')
92
+ parse_fulfillment_response('Successfully submitted the order')
90
93
  end
91
94
  end
92
95
 
@@ -195,27 +198,28 @@ module ActiveFulfillment
195
198
 
196
199
  def parse_document(xml)
197
200
  begin
198
- document = REXML::Document.new(xml)
199
- rescue REXML::ParseException
200
- return { :success => FAILURE }
201
+ document = Nokogiri::XML(xml)
202
+ rescue Nokogiri::XML::SyntaxError
203
+ return XML_FAILURE_RESPONSE
201
204
  end
202
205
  end
203
206
 
204
207
  def parse_tracking_response(document)
205
- response = {}
206
- response[:tracking_numbers] = {}
207
- response[:tracking_companies] = {}
208
- response[:tracking_urls] = {}
208
+ response = {
209
+ tracking_numbers: {},
210
+ tracking_companies: {},
211
+ tracking_urls: {}
212
+ }
209
213
 
210
- tracking_numbers = REXML::XPath.match(document, "//FulfillmentShipmentPackage/member/TrackingNumber")
214
+ tracking_numbers = document.css('FulfillmentShipmentPackage > member > TrackingNumber'.freeze)
211
215
  if tracking_numbers.present?
212
- order_id = REXML::XPath.first(document, "//FulfillmentOrder/SellerFulfillmentOrderId").text.strip
216
+ order_id = document.at_css('FulfillmentOrder > SellerFulfillmentOrderId'.freeze).text.strip
213
217
  response[:tracking_numbers][order_id] = tracking_numbers.map{ |t| t.text.strip }
214
218
  end
215
219
 
216
- tracking_companies = REXML::XPath.match(document, "//FulfillmentShipmentPackage/member/CarrierCode")
220
+ tracking_companies = document.css('FulfillmentShipmentPackage > member > CarrierCode'.freeze)
217
221
  if tracking_companies.present?
218
- order_id = REXML::XPath.first(document, "//FulfillmentOrder/SellerFulfillmentOrderId").text.strip
222
+ order_id = document.at_css('FulfillmentOrder > SellerFulfillmentOrderId'.freeze).text.strip
219
223
  response[:tracking_companies][order_id] = tracking_companies.map{ |t| t.text.strip }
220
224
  end
221
225
 
@@ -223,21 +227,20 @@ module ActiveFulfillment
223
227
  Response.new(success?(response), message_from(response), response)
224
228
  end
225
229
 
226
- def parse_fulfillment_response(document, message)
230
+ def parse_fulfillment_response(message)
227
231
  Response.new(true, message, { :response_status => SUCCESS, :response_comment => message })
228
232
  end
229
233
 
230
234
  def parse_inventory_response(document)
231
- response = {}
232
- response[:stock_levels] = {}
235
+ response = { stock_levels: {} }
233
236
 
234
- document.each_element('//InventorySupplyList/member') do |node|
237
+ document.css('InventorySupplyList > member'.freeze).each do |node|
235
238
  params = node.elements.to_a.each_with_object({}) { |elem, hash| hash[elem.name] = elem.text }
236
239
 
237
240
  response[:stock_levels][params['SellerSKU']] = params['InStockSupplyQuantity'].to_i
238
241
  end
239
242
 
240
- next_token = REXML::XPath.first(document, '//NextToken')
243
+ next_token = document.at_css('NextToken'.freeze)
241
244
  response[:next_token] = next_token ? next_token.text : nil
242
245
 
243
246
  response[:response_status] = SUCCESS
@@ -245,15 +248,15 @@ module ActiveFulfillment
245
248
  end
246
249
 
247
250
  def parse_error(http_response)
248
- response = {}
249
- response[:http_code] = http_response.code
250
- response[:http_message] = http_response.message
251
-
252
- document = REXML::Document.new(http_response.body)
251
+ response = {
252
+ http_code: http_response.code,
253
+ http_message: http_response.message
254
+ }
253
255
 
254
- node = REXML::XPath.first(document, '//Error')
255
- error_code = REXML::XPath.first(node, '//Code')
256
- error_message = REXML::XPath.first(node, '//Message')
256
+ document = Nokogiri::XML(http_response.body)
257
+ node = document.at_css('Error'.freeze)
258
+ error_code = node.at_css('Code'.freeze)
259
+ error_message = node.at_css('Message'.freeze)
257
260
 
258
261
  response[:status] = FAILURE
259
262
  response[:faultcode] = error_code ? error_code.text : ""
@@ -261,7 +264,7 @@ module ActiveFulfillment
261
264
  response[:response_message] = error_message ? error_message.text : ""
262
265
  response[:response_comment] = "#{response[:faultcode]}: #{response[:faultstring]}"
263
266
  response
264
- rescue REXML::ParseException => e
267
+ rescue Nokogiri::XML::SyntaxError => e
265
268
  rescue NoMethodError => e
266
269
  response[:http_body] = http_response.body
267
270
  response[:response_status] = FAILURE
@@ -6,7 +6,7 @@ module ActiveFulfillment
6
6
  SERVICE_URLS = {
7
7
  fulfillment: 'https://%{subdomain}.sixworks.co.uk/api/1/',
8
8
  inventory: 'https://%{subdomain}.sixworks.co.uk/api/1/stock'
9
- }
9
+ }.freeze
10
10
 
11
11
  def initialize(options = {})
12
12
  requires!(options, :subdomain, :key)
@@ -6,17 +6,17 @@ module ActiveFulfillment
6
6
  SERVICE_URLS = { :fulfillment => 'https://api.shipwire.com/exec/FulfillmentServices.php',
7
7
  :inventory => 'https://api.shipwire.com/exec/InventoryServices.php',
8
8
  :tracking => 'https://api.shipwire.com/exec/TrackingServices.php'
9
- }
9
+ }.freeze
10
10
 
11
11
  SCHEMA_URLS = { :fulfillment => 'http://www.shipwire.com/exec/download/OrderList.dtd',
12
12
  :inventory => 'http://www.shipwire.com/exec/download/InventoryUpdate.dtd',
13
13
  :tracking => 'http://www.shipwire.com/exec/download/TrackingUpdate.dtd'
14
- }
14
+ }.freeze
15
15
 
16
16
  POST_VARS = { :fulfillment => 'OrderListXML',
17
17
  :inventory => 'InventoryUpdateXML',
18
18
  :tracking => 'TrackingUpdateXML'
19
- }
19
+ }.freeze
20
20
 
21
21
  WAREHOUSES = { 'CHI' => 'Chicago',
22
22
  'LAX' => 'Los Angeles',
@@ -24,7 +24,15 @@ module ActiveFulfillment
24
24
  'VAN' => 'Vancouver',
25
25
  'TOR' => 'Toronto',
26
26
  'UK' => 'United Kingdom'
27
- }
27
+ }.freeze
28
+
29
+ SHIPPING_METHODS = {
30
+ '1 Day Service' => '1D',
31
+ '2 Day Service' => '2D',
32
+ 'Ground Service' => 'GD',
33
+ 'Freight Service' => 'FT',
34
+ 'International' => 'INTL'
35
+ }.freeze
28
36
 
29
37
  INVALID_LOGIN = /(Error with Valid Username\/EmailAddress and Password Required)|(Could not verify Username\/EmailAddress and Password combination)/
30
38
 
@@ -32,12 +40,7 @@ module ActiveFulfillment
32
40
 
33
41
  # The first is the label, and the last is the code
34
42
  def self.shipping_methods
35
- [ ['1 Day Service', '1D'],
36
- ['2 Day Service', '2D'],
37
- ['Ground Service', 'GD'],
38
- ['Freight Service', 'FT'],
39
- ['International', 'INTL']
40
- ].inject({}){|h, (k,v)| h[k] = v; h}
43
+ SHIPPING_METHODS
41
44
  end
42
45
 
43
46
  # Pass in the login and password for the shipwire account.
@@ -188,79 +191,91 @@ module ActiveFulfillment
188
191
  end
189
192
 
190
193
  def parse_fulfillment_response(xml)
191
- response = {}
194
+ Parsing.with_xml_document(xml) do |document, response|
195
+ document.root.try do |root_document|
196
+ root_document.elements.each do |node|
197
+ response[node.name.underscore.to_sym] = node.text.strip
198
+ end
199
+ end
192
200
 
193
- document = REXML::Document.new(xml)
194
- document.root.elements.each do |node|
195
- response[node.name.underscore.to_sym] = text_content(node)
201
+ response[:success] = response[:status] == '0'.freeze
202
+ response[:message] = response[:success] ? 'Successfully submitted the order'.freeze : message_from(response[:error_message])
203
+ response
196
204
  end
205
+ end
197
206
 
198
- response[:success] = response[:status] == '0'
199
- response[:message] = response[:success] ? "Successfully submitted the order" : message_from(response[:error_message])
200
- response
207
+ def compute_stock_levels(document)
208
+ items = {}
209
+ products = document.xpath('//Product'.freeze)
210
+ products.each do |product|
211
+ qty = product.at_xpath('@quantity'.freeze).child.content.to_i
212
+ code = product.at_xpath('@code'.freeze).child.content
213
+ pending_qty = include_pending_stock? ? product.at_xpath('@pending'.freeze).child.content.to_i : 0
214
+ items[code] = qty + pending_qty
215
+ end
216
+ items
201
217
  end
202
218
 
203
219
  def parse_inventory_response(xml)
204
- response = {}
205
- response[:stock_levels] = {}
206
-
207
- document = REXML::Document.new(xml)
208
- document.root.elements.each do |node|
209
- if node.name == 'Product'
210
- to_check = ['quantity']
211
- to_check << 'pending' if include_pending_stock?
212
-
213
- amount = to_check.sum { |a| node.attributes[a].to_i }
214
- response[:stock_levels][node.attributes['code']] = amount
215
- else
216
- response[node.name.underscore.to_sym] = text_content(node)
217
- end
220
+ response = { stock_levels: {} }
221
+ Parsing.with_xml_document(xml, response) do |document|
222
+ status = document.at_xpath('//Status'.freeze).child.content
223
+ total_products = document.at_xpath('//TotalProducts'.freeze).child.content
224
+ success = test? ? status == 'Test'.freeze : status == '0'.freeze
225
+ message = success ? 'Successfully received the stock levels'.freeze : document.at_xpath('//ErrorMessage'.freeze).child.content
226
+
227
+ {
228
+ status: status,
229
+ total_products: total_products,
230
+ stock_levels: compute_stock_levels(document),
231
+ message: message,
232
+ success: success
233
+ }
218
234
  end
235
+ end
219
236
 
220
- response[:success] = test? ? response[:status] == 'Test' : response[:status] == '0'
221
- response[:message] = response[:success] ? "Successfully received the stock levels" : message_from(response[:error_message])
222
-
223
- response
237
+ def shipped_order?(node)
238
+ node.name == 'Order'.freeze && node.attributes['shipped'.freeze].text == 'YES'.freeze
224
239
  end
225
240
 
226
241
  def parse_tracking_response(xml)
227
- response = {}
228
- response[:tracking_numbers] = {}
229
- response[:tracking_companies] = {}
230
- response[:tracking_urls] = {}
231
-
232
- document = REXML::Document.new(xml)
233
- document.root.elements.each do |node|
234
- if node.name == 'Order'
235
- if node.attributes["shipped"] == "YES" && node.elements['TrackingNumber']
236
- tracking_number = node.elements['TrackingNumber'].text.strip
237
- response[:tracking_numbers][node.attributes['id']] = [tracking_number]
238
-
239
- tracking_company = node.elements['TrackingNumber'].attributes['carrier']
240
- response[:tracking_companies][node.attributes['id']] = [tracking_company.strip] if tracking_company
241
-
242
- tracking_url = node.elements['TrackingNumber'].attributes['href']
243
- response[:tracking_urls][node.attributes['id']] = [tracking_url.strip] if tracking_url
242
+ response = {
243
+ tracking_numbers: {},
244
+ tracking_companies: {},
245
+ tracking_urls: {}
246
+ }
247
+
248
+ Parsing.with_xml_document(xml, response) do |document, response|
249
+ document.root.try do |root_document|
250
+ root_document.elements.each do |node|
251
+ if shipped_order?(node)
252
+ node_tracking = node.at_css('TrackingNumber'.freeze)
253
+ unless node_tracking.nil?
254
+ node_id = node.attributes['id'.freeze].text.strip
255
+ tracking_number = node_tracking.text.strip
256
+ response[:tracking_numbers][node_id] = [tracking_number]
257
+
258
+ tracking_company = node_tracking.attributes['carrier'.freeze].try { |item| item.text.strip }
259
+ response[:tracking_companies][node_id] = [tracking_company] if tracking_company
260
+
261
+ tracking_url = node_tracking.attributes['href'.freeze].try { |item| item.text.strip }
262
+ response[:tracking_urls][node_id] = [tracking_url] if tracking_url
263
+ end
264
+ else
265
+ response[node.name.underscore.to_sym] = node.text.strip
266
+ end
244
267
  end
245
- else
246
- response[node.name.underscore.to_sym] = text_content(node)
247
268
  end
248
269
  end
249
270
 
250
- response[:success] = test? ? (response[:status] == '0' || response[:status] == 'Test') : response[:status] == '0'
251
- response[:message] = response[:success] ? "Successfully received the tracking numbers" : message_from(response[:error_message])
271
+ response[:success] = test? ? (response[:status] == '0'.freeze || response[:status] == 'Test'.freeze) : response[:status] == '0'.freeze
272
+ response[:message] = response[:success] ? 'Successfully received the tracking numbers'.freeze : message_from(response[:error_message])
252
273
  response
253
274
  end
254
275
 
255
276
  def message_from(string)
256
277
  return if string.blank?
257
- string.gsub("\n", '').squeeze(" ")
258
- end
259
-
260
- def text_content(xml_node)
261
- text = xml_node.text
262
- text = xml_node.cdatas.join if text.blank?
263
- text
278
+ string.gsub("\n", ''.freeze).squeeze(' '.freeze)
264
279
  end
265
280
  end
266
281
  end
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/to_query'
3
3
  module ActiveFulfillment
4
4
  class ShopifyAPIService < Service
5
5
 
6
- OrderIdCutoffDate = Date.iso8601("2015-03-01")
6
+ OrderIdCutoffDate = Date.iso8601('2015-03-01').freeze
7
7
 
8
8
  RESCUABLE_CONNECTION_ERRORS = [
9
9
  Net::ReadTimeout,
@@ -28,7 +28,7 @@ module ActiveFulfillment
28
28
  ActiveUtils::ConnectionError,
29
29
  ActiveUtils::ResponseError,
30
30
  ActiveUtils::InvalidResponseError
31
- ]
31
+ ].freeze
32
32
 
33
33
  def initialize(options = {})
34
34
  @name = options[:name]
@@ -37,27 +37,27 @@ module ActiveFulfillment
37
37
  end
38
38
 
39
39
  def fulfill(order_id, shipping_address, line_items, options = {})
40
- raise NotImplementedError.new("Shopify API Service must listen to fulfillment/create Webhooks")
40
+ raise NotImplementedError.new('Shopify API Service must listen to fulfillment/create Webhooks'.freeze)
41
41
  end
42
42
 
43
43
  def fetch_stock_levels(options = {})
44
- response = send_app_request('fetch_stock', options.delete(:headers), options)
44
+ response = send_app_request('fetch_stock'.freeze, options.delete(:headers), options)
45
45
  if response
46
- stock_levels = parse_response(response, 'StockLevels', 'Product', 'Sku', 'Quantity') { |p| p.to_i }
47
- Response.new(true, "API stock levels", {:stock_levels => stock_levels})
46
+ stock_levels = parse_response(response, 'StockLevels'.freeze, 'Product'.freeze, 'Sku'.freeze, 'Quantity'.freeze) { |p| p.to_i }
47
+ Response.new(true, 'API stock levels'.freeze, {:stock_levels => stock_levels})
48
48
  else
49
- Response.new(false, "Unable to fetch remote stock levels")
49
+ Response.new(false, 'Unable to fetch remote stock levels'.freeze)
50
50
  end
51
51
  end
52
52
 
53
53
  def fetch_tracking_data(order_numbers, options = {})
54
54
  options.merge!({:order_ids => order_numbers, :order_names => order_numbers})
55
- response = send_app_request('fetch_tracking_numbers', options.delete(:headers), options)
55
+ response = send_app_request('fetch_tracking_numbers'.freeze, options.delete(:headers), options)
56
56
  if response
57
- tracking_numbers = parse_response(response, 'TrackingNumbers', 'Order', 'ID', 'Tracking') { |o| o }
58
- Response.new(true, "API tracking_numbers", {:tracking_numbers => tracking_numbers,
59
- :tracking_companies => {},
60
- :tracking_urls => {}})
57
+ tracking_numbers = parse_response(response, 'TrackingNumbers'.freeze, 'Order'.freeze, 'ID'.freeze, 'Tracking'.freeze) { |o| o }
58
+ Response.new(true, 'API tracking_numbers'.freeze, {:tracking_numbers => tracking_numbers,
59
+ :tracking_companies => {},
60
+ :tracking_urls => {}})
61
61
  else
62
62
  Response.new(false, "Unable to fetch remote tracking numbers #{order_numbers.inspect}")
63
63
  end
@@ -72,7 +72,7 @@ module ActiveFulfillment
72
72
  def send_app_request(action, headers, data)
73
73
  uri = request_uri(action, data)
74
74
 
75
- logger.info "[" + @name.upcase + " APP] Post #{uri}"
75
+ logger.info "[#{@name.upcase} APP] Post #{uri}"
76
76
 
77
77
  response = nil
78
78
  realtime = Benchmark.realtime do
@@ -85,39 +85,46 @@ module ActiveFulfillment
85
85
  end
86
86
  end
87
87
 
88
- line = "[" + @name.upcase + "APP] Response from #{uri} --> "
88
+ line = "[#{@name.upcase} APP] Response from #{uri} --> "
89
89
  line << "#{response} #{"%.4fs" % realtime}"
90
90
  logger.info line
91
91
 
92
92
  response
93
93
  end
94
94
 
95
- def parse_response(response, root, type, key, value)
96
- case @format
97
- when 'json'
98
- response_data = ActiveSupport::JSON.decode(response)
99
- return {} unless response_data.is_a?(Hash)
100
- response_data[root.underscore] || response_data
101
- when 'xml'
102
- response_data = {}
103
- document = REXML::Document.new(response)
104
- document.elements[root].each do |node|
105
- if node.name == type
106
- response_data[node.elements[key].text] = node.elements[value].text
107
- end
95
+ def parse_json(json_data, root)
96
+ response_data = ActiveSupport::JSON.decode(json_data)
97
+ return {} unless response_data.is_a?(Hash)
98
+ response_data[root.underscore] || response_data
99
+ rescue ActiveSupport::JSON.parse_error
100
+ {}
101
+ end
102
+
103
+
104
+ def parse_xml(xml_data, type, key, value)
105
+ Parsing.with_xml_document(xml_data) do |document, response|
106
+ # Extract All elements of type and map them!
107
+ root_element = document.xpath("//#{type}")
108
+ root_element.each do |type_item|
109
+ key_item = type_item.at_css(key).child.content
110
+ value_item = type_item.at_css(value).child.content
111
+ response[key_item] = value_item
108
112
  end
109
- response_data
113
+ response
110
114
  end
115
+ end
111
116
 
112
- rescue ActiveSupport::JSON.parse_error, REXML::ParseException
117
+ def parse_response(response, root, type, key, value)
118
+ return parse_json(response, root) if @format == 'json'.freeze
119
+ return parse_xml(response, type, key, value) if @format == 'xml'.freeze
113
120
  {}
114
121
  end
115
122
 
116
123
  def encode_payload(payload, root)
117
124
  case @format
118
- when 'json'
125
+ when 'json'.freeze
119
126
  {root => payload}.to_json
120
- when 'xml'
127
+ when 'xml'.freeze
121
128
  payload.to_xml(:root => root)
122
129
  end
123
130
  end
@@ -1,24 +1,63 @@
1
1
  module ActiveFulfillment
2
2
  class WebgistixService < Service
3
+
3
4
  SERVICE_URLS = {
4
5
  :fulfillment => 'https://www.webgistix.com/XML/CreateOrder.asp',
5
6
  :inventory => 'https://www.webgistix.com/XML/GetInventory.asp',
6
7
  :tracking => 'https://www.webgistix.com/XML/GetTracking.asp'
7
- }
8
- TEST_URLS = SERVICE_URLS.merge({
8
+ }.freeze
9
+
10
+ TEST_URLS = SERVICE_URLS.dup.merge({
9
11
  :fulfillment => 'https://www.webgistix.com/XML/CreateOrderTest.asp'
10
- })
11
-
12
- SUCCESS, DUPLICATE, FAILURE = 'True', 'Duplicate', 'False'
13
-
14
- SUCCESS_MESSAGE = 'Successfully submitted the order'
15
- FAILURE_MESSAGE = 'Failed to submit the order'
16
- DUPLICATE_MESSAGE = 'This order has already been successfully submitted'
17
-
18
- INVALID_LOGIN = 'Invalid Credentials'
19
- NOT_SHIPPED = 'Not Shipped'
20
-
21
- TRACKING_COMPANIES = %w(UPS FedEx USPS)
12
+ }).freeze
13
+
14
+ SUCCESS, DUPLICATE, FAILURE = 'True'.freeze, 'Duplicate'.freeze, 'False'.freeze
15
+
16
+ SUCCESS_MESSAGE = 'Successfully submitted the order'.freeze
17
+ FAILURE_MESSAGE = 'Failed to submit the order'.freeze
18
+ DUPLICATE_MESSAGE = 'This order has already been successfully submitted'.freeze
19
+
20
+ INVALID_LOGIN = 'Invalid Credentials'.freeze
21
+ NOT_SHIPPED = 'Not Shipped'.freeze
22
+
23
+ TRACKING_COMPANIES = %w(UPS FedEx USPS).freeze
24
+
25
+ SHIPPING_PROVIDERS = {
26
+ 'UPS Ground Shipping' => 'Ground',
27
+ 'UPS Ground' => 'Ground',
28
+ 'UPS Standard Shipping (Canada Only)' => 'Standard',
29
+ 'UPS Standard Shipping (CA & MX Only)' => 'Standard',
30
+ 'UPS 3-Business Day' => '3-Day Select',
31
+ 'UPS 2-Business Day' => '2nd Day Air',
32
+ 'UPS 2-Business Day AM' => '2nd Day Air AM',
33
+ 'UPS Next Day' => 'Next Day Air',
34
+ 'UPS Next Day Saver' => 'Next Day Air Saver',
35
+ 'UPS Next Day Early AM' => 'Next Day Air Early AM',
36
+ 'UPS Worldwide Express (Next Day)' => 'Worldwide Express',
37
+ 'UPS Worldwide Expedited (2nd Day)' => 'Worldwide Expedited',
38
+ 'UPS Worldwide Express Saver' => 'Worldwide Express Saver',
39
+ 'FedEx Priority Overnight' => 'FedEx Priority Overnight',
40
+ 'FedEx Standard Overnight' => 'FedEx Standard Overnight',
41
+ 'FedEx First Overnight' => 'FedEx First Overnight',
42
+ 'FedEx 2nd Day' => 'FedEx 2nd Day',
43
+ 'FedEx Express Saver' => 'FedEx Express Saver',
44
+ 'FedEx International Priority' => 'FedEx International Priority',
45
+ 'FedEx International Economy' => 'FedEx International Economy',
46
+ 'FedEx International First' => 'FedEx International First',
47
+ 'FedEx Ground' => 'FedEx Ground',
48
+ 'USPS Priority Mail' => 'Priority Mail',
49
+ 'USPS Priority Mail International' => 'Priority Mail International',
50
+ 'USPS Priority Mail Small Flat Rate Box' => 'Priority Mail Small Flat Rate Box',
51
+ 'USPS Priority Mail Medium Flat Rate Box' => 'Priority Mail Medium Flat Rate Box',
52
+ 'USPS Priority Mail Large Flat Rate Box' => "Priority Mail Large Flat Rate Box",
53
+ 'USPS Priority Mail Flat Rate Envelope' => 'Priority Mail Flat Rate Envelope',
54
+ 'USPS First Class Mail' => 'First Class',
55
+ 'USPS First Class International' => 'First Class International',
56
+ 'USPS Express Mail' => 'Express',
57
+ 'USPS Express Mail International' => 'Express Mail International',
58
+ 'USPS Parcel Post' => 'Parcel',
59
+ 'USPS Media Mail' => 'Media Mail'
60
+ }.freeze
22
61
 
23
62
  # If a request is detected as a duplicate only the original data will be
24
63
  # used by Webgistix, and the subsequent responses will have a
@@ -27,42 +66,7 @@ module ActiveFulfillment
27
66
 
28
67
  # The first is the label, and the last is the code
29
68
  def self.shipping_methods
30
- [
31
- ["UPS Ground Shipping", "Ground"],
32
- ["UPS Ground", "Ground"],
33
- ["UPS Standard Shipping (Canada Only)", "Standard"],
34
- ["UPS Standard Shipping (CA & MX Only)", "Standard"],
35
- ["UPS 3-Business Day", "3-Day Select"],
36
- ["UPS 2-Business Day", "2nd Day Air"],
37
- ["UPS 2-Business Day AM", "2nd Day Air AM"],
38
- ["UPS Next Day", "Next Day Air"],
39
- ["UPS Next Day Saver", "Next Day Air Saver"],
40
- ["UPS Next Day Early AM", "Next Day Air Early AM"],
41
- ["UPS Worldwide Express (Next Day)", "Worldwide Express"],
42
- ["UPS Worldwide Expedited (2nd Day)", "Worldwide Expedited"],
43
- ["UPS Worldwide Express Saver", "Worldwide Express Saver"],
44
- ["FedEx Priority Overnight", "FedEx Priority Overnight"],
45
- ["FedEx Standard Overnight", "FedEx Standard Overnight"],
46
- ["FedEx First Overnight", "FedEx First Overnight"],
47
- ["FedEx 2nd Day", "FedEx 2nd Day"],
48
- ["FedEx Express Saver", "FedEx Express Saver"],
49
- ["FedEx International Priority", "FedEx International Priority"],
50
- ["FedEx International Economy", "FedEx International Economy"],
51
- ["FedEx International First", "FedEx International First"],
52
- ["FedEx Ground", "FedEx Ground"],
53
- ["USPS Priority Mail", "Priority Mail"],
54
- ["USPS Priority Mail International", "Priority Mail International"],
55
- ["USPS Priority Mail Small Flat Rate Box", "Priority Mail Small Flat Rate Box"],
56
- ["USPS Priority Mail Medium Flat Rate Box", "Priority Mail Medium Flat Rate Box"],
57
- ["USPS Priority Mail Large Flat Rate Box", "Priority Mail Large Flat Rate Box"],
58
- ["USPS Priority Mail Flat Rate Envelope", "Priority Mail Flat Rate Envelope"],
59
- ["USPS First Class Mail", "First Class"],
60
- ["USPS First Class International", "First Class International"],
61
- ["USPS Express Mail", "Express"],
62
- ["USPS Express Mail International", "Express Mail International"],
63
- ["USPS Parcel Post", "Parcel"],
64
- ["USPS Media Mail", "Media Mail"]
65
- ].inject({}){|h, (k,v)| h[k] = v; h}
69
+ SHIPPING_PROVIDERS
66
70
  end
67
71
 
68
72
  # Pass in the login and password for the shipwire account.
@@ -245,8 +249,8 @@ module ActiveFulfillment
245
249
 
246
250
  def parse_response(action, xml)
247
251
  begin
248
- document = REXML::Document.new("<response>#{xml}</response>")
249
- rescue REXML::ParseException
252
+ document = Nokogiri::XML("<response>#{xml}</response>")
253
+ rescue Nokogiri::XML::SyntaxError
250
254
  return {:success => FAILURE}
251
255
  end
252
256
 
@@ -266,7 +270,7 @@ module ActiveFulfillment
266
270
  response = parse_errors(document)
267
271
 
268
272
  # Check if completed
269
- if completed = REXML::XPath.first(document, '//Completed')
273
+ if completed = document.at_xpath('//Completed'.freeze)
270
274
  completed.elements.each do |e|
271
275
  response[e.name.underscore.to_sym] = e.text
272
276
  end
@@ -283,11 +287,11 @@ module ActiveFulfillment
283
287
  response = parse_errors(document)
284
288
  response[:stock_levels] = {}
285
289
 
286
- document.root.each_element('//Item') do |node|
290
+ document.root.xpath('//Item'.freeze).each do |node|
287
291
  # {ItemID => 'SOME-ID', ItemQty => '101'}
288
292
  params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
289
293
 
290
- response[:stock_levels][params['ItemID']] = params['ItemQty'].to_i
294
+ response[:stock_levels][params['ItemID'.freeze]] = params['ItemQty'.freeze].to_i
291
295
  end
292
296
 
293
297
  response
@@ -295,25 +299,23 @@ module ActiveFulfillment
295
299
 
296
300
  def parse_tracking_response(document)
297
301
  response = parse_errors(document)
298
- response[:tracking_numbers] = {}
299
- response[:tracking_companies] = {}
300
- response[:tracking_urls] = {}
302
+ response = response.merge(tracking_numbers: {}, tracking_companies: {}, tracking_urls: {})
301
303
 
302
- document.root.each_element('//Shipment') do |node|
304
+ document.root.xpath('//Shipment'.freeze).each do |node|
303
305
  # {InvoiceNumber => 'SOME-ID', ShipmentTrackingNumber => 'SOME-TRACKING-NUMBER'}
304
306
  params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
305
307
 
306
- tracking = params['ShipmentTrackingNumber']
308
+ tracking = params['ShipmentTrackingNumber'.freeze]
307
309
 
308
310
  unless tracking == NOT_SHIPPED
309
- response[:tracking_numbers][params['InvoiceNumber']] ||= []
310
- response[:tracking_numbers][params['InvoiceNumber']] << tracking
311
+ response[:tracking_numbers][params['InvoiceNumber'.freeze]] ||= []
312
+ response[:tracking_numbers][params['InvoiceNumber'.freeze]] << tracking
311
313
  end
312
314
 
313
- company = params['Method'].split[0] if params['Method']
315
+ company = params['Method'.freeze].split[0] if params['Method'.freeze]
314
316
  if TRACKING_COMPANIES.include? company
315
- response[:tracking_companies][params['InvoiceNumber']] ||= []
316
- response[:tracking_companies][params['InvoiceNumber']] << company
317
+ response[:tracking_companies][params['InvoiceNumber'.freeze]] ||= []
318
+ response[:tracking_companies][params['InvoiceNumber'.freeze]] << company
317
319
  end
318
320
  end
319
321
 
@@ -323,7 +325,7 @@ module ActiveFulfillment
323
325
  def parse_errors(document)
324
326
  response = {}
325
327
 
326
- REXML::XPath.match(document, "//Errors/Error").to_a.each_with_index do |e, i|
328
+ document.xpath('//Errors/Error'.freeze).each_with_index do |e, i|
327
329
  response["error_#{i}".to_sym] = e.text
328
330
  end
329
331
 
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module ActiveFulfillment
3
- VERSION = "3.0.0.pre8"
3
+ VERSION = "3.0.1"
4
4
  end
data/test/test_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'active_fulfillment'
5
5
  require 'minitest/autorun'
6
6
  require 'mocha/setup'
7
7
  require 'timecop'
8
+ require 'rexml/document'
8
9
 
9
10
  require 'logger'
10
11
  ActiveFulfillment::Service.logger = Logger.new(nil)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_fulfillment
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.pre8
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Fauser
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-10 00:00:00.000000000 Z
12
+ date: 2016-01-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '3.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: nokogiri
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.6'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.6'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: rake
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -118,6 +132,7 @@ files:
118
132
  - CHANGELOG.md
119
133
  - lib/active_fulfillment.rb
120
134
  - lib/active_fulfillment/base.rb
135
+ - lib/active_fulfillment/parsing.rb
121
136
  - lib/active_fulfillment/response.rb
122
137
  - lib/active_fulfillment/service.rb
123
138
  - lib/active_fulfillment/services.rb
@@ -164,13 +179,13 @@ summary: Framework and tools for dealing with shipping, tracking and order fulfi
164
179
  services.
165
180
  test_files:
166
181
  - test/test_helper.rb
167
- - test/remote/webgistix_test.rb
168
- - test/remote/shipwire_test.rb
169
- - test/remote/james_and_james_test.rb
170
182
  - test/remote/amazon_mws_test.rb
183
+ - test/remote/james_and_james_test.rb
184
+ - test/remote/shipwire_test.rb
185
+ - test/remote/webgistix_test.rb
186
+ - test/unit/services/amazon_mws_test.rb
187
+ - test/unit/services/james_and_james_test.rb
171
188
  - test/unit/services/shopify_api_test.rb
172
- - test/unit/services/webgistix_test.rb
173
189
  - test/unit/services/shipwire_test.rb
174
- - test/unit/services/james_and_james_test.rb
175
- - test/unit/services/amazon_mws_test.rb
190
+ - test/unit/services/webgistix_test.rb
176
191
  - test/unit/base_test.rb