active_fulfillment 2.1.9 → 3.0.0.pre2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/lib/active_fulfillment.rb +5 -5
  4. data/lib/active_fulfillment/base.rb +10 -0
  5. data/lib/active_fulfillment/response.rb +26 -0
  6. data/lib/active_fulfillment/service.rb +56 -0
  7. data/lib/active_fulfillment/services.rb +5 -0
  8. data/lib/active_fulfillment/services/amazon_mws.rb +473 -0
  9. data/lib/active_fulfillment/services/james_and_james.rb +122 -0
  10. data/lib/active_fulfillment/services/shipwire.rb +266 -0
  11. data/lib/active_fulfillment/services/shopify_api.rb +125 -0
  12. data/lib/active_fulfillment/services/webgistix.rb +334 -0
  13. data/lib/active_fulfillment/version.rb +4 -0
  14. data/test/remote/amazon_mws_test.rb +20 -17
  15. data/test/remote/james_and_james_test.rb +77 -0
  16. data/test/remote/shipwire_test.rb +25 -25
  17. data/test/remote/webgistix_test.rb +21 -21
  18. data/test/test_helper.rb +27 -52
  19. data/test/unit/base_test.rb +4 -4
  20. data/test/unit/services/amazon_mws_test.rb +56 -26
  21. data/test/unit/services/james_and_james_test.rb +90 -0
  22. data/test/unit/services/shipwire_test.rb +18 -18
  23. data/test/unit/services/shopify_api_test.rb +7 -20
  24. data/test/unit/services/webgistix_test.rb +35 -35
  25. metadata +32 -114
  26. data/CHANGELOG +0 -62
  27. data/lib/active_fulfillment/fulfillment/base.rb +0 -12
  28. data/lib/active_fulfillment/fulfillment/response.rb +0 -28
  29. data/lib/active_fulfillment/fulfillment/service.rb +0 -58
  30. data/lib/active_fulfillment/fulfillment/services.rb +0 -5
  31. data/lib/active_fulfillment/fulfillment/services/amazon.rb +0 -389
  32. data/lib/active_fulfillment/fulfillment/services/amazon_mws.rb +0 -454
  33. data/lib/active_fulfillment/fulfillment/services/shipwire.rb +0 -268
  34. data/lib/active_fulfillment/fulfillment/services/shopify_api.rb +0 -125
  35. data/lib/active_fulfillment/fulfillment/services/webgistix.rb +0 -338
  36. data/lib/active_fulfillment/fulfillment/version.rb +0 -6
  37. data/test/fixtures.yml +0 -16
  38. data/test/fixtures/xml/amazon/inventory_get_response.xml +0 -17
  39. data/test/fixtures/xml/amazon/inventory_list_response.xml +0 -29
  40. data/test/fixtures/xml/amazon/inventory_list_response_with_next_1.xml +0 -30
  41. data/test/fixtures/xml/amazon/inventory_list_response_with_next_2.xml +0 -29
  42. data/test/fixtures/xml/amazon/tracking_response_1.xml +0 -56
  43. data/test/fixtures/xml/amazon/tracking_response_2.xml +0 -38
  44. data/test/fixtures/xml/amazon/tracking_response_error.xml +0 -13
  45. data/test/fixtures/xml/amazon/tracking_response_not_found.xml +0 -13
  46. data/test/fixtures/xml/amazon_mws/fulfillment_get_fulfillment_order.xml +0 -114
  47. data/test/fixtures/xml/amazon_mws/fulfillment_get_fulfillment_order_2.xml +0 -90
  48. data/test/fixtures/xml/amazon_mws/fulfillment_get_fullfillment_order_with_multiple_tracking_numbers.xml +0 -121
  49. data/test/fixtures/xml/amazon_mws/fulfillment_list_all_fulfillment_orders.xml +0 -70
  50. data/test/fixtures/xml/amazon_mws/inventory_list_inventory_item_supply.xml +0 -32
  51. data/test/fixtures/xml/amazon_mws/inventory_list_inventory_supply.xml +0 -75
  52. data/test/fixtures/xml/amazon_mws/inventory_list_inventory_supply_by_next_token.xml +0 -38
  53. data/test/fixtures/xml/amazon_mws/tracking_response_error.xml +0 -9
  54. data/test/fixtures/xml/amazon_mws/tracking_response_not_found.xml +0 -9
  55. data/test/fixtures/xml/shipwire/fulfillment_failure_response.xml +0 -7
  56. data/test/fixtures/xml/shipwire/invalid_login_response.xml +0 -7
  57. data/test/fixtures/xml/shipwire/inventory_get_response.xml +0 -44
  58. data/test/fixtures/xml/shipwire/successful_empty_tracking_response.xml +0 -8
  59. data/test/fixtures/xml/shipwire/successful_live_tracking_response.xml +0 -53
  60. data/test/fixtures/xml/shipwire/successful_tracking_response.xml +0 -16
  61. data/test/fixtures/xml/shipwire/successful_tracking_response_with_tracking_urls.xml +0 -31
  62. data/test/fixtures/xml/webgistix/multiple_tracking_response.xml +0 -21
  63. data/test/fixtures/xml/webgistix/tracking_response.xml +0 -14
  64. data/test/remote/amazon_test.rb +0 -124
  65. data/test/unit/services/amazon_test.rb +0 -271
@@ -1,268 +0,0 @@
1
- require 'cgi'
2
-
3
- module ActiveMerchant
4
- module Fulfillment
5
- class ShipwireService < Service
6
-
7
- SERVICE_URLS = { :fulfillment => 'https://api.shipwire.com/exec/FulfillmentServices.php',
8
- :inventory => 'https://api.shipwire.com/exec/InventoryServices.php',
9
- :tracking => 'https://api.shipwire.com/exec/TrackingServices.php'
10
- }
11
-
12
- SCHEMA_URLS = { :fulfillment => 'http://www.shipwire.com/exec/download/OrderList.dtd',
13
- :inventory => 'http://www.shipwire.com/exec/download/InventoryUpdate.dtd',
14
- :tracking => 'http://www.shipwire.com/exec/download/TrackingUpdate.dtd'
15
- }
16
-
17
- POST_VARS = { :fulfillment => 'OrderListXML',
18
- :inventory => 'InventoryUpdateXML',
19
- :tracking => 'TrackingUpdateXML'
20
- }
21
-
22
- WAREHOUSES = { 'CHI' => 'Chicago',
23
- 'LAX' => 'Los Angeles',
24
- 'REN' => 'Reno',
25
- 'VAN' => 'Vancouver',
26
- 'TOR' => 'Toronto',
27
- 'UK' => 'United Kingdom'
28
- }
29
-
30
- INVALID_LOGIN = /(Error with Valid Username\/EmailAddress and Password Required)|(Could not verify Username\/EmailAddress and Password combination)/
31
-
32
- class_attribute :affiliate_id
33
-
34
- # The first is the label, and the last is the code
35
- def self.shipping_methods
36
- [ ['1 Day Service', '1D'],
37
- ['2 Day Service', '2D'],
38
- ['Ground Service', 'GD'],
39
- ['Freight Service', 'FT'],
40
- ['International', 'INTL']
41
- ].inject(ActiveSupport::OrderedHash.new){|h, (k,v)| h[k] = v; h}
42
- end
43
-
44
- # Pass in the login and password for the shipwire account.
45
- # Optionally pass in the :test => true to force test mode
46
- def initialize(options = {})
47
- requires!(options, :login, :password)
48
-
49
- super
50
- end
51
-
52
- def fulfill(order_id, shipping_address, line_items, options = {})
53
- commit :fulfillment, build_fulfillment_request(order_id, shipping_address, line_items, options)
54
- end
55
-
56
- def fetch_stock_levels(options = {})
57
- commit :inventory, build_inventory_request(options)
58
- end
59
-
60
- def fetch_tracking_data(order_ids, options = {})
61
- commit :tracking, build_tracking_request(order_ids)
62
- end
63
-
64
- def valid_credentials?
65
- response = fetch_tracking_numbers([])
66
- response.message !~ INVALID_LOGIN
67
- end
68
-
69
- def test_mode?
70
- true
71
- end
72
-
73
- def include_pending_stock?
74
- @options[:include_pending_stock]
75
- end
76
-
77
- def include_empty_stock?
78
- @options[:include_empty_stock]
79
- end
80
-
81
- private
82
- def build_fulfillment_request(order_id, shipping_address, line_items, options)
83
- xml = Builder::XmlMarkup.new :indent => 2
84
- xml.instruct!
85
- xml.declare! :DOCTYPE, :OrderList, :SYSTEM, SCHEMA_URLS[:fulfillment]
86
- xml.tag! 'OrderList' do
87
- add_credentials(xml)
88
- xml.tag! 'Referer', 'Active Fulfillment'
89
- add_order(xml, order_id, shipping_address, line_items, options)
90
- end
91
- xml.target!
92
- end
93
-
94
- def build_inventory_request(options)
95
- xml = Builder::XmlMarkup.new :indent => 2
96
- xml.instruct!
97
- xml.declare! :DOCTYPE, :InventoryStatus, :SYSTEM, SCHEMA_URLS[:inventory]
98
- xml.tag! 'InventoryUpdate' do
99
- add_credentials(xml)
100
- xml.tag! 'Warehouse', WAREHOUSES[options[:warehouse]]
101
- xml.tag! 'ProductCode', options[:sku]
102
- xml.tag! 'IncludeEmpty' if include_empty_stock?
103
- end
104
- end
105
-
106
- def build_tracking_request(order_ids)
107
- xml = Builder::XmlMarkup.new
108
- xml.instruct!
109
- xml.declare! :DOCTYPE, :InventoryStatus, :SYSTEM, SCHEMA_URLS[:inventory]
110
- xml.tag! 'TrackingUpdate' do
111
- add_credentials(xml)
112
- xml.tag! 'Server', test? ? 'Test' : 'Production'
113
- order_ids.each do |o_id|
114
- xml.tag! 'OrderNo', o_id
115
- end
116
- end
117
- end
118
-
119
- def add_credentials(xml)
120
- xml.tag! 'EmailAddress', @options[:login]
121
- xml.tag! 'Password', @options[:password]
122
- xml.tag! 'Server', test? ? 'Test' : 'Production'
123
- xml.tag! 'AffiliateId', affiliate_id if affiliate_id.present?
124
- end
125
-
126
- def add_order(xml, order_id, shipping_address, line_items, options)
127
- xml.tag! 'Order', :id => order_id do
128
- xml.tag! 'Warehouse', options[:warehouse] || '00'
129
-
130
- add_address(xml, shipping_address, options)
131
- xml.tag! 'Shipping', options[:shipping_method] unless options[:shipping_method].blank?
132
-
133
- Array(line_items).each_with_index do |line_item, index|
134
- add_item(xml, line_item, index)
135
- end
136
- xml.tag! 'Note' do
137
- xml.cdata! options[:note] unless options[:note].blank?
138
- end
139
- end
140
- end
141
-
142
- def add_address(xml, address, options)
143
- xml.tag! 'AddressInfo', :type => 'Ship' do
144
- xml.tag! 'Name' do
145
- xml.tag! 'Full', address[:name]
146
- end
147
-
148
- xml.tag! 'Address1', address[:address1]
149
- xml.tag! 'Address2', address[:address2]
150
-
151
- xml.tag! 'Company', address[:company]
152
-
153
- xml.tag! 'City', address[:city]
154
- xml.tag! 'State', address[:state] unless address[:state].blank?
155
- xml.tag! 'Country', address[:country]
156
-
157
- xml.tag! 'Zip', address[:zip]
158
- xml.tag! 'Phone', address[:phone] unless address[:phone].blank?
159
- xml.tag! 'Email', options[:email] unless options[:email].blank?
160
- end
161
- end
162
-
163
- # Code is limited to 12 characters
164
- def add_item(xml, item, index)
165
- xml.tag! 'Item', :num => index do
166
- xml.tag! 'Code', item[:sku]
167
- xml.tag! 'Quantity', item[:quantity]
168
- end
169
- end
170
-
171
- def commit(action, request)
172
- data = ssl_post(SERVICE_URLS[action], "#{POST_VARS[action]}=#{CGI.escape(request)}")
173
-
174
- response = parse_response(action, data)
175
- Response.new(response[:success], response[:message], response, :test => test?)
176
- end
177
-
178
- def parse_response(action, data)
179
- case action
180
- when :fulfillment
181
- parse_fulfillment_response(data)
182
- when :inventory
183
- parse_inventory_response(data)
184
- when :tracking
185
- parse_tracking_response(data)
186
- else
187
- raise ArgumentError, "Unknown action #{action}"
188
- end
189
- end
190
-
191
- def parse_fulfillment_response(xml)
192
- response = {}
193
-
194
- document = REXML::Document.new(xml)
195
- document.root.elements.each do |node|
196
- response[node.name.underscore.to_sym] = text_content(node)
197
- end
198
-
199
- response[:success] = response[:status] == '0'
200
- response[:message] = response[:success] ? "Successfully submitted the order" : message_from(response[:error_message])
201
- response
202
- end
203
-
204
- def parse_inventory_response(xml)
205
- response = {}
206
- response[:stock_levels] = {}
207
-
208
- document = REXML::Document.new(xml)
209
- document.root.elements.each do |node|
210
- if node.name == 'Product'
211
- to_check = ['quantity']
212
- to_check << 'pending' if include_pending_stock?
213
-
214
- amount = to_check.sum { |a| node.attributes[a].to_i }
215
- response[:stock_levels][node.attributes['code']] = amount
216
- else
217
- response[node.name.underscore.to_sym] = text_content(node)
218
- end
219
- end
220
-
221
- response[:success] = test? ? response[:status] == 'Test' : response[:status] == '0'
222
- response[:message] = response[:success] ? "Successfully received the stock levels" : message_from(response[:error_message])
223
-
224
- response
225
- end
226
-
227
- def parse_tracking_response(xml)
228
- response = {}
229
- response[:tracking_numbers] = {}
230
- response[:tracking_companies] = {}
231
- response[:tracking_urls] = {}
232
-
233
- document = REXML::Document.new(xml)
234
- document.root.elements.each do |node|
235
- if node.name == 'Order'
236
- if node.attributes["shipped"] == "YES" && node.elements['TrackingNumber']
237
- tracking_number = node.elements['TrackingNumber'].text.strip
238
- response[:tracking_numbers][node.attributes['id']] = [tracking_number]
239
-
240
- tracking_company = node.elements['TrackingNumber'].attributes['carrier']
241
- response[:tracking_companies][node.attributes['id']] = [tracking_company.strip] if tracking_company
242
-
243
- tracking_url = node.elements['TrackingNumber'].attributes['href']
244
- response[:tracking_urls][node.attributes['id']] = [tracking_url.strip] if tracking_url
245
- end
246
- else
247
- response[node.name.underscore.to_sym] = text_content(node)
248
- end
249
- end
250
-
251
- response[:success] = test? ? (response[:status] == '0' || response[:status] == 'Test') : response[:status] == '0'
252
- response[:message] = response[:success] ? "Successfully received the tracking numbers" : message_from(response[:error_message])
253
- response
254
- end
255
-
256
- def message_from(string)
257
- return if string.blank?
258
- string.gsub("\n", '').squeeze(" ")
259
- end
260
-
261
- def text_content(xml_node)
262
- text = xml_node.text
263
- text = xml_node.cdatas.join if text.blank?
264
- text
265
- end
266
- end
267
- end
268
- end
@@ -1,125 +0,0 @@
1
- module ActiveMerchant
2
- module Fulfillment
3
- class ShopifyAPIService < Service
4
-
5
- OrderIdCutoffDate = Date.iso8601("2015-03-01")
6
-
7
- RESCUABLE_CONNECTION_ERRORS = [
8
- Net::ReadTimeout,
9
- Net::OpenTimeout,
10
- TimeoutError,
11
- Errno::ETIMEDOUT,
12
- Timeout::Error,
13
- IOError,
14
- EOFError,
15
- SocketError,
16
- Errno::ECONNRESET,
17
- Errno::ECONNABORTED,
18
- Errno::EPIPE,
19
- Errno::ECONNREFUSED,
20
- Errno::EAGAIN,
21
- Errno::EHOSTUNREACH,
22
- Errno::ENETUNREACH,
23
- Resolv::ResolvError,
24
- Net::HTTPBadResponse,
25
- Net::HTTPHeaderSyntaxError,
26
- Net::ProtocolError,
27
- ActiveMerchant::ConnectionError,
28
- ActiveMerchant::ResponseError,
29
- ActiveMerchant::InvalidResponseError
30
- ]
31
-
32
- def initialize(options = {})
33
- @name = options[:name]
34
- @callback_url = options[:callback_url]
35
- @format = options[:format]
36
- end
37
-
38
- def fulfill(order_id, shipping_address, line_items, options = {})
39
- raise NotImplementedError.new("Shopify API Service must listen to fulfillment/create Webhooks")
40
- end
41
-
42
- def fetch_stock_levels(options = {})
43
- response = send_app_request('fetch_stock', options.delete(:headers), options)
44
- if response
45
- stock_levels = parse_response(response, 'StockLevels', 'Product', 'Sku', 'Quantity') { |p| p.to_i }
46
- Response.new(true, "API stock levels", {:stock_levels => stock_levels})
47
- else
48
- Response.new(false, "Unable to fetch remote stock levels")
49
- end
50
- end
51
-
52
- def fetch_tracking_data(order_numbers, options = {})
53
- options.merge!({:order_ids => order_numbers, :order_names => order_numbers})
54
- response = send_app_request('fetch_tracking_numbers', options.delete(:headers), options)
55
- if response
56
- tracking_numbers = parse_response(response, 'TrackingNumbers', 'Order', 'ID', 'Tracking') { |o| o }
57
- Response.new(true, "API tracking_numbers", {:tracking_numbers => tracking_numbers,
58
- :tracking_companies => {},
59
- :tracking_urls => {}})
60
- else
61
- Response.new(false, "Unable to fetch remote tracking numbers #{order_numbers.inspect}")
62
- end
63
- end
64
-
65
- private
66
-
67
- def request_uri(action, data)
68
- URI.parse "#{@callback_url}/#{action}.#{@format}?#{data.to_param}"
69
- end
70
-
71
- def send_app_request(action, headers, data)
72
- uri = request_uri(action, data)
73
-
74
- logger.info "[" + @name.upcase + " APP] Post #{uri}"
75
-
76
- response = nil
77
- realtime = Benchmark.realtime do
78
- begin
79
- Timeout.timeout(20.seconds) do
80
- response = ssl_get(uri, headers)
81
- end
82
- rescue *(RESCUABLE_CONNECTION_ERRORS) => e
83
- logger.warn "[#{self}] Error while contacting fulfillment service error =\"#{e.message}\""
84
- end
85
- end
86
-
87
- line = "[" + @name.upcase + "APP] Response from #{uri} --> "
88
- line << "#{response} #{"%.4fs" % realtime}"
89
- logger.info line
90
-
91
- response
92
- end
93
-
94
- def parse_response(response, root, type, key, value)
95
- case @format
96
- when 'json'
97
- response_data = ActiveSupport::JSON.decode(response)
98
- return {} unless response_data.is_a?(Hash)
99
- response_data[root.underscore] || response_data
100
- when 'xml'
101
- response_data = {}
102
- document = REXML::Document.new(response)
103
- document.elements[root].each do |node|
104
- if node.name == type
105
- response_data[node.elements[key].text] = node.elements[value].text
106
- end
107
- end
108
- response_data
109
- end
110
-
111
- rescue ActiveSupport::JSON.parse_error, REXML::ParseException
112
- {}
113
- end
114
-
115
- def encode_payload(payload, root)
116
- case @format
117
- when 'json'
118
- {root => payload}.to_json
119
- when 'xml'
120
- payload.to_xml(:root => root)
121
- end
122
- end
123
- end
124
- end
125
- end
@@ -1,338 +0,0 @@
1
- module ActiveMerchant
2
- module Fulfillment
3
- class WebgistixService < Service
4
- SERVICE_URLS = {
5
- :fulfillment => 'https://www.webgistix.com/XML/CreateOrder.asp',
6
- :inventory => 'https://www.webgistix.com/XML/GetInventory.asp',
7
- :tracking => 'https://www.webgistix.com/XML/GetTracking.asp'
8
- }
9
- TEST_URLS = SERVICE_URLS.merge({
10
- :fulfillment => 'https://www.webgistix.com/XML/CreateOrderTest.asp'
11
- })
12
-
13
- SUCCESS, DUPLICATE, FAILURE = 'True', 'Duplicate', 'False'
14
-
15
- SUCCESS_MESSAGE = 'Successfully submitted the order'
16
- FAILURE_MESSAGE = 'Failed to submit the order'
17
- DUPLICATE_MESSAGE = 'This order has already been successfully submitted'
18
-
19
- INVALID_LOGIN = 'Invalid Credentials'
20
- NOT_SHIPPED = 'Not Shipped'
21
-
22
- TRACKING_COMPANIES = %w(UPS FedEx USPS)
23
-
24
- # If a request is detected as a duplicate only the original data will be
25
- # used by Webgistix, and the subsequent responses will have a
26
- # :duplicate parameter set in the params hash.
27
- self.retry_safe = true
28
-
29
- # The first is the label, and the last is the code
30
- def self.shipping_methods
31
- [
32
- ["UPS Ground Shipping", "Ground"],
33
- ["UPS Ground", "Ground"],
34
- ["UPS Standard Shipping (Canada Only)", "Standard"],
35
- ["UPS Standard Shipping (CA & MX Only)", "Standard"],
36
- ["UPS 3-Business Day", "3-Day Select"],
37
- ["UPS 2-Business Day", "2nd Day Air"],
38
- ["UPS 2-Business Day AM", "2nd Day Air AM"],
39
- ["UPS Next Day", "Next Day Air"],
40
- ["UPS Next Day Saver", "Next Day Air Saver"],
41
- ["UPS Next Day Early AM", "Next Day Air Early AM"],
42
- ["UPS Worldwide Express (Next Day)", "Worldwide Express"],
43
- ["UPS Worldwide Expedited (2nd Day)", "Worldwide Expedited"],
44
- ["UPS Worldwide Express Saver", "Worldwide Express Saver"],
45
- ["FedEx Priority Overnight", "FedEx Priority Overnight"],
46
- ["FedEx Standard Overnight", "FedEx Standard Overnight"],
47
- ["FedEx First Overnight", "FedEx First Overnight"],
48
- ["FedEx 2nd Day", "FedEx 2nd Day"],
49
- ["FedEx Express Saver", "FedEx Express Saver"],
50
- ["FedEx International Priority", "FedEx International Priority"],
51
- ["FedEx International Economy", "FedEx International Economy"],
52
- ["FedEx International First", "FedEx International First"],
53
- ["FedEx Ground", "FedEx Ground"],
54
- ["USPS Priority Mail", "Priority Mail"],
55
- ["USPS Priority Mail International", "Priority Mail International"],
56
- ["USPS Priority Mail Small Flat Rate Box", "Priority Mail Small Flat Rate Box"],
57
- ["USPS Priority Mail Medium Flat Rate Box", "Priority Mail Medium Flat Rate Box"],
58
- ["USPS Priority Mail Large Flat Rate Box", "Priority Mail Large Flat Rate Box"],
59
- ["USPS Priority Mail Flat Rate Envelope", "Priority Mail Flat Rate Envelope"],
60
- ["USPS First Class Mail", "First Class"],
61
- ["USPS First Class International", "First Class International"],
62
- ["USPS Express Mail", "Express"],
63
- ["USPS Express Mail International", "Express Mail International"],
64
- ["USPS Parcel Post", "Parcel"],
65
- ["USPS Media Mail", "Media Mail"]
66
- ].inject(ActiveSupport::OrderedHash.new){|h, (k,v)| h[k] = v; h}
67
- end
68
-
69
- # Pass in the login and password for the shipwire account.
70
- # Optionally pass in the :test => true to force test mode
71
- def initialize(options = {})
72
- requires!(options, :login, :password)
73
- super
74
- end
75
-
76
- def fulfill(order_id, shipping_address, line_items, options = {})
77
- requires!(options, :shipping_method)
78
- commit :fulfillment, build_fulfillment_request(order_id, shipping_address, line_items, options)
79
- end
80
-
81
- def fetch_stock_levels(options = {})
82
- commit :inventory, build_inventory_request(options)
83
- end
84
-
85
- def fetch_tracking_data(order_ids, options = {})
86
- commit :tracking, build_tracking_request(order_ids, options)
87
- end
88
-
89
- def valid_credentials?
90
- response = fulfill('', {}, [], :shipping_method => '')
91
- response.message != INVALID_LOGIN
92
- end
93
-
94
- def test_mode?
95
- true
96
- end
97
-
98
- private
99
- #<?xml version="1.0"?>
100
- # <OrderXML>
101
- # <Password>Webgistix</Password>
102
- # <CustomerID>3</CustomerID>
103
- # <Order>
104
- # <ReferenceNumber></ReferenceNumber>
105
- # <Company>Test Company</Company>
106
- # <Name>Joe Smith</Name>
107
- # <Address1>123 Main St.</Address1>
108
- # <Address2></Address2>
109
- # <Address3></Address3>
110
- # <City>Olean</City>
111
- # <State>NY</State>
112
- # <ZipCode>14760</ZipCode>
113
- # <Country>United States</Country>
114
- # <Email>info@webgistix.com</Email>
115
- # <Phone>1-123-456-7890</Phone>
116
- # <ShippingInstructions>Ground</ShippingInstructions>
117
- # <OrderComments>Test Order</OrderComments>
118
- # <Approve>0</Approve>
119
- # <Item>
120
- # <ItemID>testitem</ItemID>
121
- # <ItemQty>2</ItemQty>
122
- # </Item>
123
- # </Order>
124
- # </OrderXML>
125
- def build_fulfillment_request(order_id, shipping_address, line_items, options)
126
- xml = Builder::XmlMarkup.new :indent => 2
127
- xml.instruct!
128
- xml.tag! 'OrderXML' do
129
- add_credentials(xml)
130
- add_order(xml, order_id, shipping_address, line_items, options)
131
- end
132
- xml.target!
133
- end
134
-
135
- #<?xml version="1.0"?>
136
- # <InventoryXML>
137
- # <Password>Webgistix</Password>
138
- # <CustomerID>3</CustomerID>
139
- # </InventoryXML>
140
- def build_inventory_request(options)
141
- xml = Builder::XmlMarkup.new :indent => 2
142
- xml.instruct!
143
- xml.tag! 'InventoryXML' do
144
- add_credentials(xml)
145
- end
146
- end
147
-
148
- #<?xml version="1.0"?>
149
- # <TrackingXML>
150
- # <Password>Webgistix</Password>
151
- # <CustomerID>3</CustomerID>
152
- # <Tracking>
153
- # <Order>AB12345</Order>
154
- # </Tracking>
155
- # <Tracking>
156
- # <Order>XY4567</Order>
157
- # </Tracking>
158
- # </TrackingXML>
159
- def build_tracking_request(order_ids, options)
160
- xml = Builder::XmlMarkup.new :indent => 2
161
- xml.instruct!
162
- xml.tag! 'TrackingXML' do
163
- add_credentials(xml)
164
-
165
- order_ids.each do |o_id|
166
- xml.tag! 'Tracking' do
167
- xml.tag! 'Order', o_id
168
- end
169
- end
170
- end
171
- end
172
-
173
- def add_credentials(xml)
174
- xml.tag! 'CustomerID', @options[:login]
175
- xml.tag! 'Password', @options[:password]
176
- end
177
-
178
- def add_order(xml, order_id, shipping_address, line_items, options)
179
- xml.tag! 'Order' do
180
- xml.tag! 'ReferenceNumber', order_id
181
- xml.tag! 'ShippingInstructions', options[:shipping_method]
182
- xml.tag! 'Approve', 1
183
- xml.tag! 'OrderComments', options[:comment] unless options[:comment].blank?
184
-
185
- add_address(xml, shipping_address, options)
186
-
187
- Array(line_items).each_with_index do |line_item, index|
188
- add_item(xml, line_item, index)
189
- end
190
- end
191
- end
192
-
193
- def add_address(xml, address, options)
194
- xml.tag! 'Name', address[:name]
195
- xml.tag! 'Address1', address[:address1]
196
- xml.tag! 'Address2', address[:address2] unless address[:address2].blank?
197
- xml.tag! 'Address3', address[:address3] unless address[:address3].blank?
198
- xml.tag! 'City', address[:city]
199
- xml.tag! 'State', address[:state]
200
- xml.tag! 'ZipCode', address[:zip]
201
- xml.tag! 'Company', address[:company]
202
-
203
- unless address[:country].blank?
204
- country = Country.find(address[:country])
205
- xml.tag! 'Country', country.name
206
- end
207
-
208
- xml.tag! 'Phone', address[:phone]
209
- xml.tag! 'Email', options[:email] unless options[:email].blank?
210
- end
211
-
212
- def add_item(xml, item, index)
213
- xml.tag! 'Item' do
214
- xml.tag! 'ItemID', item[:sku] unless item[:sku].blank?
215
- xml.tag! 'ItemQty', item[:quantity] unless item[:quantity].blank?
216
- end
217
- end
218
-
219
- def commit(action, request)
220
- url = test? ? TEST_URLS[action] : SERVICE_URLS[action]
221
-
222
- data = ssl_post(url, request,
223
- 'EndPointURL' => url,
224
- 'Content-Type' => 'text/xml; charset="utf-8"'
225
- )
226
-
227
- response = parse_response(action, data)
228
- Response.new(success?(response), message_from(response), response, :test => test?)
229
- end
230
-
231
- def success?(response)
232
- response[:success] == SUCCESS || response[:success] == DUPLICATE
233
- end
234
-
235
- def message_from(response)
236
- if response[:duplicate]
237
- DUPLICATE_MESSAGE
238
- elsif success?(response)
239
- SUCCESS_MESSAGE
240
- elsif response[:error_0] == INVALID_LOGIN
241
- INVALID_LOGIN
242
- else
243
- FAILURE_MESSAGE
244
- end
245
- end
246
-
247
- def parse_response(action, xml)
248
- begin
249
- document = REXML::Document.new("<response>#{xml}</response>")
250
- rescue REXML::ParseException
251
- return {:success => FAILURE}
252
- end
253
-
254
- case action
255
- when :fulfillment
256
- parse_fulfillment_response(document)
257
- when :inventory
258
- parse_inventory_response(document)
259
- when :tracking
260
- parse_tracking_response(document)
261
- else
262
- raise ArgumentError, "Unknown action #{action}"
263
- end
264
- end
265
-
266
- def parse_fulfillment_response(document)
267
- response = parse_errors(document)
268
-
269
- # Check if completed
270
- if completed = REXML::XPath.first(document, '//Completed')
271
- completed.elements.each do |e|
272
- response[e.name.underscore.to_sym] = e.text
273
- end
274
- else
275
- response[:success] = FAILURE
276
- end
277
-
278
- response[:duplicate] = response[:success] == DUPLICATE
279
-
280
- response
281
- end
282
-
283
- def parse_inventory_response(document)
284
- response = parse_errors(document)
285
- response[:stock_levels] = {}
286
-
287
- document.root.each_element('//Item') do |node|
288
- # {ItemID => 'SOME-ID', ItemQty => '101'}
289
- params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
290
-
291
- response[:stock_levels][params['ItemID']] = params['ItemQty'].to_i
292
- end
293
-
294
- response
295
- end
296
-
297
- def parse_tracking_response(document)
298
- response = parse_errors(document)
299
- response[:tracking_numbers] = {}
300
- response[:tracking_companies] = {}
301
- response[:tracking_urls] = {}
302
-
303
- document.root.each_element('//Shipment') do |node|
304
- # {InvoiceNumber => 'SOME-ID', ShipmentTrackingNumber => 'SOME-TRACKING-NUMBER'}
305
- params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
306
-
307
- tracking = params['ShipmentTrackingNumber']
308
-
309
- unless tracking == NOT_SHIPPED
310
- response[:tracking_numbers][params['InvoiceNumber']] ||= []
311
- response[:tracking_numbers][params['InvoiceNumber']] << tracking
312
- end
313
-
314
- company = params['Method'].split[0] if params['Method']
315
- if TRACKING_COMPANIES.include? company
316
- response[:tracking_companies][params['InvoiceNumber']] ||= []
317
- response[:tracking_companies][params['InvoiceNumber']] << company
318
- end
319
- end
320
-
321
- response
322
- end
323
-
324
- def parse_errors(document)
325
- response = {}
326
-
327
- REXML::XPath.match(document, "//Errors/Error").to_a.each_with_index do |e, i|
328
- response["error_#{i}".to_sym] = e.text
329
- end
330
-
331
- response[:success] = response.empty? ? SUCCESS : FAILURE
332
- response
333
- end
334
- end
335
- end
336
- end
337
-
338
-