active_fulfillment 2.0.1 → 2.0.2
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 +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
|