active_fulfillment 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/active_fulfillment.rb +4 -18
- data/lib/active_fulfillment/fulfillment/services/amazon_mws.rb +438 -0
- data/lib/active_fulfillment/fulfillment/services/shipwire.rb +39 -34
- data/lib/active_fulfillment/fulfillment/services/webgistix.rb +5 -1
- data/lib/active_fulfillment/fulfillment/version.rb +6 -0
- data/test/fixtures/xml/amazon_mws/fulfillment_get_fulfillment_order.xml +114 -0
- data/test/fixtures/xml/amazon_mws/fulfillment_get_fulfillment_order_2.xml +90 -0
- data/test/fixtures/xml/amazon_mws/fulfillment_get_fullfillment_order_with_multiple_tracking_numbers.xml +121 -0
- data/test/fixtures/xml/amazon_mws/fulfillment_list_all_fulfillment_orders.xml +70 -0
- data/test/fixtures/xml/amazon_mws/inventory_list_inventory_item_supply.xml +32 -0
- data/test/fixtures/xml/amazon_mws/inventory_list_inventory_supply.xml +75 -0
- data/test/fixtures/xml/amazon_mws/inventory_list_inventory_supply_by_next_token.xml +38 -0
- data/test/fixtures/xml/amazon_mws/tracking_response_error.xml +9 -0
- data/test/fixtures/xml/amazon_mws/tracking_response_not_found.xml +9 -0
- data/test/fixtures/xml/shipwire/fulfillment_failure_response.xml +7 -0
- data/test/fixtures/xml/shipwire/invalid_login_response.xml +7 -0
- data/test/fixtures/xml/shipwire/inventory_get_response.xml +44 -0
- data/test/fixtures/xml/shipwire/successful_empty_tracking_response.xml +8 -0
- data/test/fixtures/xml/shipwire/successful_live_tracking_response.xml +53 -0
- data/test/fixtures/xml/shipwire/successful_tracking_response.xml +16 -0
- data/test/fixtures/xml/shipwire/successful_tracking_response_with_tracking_urls.xml +31 -0
- data/test/fixtures/xml/webgistix/multiple_tracking_response.xml +21 -0
- data/test/remote/amazon_mws_test.rb +114 -0
- data/test/unit/services/amazon_mws_test.rb +357 -0
- data/test/unit/services/shipwire_test.rb +20 -95
- data/test/unit/services/webgistix_test.rb +12 -1
- metadata +57 -11
- data/Rakefile +0 -36
- data/VERSION +0 -1
- data/active_fulfillment.gemspec +0 -70
- data/init.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 143a9457ed3cbe9faff1de7fe049ba1061421d5d
|
4
|
+
data.tar.gz: fd48163baf2ed6c6388a25d2af6812630a75cf6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef2ab9127ce387ada96e6dcb90c43b660957a3c0d5fa9da36c4c5d66f6518bdf21c48fcf811b6e1da9e269c19528053db8c011306558694abe0d72e6f821daa7
|
7
|
+
data.tar.gz: 37ce0735dcfaff356284a72dca0d7b8aadea81b42630f4fb5d5a517e99111993b0a41ee5706c4dccaefa8acfba44631937604535240accbba41261c96699f946
|
data/lib/active_fulfillment.rb
CHANGED
@@ -20,17 +20,10 @@
|
|
20
20
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
|
-
|
24
|
-
$:.unshift File.dirname(__FILE__)
|
25
|
-
|
26
|
-
begin
|
27
|
-
require 'active_support'
|
28
|
-
rescue LoadError
|
29
|
-
require 'rubygems'
|
30
|
-
require 'active_support'
|
31
|
-
end
|
32
23
|
|
24
|
+
require 'active_support'
|
33
25
|
require 'active_support/core_ext/class/attribute'
|
26
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
34
27
|
require 'active_support/core_ext/class/delegating_attributes'
|
35
28
|
require 'active_support/core_ext/time/calculations'
|
36
29
|
require 'active_support/core_ext/date/calculations'
|
@@ -41,21 +34,14 @@ begin
|
|
41
34
|
rescue LoadError
|
42
35
|
end
|
43
36
|
|
44
|
-
|
45
|
-
require 'builder'
|
46
|
-
rescue LoadError
|
47
|
-
require 'rubygems'
|
48
|
-
require_gem 'builder'
|
49
|
-
end
|
50
|
-
|
51
|
-
|
37
|
+
require 'builder'
|
52
38
|
require 'cgi'
|
53
39
|
require 'net/https'
|
54
40
|
require 'rexml/document'
|
55
41
|
require 'active_utils'
|
56
42
|
|
43
|
+
require 'active_fulfillment/fulfillment/version'
|
57
44
|
require 'active_fulfillment/fulfillment/base'
|
58
45
|
require 'active_fulfillment/fulfillment/response'
|
59
46
|
require 'active_fulfillment/fulfillment/service'
|
60
47
|
require 'active_fulfillment/fulfillment/services'
|
61
|
-
|
@@ -0,0 +1,438 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'time'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module ActiveMerchant
|
6
|
+
module Fulfillment
|
7
|
+
class AmazonMarketplaceWebService < Service
|
8
|
+
|
9
|
+
APPLICATION_IDENTIFIER = "active_merchant_mws/0.01 (Language=ruby)"
|
10
|
+
|
11
|
+
REGISTRATION_URI = URI.parse("https://sellercentral.amazon.com/gp/mws/registration/register.html")
|
12
|
+
|
13
|
+
SIGNATURE_VERSION = 2
|
14
|
+
SIGNATURE_METHOD = "SHA256"
|
15
|
+
VERSION = "2010-10-01"
|
16
|
+
|
17
|
+
SUCCESS, FAILURE, ERROR = 'Accepted', 'Failure', 'Error'
|
18
|
+
|
19
|
+
MESSAGES = {
|
20
|
+
:status => {
|
21
|
+
'Accepted' => 'Success',
|
22
|
+
'Failure' => 'Failed',
|
23
|
+
'Error' => 'An error occurred'
|
24
|
+
},
|
25
|
+
:create => {
|
26
|
+
'Accepted' => 'Successfully submitted the order',
|
27
|
+
'Failure' => 'Failed to submit the order',
|
28
|
+
'Error' => 'An error occurred while submitting the order'
|
29
|
+
},
|
30
|
+
:list => {
|
31
|
+
'Accepted' => 'Successfully submitted request',
|
32
|
+
'Failure' => 'Failed to submit request',
|
33
|
+
'Error' => 'An error occurred while submitting request'
|
34
|
+
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
ENDPOINTS = {
|
39
|
+
:ca => 'mws.amazonservices.ca',
|
40
|
+
:cn => 'mws.amazonservices.com.cn',
|
41
|
+
:de => 'mws-eu.amazonservices.ca',
|
42
|
+
:es => 'mws-eu.amazonservices.ca',
|
43
|
+
:fr => 'mws-eu.amazonservices.ca',
|
44
|
+
:it => 'mws-eu.amazonservices.ca',
|
45
|
+
:jp => 'mws.amazonservices.jp',
|
46
|
+
:uk => 'mws-eu.amazonservices.ca',
|
47
|
+
:us => 'mws.amazonservices.com'
|
48
|
+
}
|
49
|
+
|
50
|
+
LOOKUPS = {
|
51
|
+
:destination_address => {
|
52
|
+
:name => "DestinationAddress.Name",
|
53
|
+
:address1 => "DestinationAddress.Line1",
|
54
|
+
:address2 => "DestinationAddress.Line2",
|
55
|
+
:city => "DestinationAddress.City",
|
56
|
+
:state => "DestinationAddress.StateOrProvinceCode",
|
57
|
+
:country => "DestinationAddress.CountryCode",
|
58
|
+
:zip => "DestinationAddress.PostalCode",
|
59
|
+
:phone => "DestinationAddress.PhoneNumber"
|
60
|
+
},
|
61
|
+
:line_items => {
|
62
|
+
:comment => "Items.member.%d.DisplayableComment",
|
63
|
+
:gift_message => "Items.member.%d.GiftMessage",
|
64
|
+
:currency_code => "Items.member.%d.PerUnitDeclaredValue.CurrencyCode",
|
65
|
+
:value => "Items.member.%d.PerUnitDeclaredValue.Value",
|
66
|
+
:quantity => "Items.member.%d.Quantity",
|
67
|
+
:order_id => "Items.member.%d.SellerFulfillmentOrderItemId",
|
68
|
+
:sku => "Items.member.%d.SellerSKU",
|
69
|
+
:network_sku => "Items.member.%d.FulfillmentNetworkSKU",
|
70
|
+
:item_disposition => "Items.member.%d.OrderItemDisposition",
|
71
|
+
},
|
72
|
+
:list_inventory => {
|
73
|
+
:sku => "SellerSkus.member.%d"
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
ACTIONS = {
|
78
|
+
:outbound => "FulfillmentOutboundShipment",
|
79
|
+
:inventory => "FulfillmentInventory"
|
80
|
+
}
|
81
|
+
|
82
|
+
OPERATIONS = {
|
83
|
+
:outbound => {
|
84
|
+
:status => 'GetServiceStatus',
|
85
|
+
:create => 'CreateFulfillmentOrder',
|
86
|
+
:list => 'ListAllFulfillmentOrders',
|
87
|
+
:tracking => 'GetFulfillmentOrder'
|
88
|
+
},
|
89
|
+
:inventory => {
|
90
|
+
:get => 'ListInventorySupply',
|
91
|
+
:list => 'ListInventorySupply',
|
92
|
+
:list_next => 'ListInventorySupplyByNextToken'
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
# The first is the label, and the last is the code
|
97
|
+
# Standard: 3-5 business days
|
98
|
+
# Expedited: 2 business days
|
99
|
+
# Priority: 1 business day
|
100
|
+
def self.shipping_methods
|
101
|
+
[
|
102
|
+
[ 'Standard Shipping', 'Standard' ],
|
103
|
+
[ 'Expedited Shipping', 'Expedited' ],
|
104
|
+
[ 'Priority Shipping', 'Priority' ]
|
105
|
+
].inject(ActiveSupport::OrderedHash.new){|h, (k,v)| h[k] = v; h}
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(options = {})
|
109
|
+
requires!(options, :login, :password)
|
110
|
+
@seller_id = options[:seller_id]
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
def seller_id=(seller_id)
|
115
|
+
@seller_id = seller_id
|
116
|
+
end
|
117
|
+
|
118
|
+
def endpoint
|
119
|
+
ENDPOINTS[@options[:endpoint] || :us]
|
120
|
+
end
|
121
|
+
|
122
|
+
def fulfill(order_id, shipping_address, line_items, options = {})
|
123
|
+
requires!(options, :order_date, :shipping_method)
|
124
|
+
commit :post, :outbound, :create, build_fulfillment_request(order_id, shipping_address, line_items, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
def status
|
128
|
+
commit :post, :outbound, :status, build_status_request
|
129
|
+
end
|
130
|
+
|
131
|
+
def fetch_current_orders
|
132
|
+
commit :post, :outbound, :status, build_get_current_fulfillment_orders_request
|
133
|
+
end
|
134
|
+
|
135
|
+
def fetch_stock_levels(options = {})
|
136
|
+
options[:skus] = [options.delete(:sku)] if options.include?(:sku)
|
137
|
+
response = commit :post, :inventory, :list, build_inventory_list_request(options)
|
138
|
+
|
139
|
+
while token = response.params['next_token'] do
|
140
|
+
next_page = commit :post, :inventory, :list_next, build_next_inventory_list_request(token)
|
141
|
+
|
142
|
+
next_page.stock_levels.merge!(response.stock_levels)
|
143
|
+
response = next_page
|
144
|
+
end
|
145
|
+
|
146
|
+
response
|
147
|
+
end
|
148
|
+
|
149
|
+
def fetch_tracking_numbers(order_ids, options = {})
|
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
|
163
|
+
|
164
|
+
response.tracking_numbers.merge!(previous.tracking_numbers) if previous
|
165
|
+
response
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def valid_credentials?
|
170
|
+
fetch_stock_levels.success?
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_mode?
|
174
|
+
false
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_full_query(verb, uri, params)
|
178
|
+
signature = sign(verb, uri, params)
|
179
|
+
build_query(params) + "&Signature=#{signature}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def commit(verb, service, op, params)
|
183
|
+
uri = URI.parse("https://#{endpoint}/#{ACTIONS[service]}/#{VERSION}")
|
184
|
+
query = build_full_query(verb, uri, params)
|
185
|
+
headers = build_headers(query)
|
186
|
+
|
187
|
+
data = ssl_post(uri.to_s, query, headers)
|
188
|
+
response = parse_response(service, op, data)
|
189
|
+
Response.new(success?(response), message_from(response), response)
|
190
|
+
rescue ActiveMerchant::ResponseError => e
|
191
|
+
response = parse_error(e.response)
|
192
|
+
Response.new(false, message_from(response), response)
|
193
|
+
end
|
194
|
+
|
195
|
+
def success?(response)
|
196
|
+
response[:response_status] == SUCCESS
|
197
|
+
end
|
198
|
+
|
199
|
+
def message_from(response)
|
200
|
+
response[:response_message]
|
201
|
+
end
|
202
|
+
|
203
|
+
## PARSING
|
204
|
+
|
205
|
+
def parse_response(service, op, xml)
|
206
|
+
begin
|
207
|
+
document = REXML::Document.new(xml)
|
208
|
+
rescue REXML::ParseException
|
209
|
+
return { :success => FAILURE }
|
210
|
+
end
|
211
|
+
|
212
|
+
case service
|
213
|
+
when :outbound
|
214
|
+
case op
|
215
|
+
when :tracking
|
216
|
+
parse_tracking_response(document)
|
217
|
+
else
|
218
|
+
parse_fulfillment_response(op, document)
|
219
|
+
end
|
220
|
+
when :inventory
|
221
|
+
parse_inventory_response(document)
|
222
|
+
else
|
223
|
+
raise ArgumentError, "Unknown service #{service}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_tracking_response(document)
|
228
|
+
response = {}
|
229
|
+
response[:tracking_numbers] = {}
|
230
|
+
|
231
|
+
tracking_numbers = REXML::XPath.match(document, "//FulfillmentShipmentPackage/member/TrackingNumber")
|
232
|
+
if tracking_numbers.present?
|
233
|
+
order_id = REXML::XPath.first(document, "//FulfillmentOrder/SellerFulfillmentOrderId").text.strip
|
234
|
+
response[:tracking_numbers][order_id] = tracking_numbers.map{ |t| t.text.strip }
|
235
|
+
end
|
236
|
+
|
237
|
+
response[:response_status] = SUCCESS
|
238
|
+
response
|
239
|
+
end
|
240
|
+
|
241
|
+
def parse_fulfillment_response(op, document)
|
242
|
+
{ :response_status => SUCCESS, :response_comment => MESSAGES[op][SUCCESS] }
|
243
|
+
end
|
244
|
+
|
245
|
+
def parse_inventory_response(document)
|
246
|
+
response = {}
|
247
|
+
response[:stock_levels] = {}
|
248
|
+
|
249
|
+
document.each_element('//InventorySupplyList/member') do |node|
|
250
|
+
params = node.elements.to_a.each_with_object({}) { |elem, hash| hash[elem.name] = elem.text }
|
251
|
+
|
252
|
+
response[:stock_levels][params['SellerSKU']] = params['InStockSupplyQuantity'].to_i
|
253
|
+
end
|
254
|
+
|
255
|
+
next_token = REXML::XPath.first(document, '//NextToken')
|
256
|
+
response[:next_token] = next_token ? next_token.text : nil
|
257
|
+
|
258
|
+
response[:response_status] = SUCCESS
|
259
|
+
response
|
260
|
+
end
|
261
|
+
|
262
|
+
def parse_error(http_response)
|
263
|
+
response = {}
|
264
|
+
response[:http_code] = http_response.code
|
265
|
+
response[:http_message] = http_response.message
|
266
|
+
|
267
|
+
document = REXML::Document.new(http_response.body)
|
268
|
+
|
269
|
+
node = REXML::XPath.first(document, '//Error')
|
270
|
+
error_code = REXML::XPath.first(node, '//Code')
|
271
|
+
error_message = REXML::XPath.first(node, '//Message')
|
272
|
+
|
273
|
+
response[:status] = FAILURE
|
274
|
+
response[:faultcode] = error_code ? error_code.text : ""
|
275
|
+
response[:faultstring] = error_message ? error_message.text : ""
|
276
|
+
response[:response_message] = error_message ? error_message.text : ""
|
277
|
+
response[:response_comment] = "#{response[:faultcode]}: #{response[:faultstring]}"
|
278
|
+
response
|
279
|
+
rescue REXML::ParseException => e
|
280
|
+
rescue NoMethodError => e
|
281
|
+
response[:http_body] = http_response.body
|
282
|
+
response[:response_status] = FAILURE
|
283
|
+
response[:response_comment] = "#{response[:http_code]}: #{response[:http_message]}"
|
284
|
+
response
|
285
|
+
end
|
286
|
+
|
287
|
+
def sign(http_verb, uri, options)
|
288
|
+
string_to_sign = "#{http_verb.to_s.upcase}\n"
|
289
|
+
string_to_sign += "#{uri.host}\n"
|
290
|
+
string_to_sign += uri.path.length <= 0 ? "/\n" : "#{uri.path}\n"
|
291
|
+
string_to_sign += build_query(options)
|
292
|
+
|
293
|
+
# remove trailing newline created by encode64
|
294
|
+
escape(Base64.encode64(OpenSSL::HMAC.digest(SIGNATURE_METHOD, @options[:password], string_to_sign)).chomp)
|
295
|
+
end
|
296
|
+
|
297
|
+
def amazon_request?(signed_string, expected_signature)
|
298
|
+
calculated_signature = escape(Base64.encode64(OpenSSL::HMAC.digest(SIGNATURE_METHOD, @options[:password], signed_string)).chomp)
|
299
|
+
calculated_signature == expected_signature
|
300
|
+
end
|
301
|
+
|
302
|
+
def registration_url(options)
|
303
|
+
opts = {
|
304
|
+
"returnPathAndParameters" => options["returnPathAndParameters"],
|
305
|
+
"id" => @options[:app_id],
|
306
|
+
"AWSAccessKeyId" => @options[:login],
|
307
|
+
"SignatureMethod" => "Hmac#{SIGNATURE_METHOD}",
|
308
|
+
"SignatureVersion" => SIGNATURE_VERSION
|
309
|
+
}
|
310
|
+
signature = sign(:get, REGISTRATION_URI, opts)
|
311
|
+
"#{REGISTRATION_URI.to_s}?#{build_query(opts)}&Signature=#{signature}"
|
312
|
+
end
|
313
|
+
|
314
|
+
def md5_content(content)
|
315
|
+
Base64.encode64(OpenSSL::Digest::Digest.new('md5', content).digest).chomp
|
316
|
+
end
|
317
|
+
|
318
|
+
def build_query(query_params)
|
319
|
+
query_params.sort.map{ |key, value| [escape(key.to_s), escape(value.to_s)].join('=') }.join('&')
|
320
|
+
end
|
321
|
+
|
322
|
+
def build_headers(querystr)
|
323
|
+
{
|
324
|
+
'User-Agent' => APPLICATION_IDENTIFIER,
|
325
|
+
'Content-MD5' => md5_content(querystr),
|
326
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
def build_basic_api_query(options)
|
331
|
+
opts = Hash[options.map{ |k,v| [k.to_s, v.to_s] }]
|
332
|
+
opts["AWSAccessKeyId"] = @options[:login] unless opts["AWSAccessKey"]
|
333
|
+
opts["Timestamp"] = Time.now.utc.iso8601 unless opts["Timestamp"]
|
334
|
+
opts["Version"] = VERSION unless opts["Version"]
|
335
|
+
opts["SignatureMethod"] = "Hmac#{SIGNATURE_METHOD}" unless opts["SignatureMethod"]
|
336
|
+
opts["SignatureVersion"] = SIGNATURE_VERSION unless opts["SignatureVersion"]
|
337
|
+
opts["SellerId"] = @seller_id unless opts["SellerId"] || !@seller_id
|
338
|
+
opts
|
339
|
+
end
|
340
|
+
|
341
|
+
def build_fulfillment_request(order_id, shipping_address, line_items, options)
|
342
|
+
params = {
|
343
|
+
:Action => OPERATIONS[:outbound][:create],
|
344
|
+
:SellerFulfillmentOrderId => order_id.to_s,
|
345
|
+
:DisplayableOrderId => order_id.to_s,
|
346
|
+
:DisplayableOrderDateTime => options[:order_date].utc.iso8601,
|
347
|
+
:ShippingSpeedCategory => options[:shipping_method]
|
348
|
+
}
|
349
|
+
params[:DisplayableOrderComment] = options[:comment] if options[:comment]
|
350
|
+
|
351
|
+
request = build_basic_api_query(params.merge(options))
|
352
|
+
request = request.merge build_address(shipping_address)
|
353
|
+
request = request.merge build_items(line_items)
|
354
|
+
|
355
|
+
request
|
356
|
+
end
|
357
|
+
|
358
|
+
def build_get_current_fulfillment_orders_request(options = {})
|
359
|
+
start_time = options.delete(:start_time) || 1.day.ago.utc
|
360
|
+
params = {
|
361
|
+
:Action => OPERATIONS[:outbound][:list],
|
362
|
+
:QueryStartDateTime => start_time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
363
|
+
}
|
364
|
+
|
365
|
+
build_basic_api_query(params.merge(options))
|
366
|
+
end
|
367
|
+
|
368
|
+
def build_inventory_list_request(options = {})
|
369
|
+
response_group = options.delete(:response_group) || "Basic"
|
370
|
+
params = {
|
371
|
+
:Action => OPERATIONS[:inventory][:list],
|
372
|
+
:ResponseGroup => response_group
|
373
|
+
}
|
374
|
+
if skus = options.delete(:skus)
|
375
|
+
skus.each_with_index do |sku, index|
|
376
|
+
params[LOOKUPS[:list_inventory][:sku] % (index + 1)] = sku
|
377
|
+
end
|
378
|
+
else
|
379
|
+
start_time = options.delete(:start_time) || 1.day.ago
|
380
|
+
params[:QueryStartDateTime] = start_time.utc.iso8601
|
381
|
+
end
|
382
|
+
|
383
|
+
build_basic_api_query(params.merge(options))
|
384
|
+
end
|
385
|
+
|
386
|
+
def build_next_inventory_list_request(token)
|
387
|
+
params = {
|
388
|
+
:NextToken => token,
|
389
|
+
:Action => OPERATIONS[:inventory][:list_next]
|
390
|
+
}
|
391
|
+
|
392
|
+
build_basic_api_query(params)
|
393
|
+
end
|
394
|
+
|
395
|
+
def build_tracking_request(order_id, options)
|
396
|
+
params = {:Action => OPERATIONS[:outbound][:tracking], :SellerFulfillmentOrderId => order_id}
|
397
|
+
|
398
|
+
build_basic_api_query(params.merge(options))
|
399
|
+
end
|
400
|
+
|
401
|
+
def build_address(address)
|
402
|
+
requires!(address, :name, :address1, :city, :state, :country, :zip)
|
403
|
+
ary = address.map{ |key, value| [LOOKUPS[:destination_address][key], value] if LOOKUPS[:destination_address].include?(key) && value.present? }
|
404
|
+
Hash[ary.compact]
|
405
|
+
end
|
406
|
+
|
407
|
+
def build_items(line_items)
|
408
|
+
lookup = LOOKUPS[:line_items]
|
409
|
+
counter = 0
|
410
|
+
line_items.reduce({}) do |items, line_item|
|
411
|
+
counter += 1
|
412
|
+
lookup.each do |key, value|
|
413
|
+
entry = value % counter
|
414
|
+
case key
|
415
|
+
when :sku
|
416
|
+
items[entry] = line_item[:sku] || "SKU-#{counter}"
|
417
|
+
when :order_id
|
418
|
+
items[entry] = line_item[:sku] || "FULFILLMENT-ITEM-ID-#{counter}"
|
419
|
+
when :quantity
|
420
|
+
items[entry] = line_item[:quantity] || 1
|
421
|
+
else
|
422
|
+
items[entry] = line_item[key] if line_item.include? key
|
423
|
+
end
|
424
|
+
end
|
425
|
+
items
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def build_status_request
|
430
|
+
build_basic_api_query({ :Action => OPERATIONS[:outbound][:status] })
|
431
|
+
end
|
432
|
+
|
433
|
+
def escape(str)
|
434
|
+
CGI.escape(str.to_s).gsub('+', '%20')
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|