active_fulfillment 2.0.2 → 2.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 143a9457ed3cbe9faff1de7fe049ba1061421d5d
4
- data.tar.gz: fd48163baf2ed6c6388a25d2af6812630a75cf6b
3
+ metadata.gz: cba1390bb08c4c010debba62180ac2f00b2c1d33
4
+ data.tar.gz: e2b321fd688279d43ddc20127de4a9640c78cc1e
5
5
  SHA512:
6
- metadata.gz: ef2ab9127ce387ada96e6dcb90c43b660957a3c0d5fa9da36c4c5d66f6518bdf21c48fcf811b6e1da9e269c19528053db8c011306558694abe0d72e6f821daa7
7
- data.tar.gz: 37ce0735dcfaff356284a72dca0d7b8aadea81b42630f4fb5d5a517e99111993b0a41ee5706c4dccaefa8acfba44631937604535240accbba41261c96699f946
6
+ metadata.gz: 2915b119a9aa6b85050b6db4942680a773a7a9b992c5850e8d79836a8abfb9dde3396d7e8c1f91ca08a62b5ab782973f09586a1c4d8b05d1a8404e03ab0b2fc9
7
+ data.tar.gz: 05b69f636a0b59eefe4ae75f677cfc7ed6f5fe9cf567bfb60ed7cd14763d0d749df31ce5b2a8bf107f6751b186334ba5b62cfe183f8660f10c2a5bf3e23c65eb
data/CHANGELOG CHANGED
@@ -1,5 +1,9 @@
1
1
  = ActiveFulfillment CHANGELOG
2
2
 
3
+ == Version 2.1.0
4
+
5
+ * Added fetch_tracking_data methods which returns tracking_companies and tracking_urls if available in addition to tracking_numbers for each service
6
+
3
7
  == Version 2.0.0 (Jan 5, 2013)
4
8
 
5
9
  * API Change on tracking numbers, returns array instead of single string [csaunders]
@@ -4,18 +4,18 @@ module ActiveMerchant
4
4
 
5
5
  include RequiresParameters
6
6
  include PostsData
7
-
7
+
8
8
  def initialize(options = {})
9
9
  check_test_mode(options)
10
-
10
+
11
11
  @options = {}
12
12
  @options.update(options)
13
13
  end
14
-
14
+
15
15
  def test_mode?
16
16
  false
17
17
  end
18
-
18
+
19
19
  def test?
20
20
  @options[:test] || Base.mode == :test
21
21
  end
@@ -34,6 +34,13 @@ module ActiveMerchant
34
34
  end
35
35
 
36
36
  def fetch_tracking_numbers(order_ids, options = {})
37
+ response = fetch_tracking_data(order_ids, options)
38
+ response.params.delete('tracking_companies')
39
+ response.params.delete('tracking_urls')
40
+ response
41
+ end
42
+
43
+ def fetch_tracking_data(order_ids, options = {})
37
44
  raise NotImplementedError.new("Subclasses must implement")
38
45
  end
39
46
 
@@ -44,7 +51,7 @@ module ActiveMerchant
44
51
  def test_mode?
45
52
  raise NotImplementedError.new("Subclasses must implement")
46
53
  end
47
-
54
+
48
55
  private
49
56
  def check_test_mode(options)
50
57
  if options[:test] and not test_mode?
@@ -53,4 +60,4 @@ module ActiveMerchant
53
60
  end
54
61
  end
55
62
  end
56
- end
63
+ end
@@ -13,16 +13,16 @@ module ActiveMerchant
13
13
  :inventory => {
14
14
  :url => 'https://fba-inventory.amazonaws.com',
15
15
  :xmlns => 'http://fba-inventory.amazonaws.com/doc/2009-07-31/',
16
- :version => '2009-07-31'
16
+ :version => '2009-07-31'
17
17
  }
18
18
  }
19
-
20
- SUCCESS, FAILURE, ERROR = 'Accepted', 'Failure', 'Error'
19
+
20
+ SUCCESS, FAILURE, ERROR = 'Accepted', 'Failure', 'Error'
21
21
  MESSAGES = {
22
22
  :status => {
23
23
  'Accepted' => 'Success',
24
24
  'Failure' => 'Failed',
25
- 'Error' => 'An error occurred'
25
+ 'Error' => 'An error occurred'
26
26
  },
27
27
  :create => {
28
28
  'Accepted' => 'Successfully submitted the order',
@@ -33,10 +33,10 @@ module ActiveMerchant
33
33
  'Accepted' => 'Successfully submitted request',
34
34
  'Failure' => 'Failed to submit request',
35
35
  'Error' => 'An error occurred while submitting request'
36
-
36
+
37
37
  }
38
38
  }
39
-
39
+
40
40
  ENV_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
41
41
  'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
42
42
  'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
@@ -48,8 +48,8 @@ module ActiveMerchant
48
48
  "xmlns:aws" => "http://security.amazonaws.com/doc/2007-01-01/"
49
49
  }
50
50
 
51
- @@digest = OpenSSL::Digest::Digest.new("sha1")
52
-
51
+ @@digest = OpenSSL::Digest.new("sha1")
52
+
53
53
  OPERATIONS = {
54
54
  :outbound => {
55
55
  :status => 'GetServiceStatus',
@@ -63,37 +63,37 @@ module ActiveMerchant
63
63
  :list_next => 'ListUpdatedInventorySupplyByNextToken'
64
64
  }
65
65
  }
66
-
66
+
67
67
  # The first is the label, and the last is the code
68
68
  # Standard: 3-5 business days
69
69
  # Expedited: 2 business days
70
70
  # Priority: 1 business day
71
71
  def self.shipping_methods
72
- [
72
+ [
73
73
  [ 'Standard Shipping', 'Standard' ],
74
74
  [ 'Expedited Shipping', 'Expedited' ],
75
75
  [ 'Priority Shipping', 'Priority' ]
76
76
  ].inject(ActiveSupport::OrderedHash.new){|h, (k,v)| h[k] = v; h}
77
77
  end
78
-
78
+
79
79
  def self.sign(aws_secret_access_key, auth_string)
80
80
  Base64.encode64(OpenSSL::HMAC.digest(@@digest, aws_secret_access_key, auth_string)).strip
81
81
  end
82
-
82
+
83
83
  def initialize(options = {})
84
84
  requires!(options, :login, :password)
85
85
  super
86
86
  end
87
-
87
+
88
88
  def status
89
89
  commit :outbound, :status, build_status_request
90
90
  end
91
91
 
92
- def fulfill(order_id, shipping_address, line_items, options = {})
92
+ def fulfill(order_id, shipping_address, line_items, options = {})
93
93
  requires!(options, :order_date, :comment, :shipping_method)
94
94
  commit :outbound, :create, build_fulfillment_request(order_id, shipping_address, line_items, options)
95
95
  end
96
-
96
+
97
97
  def fetch_current_orders
98
98
  commit :outbound, :list, build_get_current_fulfillment_orders_request
99
99
  end
@@ -110,35 +110,30 @@ module ActiveMerchant
110
110
  next_page.stock_levels.merge!(response.stock_levels)
111
111
  response = next_page
112
112
  end
113
-
113
+
114
114
  response
115
115
  end
116
116
  end
117
117
 
118
- def fetch_tracking_numbers(order_ids, options = {})
118
+ def fetch_tracking_data(order_ids, options = {})
119
119
  order_ids.inject(nil) do |previous, o_id|
120
120
  response = commit :outbound, :tracking, build_tracking_request(o_id, options)
121
+ return response unless response.success?
121
122
 
122
- if !response.success?
123
- if response.faultstring =~ /Reason: requested order not found./
124
- response = Response.new(true, nil, {
125
- :status => SUCCESS,
126
- :tracking_numbers => {}
127
- })
128
- else
129
- return response
130
- end
123
+ if previous
124
+ response.tracking_numbers.merge!(previous.tracking_numbers)
125
+ response.tracking_companies.merge!(previous.tracking_companies)
126
+ response.tracking_urls.merge!(previous.tracking_urls)
131
127
  end
132
128
 
133
- response.tracking_numbers.merge!(previous.tracking_numbers) if previous
134
129
  response
135
130
  end
136
131
  end
137
-
132
+
138
133
  def valid_credentials?
139
134
  status.success?
140
135
  end
141
-
136
+
142
137
  def test_mode?
143
138
  false
144
139
  end
@@ -157,14 +152,14 @@ module ActiveMerchant
157
152
  end
158
153
  xml.target!
159
154
  end
160
-
155
+
161
156
  def build_status_request
162
157
  request = OPERATIONS[:outbound][:status]
163
158
  soap_request(request) do |xml|
164
159
  xml.tag! request, { 'xmlns' => SERVICES[:outbound][:xmlns] }
165
160
  end
166
161
  end
167
-
162
+
168
163
  def build_get_current_fulfillment_orders_request
169
164
  request = OPERATIONS[:outbound][:list]
170
165
  soap_request(request) do |xml|
@@ -184,7 +179,7 @@ module ActiveMerchant
184
179
  xml.tag! "DisplayableOrderDateTime", options[:order_date].strftime("%Y-%m-%dT%H:%M:%SZ")
185
180
  xml.tag! "DisplayableOrderComment", options[:comment]
186
181
  xml.tag! "ShippingSpeedCategory", options[:shipping_method]
187
-
182
+
188
183
  add_address(xml, shipping_address)
189
184
  add_items(xml, line_items)
190
185
  end
@@ -236,13 +231,13 @@ module ActiveMerchant
236
231
  login = @options[:login]
237
232
  timestamp = "#{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S")}Z"
238
233
  signature = self.class.sign(@options[:password], "#{request}#{timestamp}")
239
-
234
+
240
235
  xml.tag! 'aws:AWSAccessKeyId', login, AWS_SECURITY_ATTRIBUTES
241
236
  xml.tag! 'aws:Signature', signature, AWS_SECURITY_ATTRIBUTES
242
237
  xml.tag! 'aws:Timestamp', timestamp, AWS_SECURITY_ATTRIBUTES
243
238
  end
244
-
245
- def add_items(xml, line_items)
239
+
240
+ def add_items(xml, line_items)
246
241
  Array(line_items).each_with_index do |item, index|
247
242
  xml.tag! 'Item' do
248
243
  xml.tag! 'MerchantSKU', item[:sku]
@@ -253,9 +248,9 @@ module ActiveMerchant
253
248
  end
254
249
  end
255
250
  end
256
-
251
+
257
252
  def add_address(xml, address)
258
- xml.tag! 'DestinationAddress' do
253
+ xml.tag! 'DestinationAddress' do
259
254
  xml.tag! 'Name', address[:name]
260
255
  xml.tag! 'Line1', address[:address1]
261
256
  xml.tag! 'Line2', address[:address2] unless address[:address2].blank?
@@ -267,26 +262,34 @@ module ActiveMerchant
267
262
  xml.tag! 'PhoneNumber', address[:phone] unless address[:phone].blank?
268
263
  end
269
264
  end
270
-
265
+
271
266
  def commit(service, op, body)
272
267
  data = ssl_post(SERVICES[service][:url], body, 'Content-Type' => 'application/soap+xml; charset=utf-8')
273
- response = parse_response(service, op, data)
268
+ response = parse_response(service, op, data)
274
269
  Response.new(success?(response), message_from(response), response)
275
- rescue ActiveMerchant::ResponseError => e
270
+ rescue ActiveMerchant::ResponseError => e
271
+ handle_error(e)
272
+ end
273
+
274
+ def handle_error(e)
276
275
  response = parse_error(e.response)
277
- Response.new(false, message_from(response), response)
276
+ if response.fetch(:faultstring, "") =~ /Reason: requested order not found./
277
+ Response.new(true, nil, {:status => SUCCESS, :tracking_numbers => {}, :tracking_companies => {}, :tracking_urls => {}})
278
+ else
279
+ Response.new(false, message_from(response), response)
280
+ end
278
281
  end
279
-
282
+
280
283
  def success?(response)
281
284
  response[:response_status] == SUCCESS
282
285
  end
283
-
286
+
284
287
  def message_from(response)
285
288
  response[:response_comment]
286
289
  end
287
-
290
+
288
291
  def parse_response(service, op, xml)
289
- begin
292
+ begin
290
293
  document = REXML::Document.new(xml)
291
294
  rescue REXML::ParseException
292
295
  return {:success => FAILURE}
@@ -306,12 +309,12 @@ module ActiveMerchant
306
309
  raise ArgumentError, "Unknown service #{service}"
307
310
  end
308
311
  end
309
-
312
+
310
313
  def parse_fulfillment_response(op, document)
311
314
  response = {}
312
315
  action = OPERATIONS[:outbound][op]
313
316
  node = REXML::XPath.first(document, "//ns1:#{action}Response")
314
-
317
+
315
318
  response[:response_status] = SUCCESS
316
319
  response[:response_comment] = MESSAGES[op][SUCCESS]
317
320
  response
@@ -334,10 +337,12 @@ module ActiveMerchant
334
337
  response[:response_status] = SUCCESS
335
338
  response
336
339
  end
337
-
340
+
338
341
  def parse_tracking_response(document)
339
342
  response = {}
340
343
  response[:tracking_numbers] = {}
344
+ response[:tracking_companies] = {}
345
+ response[:tracking_urls] = {}
341
346
 
342
347
  track_node = REXML::XPath.first(document, '//ns1:FulfillmentShipmentPackage/ns1:TrackingNumber')
343
348
  if track_node
@@ -345,10 +350,16 @@ module ActiveMerchant
345
350
  response[:tracking_numbers][id_node.text] = [track_node.text]
346
351
  end
347
352
 
353
+ company_node = REXML::XPath.first(document, '//ns1:FulfillmentShipmentPackage/ns1:CarrierCode')
354
+ if company_node
355
+ id_node = REXML::XPath.first(document, '//ns1:MerchantFulfillmentOrderId')
356
+ response[:tracking_companies][id_node.text] = [company_node.text]
357
+ end
358
+
348
359
  response[:response_status] = SUCCESS
349
360
  response
350
361
  end
351
-
362
+
352
363
  def parse_error(http_response)
353
364
  response = {}
354
365
  response[:http_code] = http_response.code
@@ -361,7 +372,7 @@ module ActiveMerchant
361
372
  failed_node = node.find_first_recursive {|sib| sib.name == "Fault" }
362
373
  faultcode_node = node.find_first_recursive {|sib| sib.name == "faultcode" }
363
374
  faultstring_node = node.find_first_recursive {|sib| sib.name == "faultstring" }
364
-
375
+
365
376
  response[:response_status] = FAILURE
366
377
  response[:faultcode] = faultcode_node ? faultcode_node.text : ""
367
378
  response[:faultstring] = faultstring_node ? faultstring_node.text : ""
@@ -373,6 +384,6 @@ module ActiveMerchant
373
384
  response[:response_comment] = "#{response[:http_code]}: #{response[:http_message]}"
374
385
  response
375
386
  end
376
- end
387
+ end
377
388
  end
378
389
  end
@@ -20,7 +20,7 @@ module ActiveMerchant
20
20
  :status => {
21
21
  'Accepted' => 'Success',
22
22
  'Failure' => 'Failed',
23
- 'Error' => 'An error occurred'
23
+ 'Error' => 'An error occurred'
24
24
  },
25
25
  :create => {
26
26
  'Accepted' => 'Successfully submitted the order',
@@ -31,7 +31,7 @@ module ActiveMerchant
31
31
  'Accepted' => 'Successfully submitted request',
32
32
  'Failure' => 'Failed to submit request',
33
33
  'Error' => 'An error occurred while submitting request'
34
-
34
+
35
35
  }
36
36
  }
37
37
 
@@ -98,7 +98,7 @@ module ActiveMerchant
98
98
  # Expedited: 2 business days
99
99
  # Priority: 1 business day
100
100
  def self.shipping_methods
101
- [
101
+ [
102
102
  [ 'Standard Shipping', 'Standard' ],
103
103
  [ 'Expedited Shipping', 'Expedited' ],
104
104
  [ 'Priority Shipping', 'Priority' ]
@@ -146,23 +146,18 @@ module ActiveMerchant
146
146
  response
147
147
  end
148
148
 
149
- def fetch_tracking_numbers(order_ids, options = {})
149
+ def fetch_tracking_data(order_ids, options = {})
150
150
  order_ids.reduce(nil) do |previous, order_id|
151
- response = commit :post, :outbound, :tracking, build_tracking_request(order_id, options)
152
-
153
- if !response.success?
154
- if response.faultstring.match(/^Requested order \'.+\' not found$/)
155
- response = Response.new(true, nil, {
156
- :status => SUCCESS,
157
- :tracking_numbers => {}
158
- })
159
- else
160
- return response
161
- end
162
- end
151
+ response = commit :post, :outbound, :tracking, build_tracking_request(order_id, options)
152
+ return response if !response.success?
163
153
 
164
- response.tracking_numbers.merge!(previous.tracking_numbers) if previous
165
- response
154
+ if previous
155
+ response.tracking_numbers.merge!(previous.tracking_numbers)
156
+ response.tracking_companies.merge!(previous.tracking_companies)
157
+ response.tracking_urls.merge!(previous.tracking_urls)
158
+ end
159
+
160
+ response
166
161
  end
167
162
  end
168
163
 
@@ -188,8 +183,16 @@ module ActiveMerchant
188
183
  response = parse_response(service, op, data)
189
184
  Response.new(success?(response), message_from(response), response)
190
185
  rescue ActiveMerchant::ResponseError => e
186
+ handle_error(e)
187
+ end
188
+
189
+ def handle_error(e)
191
190
  response = parse_error(e.response)
192
- Response.new(false, message_from(response), response)
191
+ if response.fetch(:faultstring, "").match(/^Requested order \'.+\' not found$/)
192
+ Response.new(true, nil, {:status => SUCCESS, :tracking_numbers => {}, :tracking_companies => {}, :tracking_urls => {}})
193
+ else
194
+ Response.new(false, message_from(response), response)
195
+ end
193
196
  end
194
197
 
195
198
  def success?(response)
@@ -227,6 +230,8 @@ module ActiveMerchant
227
230
  def parse_tracking_response(document)
228
231
  response = {}
229
232
  response[:tracking_numbers] = {}
233
+ response[:tracking_companies] = {}
234
+ response[:tracking_urls] = {}
230
235
 
231
236
  tracking_numbers = REXML::XPath.match(document, "//FulfillmentShipmentPackage/member/TrackingNumber")
232
237
  if tracking_numbers.present?
@@ -234,6 +239,12 @@ module ActiveMerchant
234
239
  response[:tracking_numbers][order_id] = tracking_numbers.map{ |t| t.text.strip }
235
240
  end
236
241
 
242
+ tracking_companies = REXML::XPath.match(document, "//FulfillmentShipmentPackage/member/CarrierCode")
243
+ if tracking_companies.present?
244
+ order_id = REXML::XPath.first(document, "//FulfillmentOrder/SellerFulfillmentOrderId").text.strip
245
+ response[:tracking_companies][order_id] = tracking_companies.map{ |t| t.text.strip }
246
+ end
247
+
237
248
  response[:response_status] = SUCCESS
238
249
  response
239
250
  end
@@ -251,10 +262,10 @@ module ActiveMerchant
251
262
 
252
263
  response[:stock_levels][params['SellerSKU']] = params['InStockSupplyQuantity'].to_i
253
264
  end
254
-
265
+
255
266
  next_token = REXML::XPath.first(document, '//NextToken')
256
267
  response[:next_token] = next_token ? next_token.text : nil
257
-
268
+
258
269
  response[:response_status] = SUCCESS
259
270
  response
260
271
  end
@@ -312,7 +323,7 @@ module ActiveMerchant
312
323
  end
313
324
 
314
325
  def md5_content(content)
315
- Base64.encode64(OpenSSL::Digest::Digest.new('md5', content).digest).chomp
326
+ Base64.encode64(OpenSSL::Digest.new('md5', content).digest).chomp
316
327
  end
317
328
 
318
329
  def build_query(query_params)
@@ -347,7 +358,7 @@ module ActiveMerchant
347
358
  :ShippingSpeedCategory => options[:shipping_method]
348
359
  }
349
360
  params[:DisplayableOrderComment] = options[:comment] if options[:comment]
350
-
361
+
351
362
  request = build_basic_api_query(params.merge(options))
352
363
  request = request.merge build_address(shipping_address)
353
364
  request = request.merge build_items(line_items)
@@ -400,6 +411,7 @@ module ActiveMerchant
400
411
 
401
412
  def build_address(address)
402
413
  requires!(address, :name, :address1, :city, :state, :country, :zip)
414
+ address[:zip].upcase!
403
415
  ary = address.map{ |key, value| [LOOKUPS[:destination_address][key], value] if LOOKUPS[:destination_address].include?(key) && value.present? }
404
416
  Hash[ary.compact]
405
417
  end
@@ -4,9 +4,9 @@ module ActiveMerchant
4
4
  module Fulfillment
5
5
  class ShipwireService < Service
6
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'
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
10
  }
11
11
 
12
12
  SCHEMA_URLS = { :fulfillment => 'http://www.shipwire.com/exec/download/OrderList.dtd',
@@ -57,7 +57,7 @@ module ActiveMerchant
57
57
  commit :inventory, build_inventory_request(options)
58
58
  end
59
59
 
60
- def fetch_tracking_numbers(order_ids)
60
+ def fetch_tracking_data(order_ids, options = {})
61
61
  commit :tracking, build_tracking_request(order_ids)
62
62
  end
63
63
 
@@ -225,8 +225,10 @@ module ActiveMerchant
225
225
  end
226
226
 
227
227
  def parse_tracking_response(xml)
228
- response = Hash.new { |hash, key| hash[key] = {} }
228
+ response = {}
229
229
  response[:tracking_numbers] = {}
230
+ response[:tracking_companies] = {}
231
+ response[:tracking_urls] = {}
230
232
 
231
233
  document = REXML::Document.new(xml)
232
234
  document.root.elements.each do |node|
@@ -236,7 +238,7 @@ module ActiveMerchant
236
238
  response[:tracking_numbers][node.attributes['id']] = [tracking_number]
237
239
 
238
240
  tracking_company = node.elements['TrackingNumber'].attributes['carrier']
239
- response[:tracking_company][node.attributes['id']] = tracking_company.strip if tracking_company
241
+ response[:tracking_companies][node.attributes['id']] = tracking_company.strip if tracking_company
240
242
 
241
243
  tracking_url = node.elements['TrackingNumber'].attributes['href']
242
244
  response[:tracking_urls][node.attributes['id']] = [tracking_url.strip] if tracking_url