active_fulfillment 2.1.9 → 3.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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
-