active_fulfillment 2.0.2 → 2.1.0

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