peddler 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/History.txt +8 -0
- data/LICENSE +22 -0
- data/README.rdoc +118 -0
- data/Rakefile +18 -0
- data/VERSION.yml +4 -0
- data/init.rb +1 -0
- data/lib/peddler/client.rb +215 -0
- data/lib/peddler/feeds.rb +184 -0
- data/lib/peddler/handlers.rb +58 -0
- data/lib/peddler/inventory.rb +108 -0
- data/lib/peddler/legacy_reports.rb +107 -0
- data/lib/peddler/refunds.rb +54 -0
- data/lib/peddler/reports.rb +94 -0
- data/lib/peddler/transport.rb +135 -0
- data/lib/peddler.rb +25 -0
- data/peddler.gemspec +80 -0
- data/spec/peddler/client_spec.rb +47 -0
- data/spec/peddler/feeds_spec.rb +80 -0
- data/spec/peddler/handlers_spec.rb +19 -0
- data/spec/peddler/inventory_spec.rb +74 -0
- data/spec/peddler/legacy_reports_spec.rb +86 -0
- data/spec/peddler/refunds_spec.rb +35 -0
- data/spec/peddler/reports_spec.rb +19 -0
- data/spec/peddler/transport_spec.rb +58 -0
- data/spec/spec_helper.rb +3 -0
- metadata +108 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2009 Hakan Şenol Ensari
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= Peddler
|
2
|
+
|
3
|
+
Peddler is a Ruby wrapper to the Amazon Inventory management API.
|
4
|
+
|
5
|
+
== Example usage
|
6
|
+
|
7
|
+
Fire off a client:
|
8
|
+
|
9
|
+
client = Peddler::Client.new(
|
10
|
+
:username => "foo@bar.com",
|
11
|
+
:password => "secret",
|
12
|
+
:region => "us")
|
13
|
+
|
14
|
+
Create an inventory file:
|
15
|
+
|
16
|
+
batch = client.new_inventory_batch
|
17
|
+
item = client.new_inventory_item(
|
18
|
+
:product_id => "1234567890",
|
19
|
+
:price => 100.00,
|
20
|
+
:sku => "SKU-123",
|
21
|
+
:quantity => 10)
|
22
|
+
batch << item
|
23
|
+
...
|
24
|
+
|
25
|
+
Repeat ad infinitum and upload:
|
26
|
+
|
27
|
+
batch.upload
|
28
|
+
|
29
|
+
The batch now should have an upload ID. Go ahead and check the error log:
|
30
|
+
|
31
|
+
upload_log = client.new_report(
|
32
|
+
:upload,
|
33
|
+
:id => batch.id)
|
34
|
+
upload_log.body
|
35
|
+
=> "Feed Processing Summary:\n\tNumber of records processed\t\t1\n\tNumber of records successful\t\t1\n\n"
|
36
|
+
|
37
|
+
You're done listing and are wondering if you have any new orders:
|
38
|
+
|
39
|
+
orders_report = client.new_report :order
|
40
|
+
orders = client.detab(orders_report.body)
|
41
|
+
p orders.size
|
42
|
+
=> 1500
|
43
|
+
p orders[0].item_name
|
44
|
+
=> "A Thousand Plateaus: Capitalism and Schizophrenia (Paperback) by Gilles Deleuze"
|
45
|
+
|
46
|
+
Now that you have diligently processed the orders, post back the results to Amazon:
|
47
|
+
|
48
|
+
feed = client.new_order_fulfillment_feed
|
49
|
+
fulfilled_order = client.new_fulfilled_order(
|
50
|
+
:order_id => "123-1234567-1234567",
|
51
|
+
:order_date => "2009-08-01",
|
52
|
+
:carrier_code => "USPS",
|
53
|
+
:tracking_number => "0308 0330 0000 0000 0000")
|
54
|
+
feed << fulfilled_order
|
55
|
+
|
56
|
+
Again, repeat until done and upload:
|
57
|
+
|
58
|
+
feed.upload
|
59
|
+
|
60
|
+
Curious to see the processing report?
|
61
|
+
|
62
|
+
p feed.status
|
63
|
+
=> "_SUBMITTED_"
|
64
|
+
|
65
|
+
Refresh until you get:
|
66
|
+
|
67
|
+
p feed.status!
|
68
|
+
=> "_DONE_"
|
69
|
+
|
70
|
+
Finally, check the report:
|
71
|
+
|
72
|
+
p feed.download.to_s
|
73
|
+
=> ...
|
74
|
+
|
75
|
+
Sadly, you also have an order you can't fulfill. No problem. The workflow is similar:
|
76
|
+
|
77
|
+
feed = client.new_order_cancellation_feed
|
78
|
+
cancelled_order = client.new_cancelled_order(
|
79
|
+
:order_id => "123-1234567-1234567",
|
80
|
+
:cancellation_reason_code => "NoInventory",
|
81
|
+
:amazon_order_item_code => "12341234567890")
|
82
|
+
feed << cancelled_order
|
83
|
+
feed.upload
|
84
|
+
sleep(60)
|
85
|
+
feed.status!
|
86
|
+
=> "_DONE_"
|
87
|
+
p feed.download.to_s
|
88
|
+
=> ...
|
89
|
+
|
90
|
+
Need to post a partial refund? You'll have to revert to the older batch refund API method:
|
91
|
+
|
92
|
+
refunds = client.new_refund_batch
|
93
|
+
refund = client.new_refund(
|
94
|
+
:order_id => "123-1234567-1234567",
|
95
|
+
:payments_transaction_id => "12341234567890",
|
96
|
+
:refund_amount => 1.00,
|
97
|
+
:reason => "GeneralAdjustment",
|
98
|
+
:message => "With our apologies.")
|
99
|
+
refunds << refund
|
100
|
+
refunds.upload
|
101
|
+
|
102
|
+
Great sales. For a change, let's download something different from Amazon. Here's a preorder report:
|
103
|
+
|
104
|
+
preorder_report = client.new_report(
|
105
|
+
:preorder,
|
106
|
+
:product_line => "Books",
|
107
|
+
:frequency => 2)
|
108
|
+
preorders = client.detab(preorder_report.body)
|
109
|
+
p preorders.size
|
110
|
+
=> 2000
|
111
|
+
p preorders[0].asin
|
112
|
+
=> "1234567890"
|
113
|
+
p preorders[0].average_asking_price
|
114
|
+
=> "100"
|
115
|
+
|
116
|
+
Run rdoc and check the source for more detailed info.
|
117
|
+
|
118
|
+
Copyright © 2009 Hakan Senol Ensari, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
begin
|
4
|
+
require "jeweler"
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "peddler"
|
7
|
+
s.summary = "A Ruby wrapper to the Amazon Inventory Management API"
|
8
|
+
s.email = "hakan.ensari@papercavalier.com"
|
9
|
+
s.homepage = "http://snl.github.com/peddler"
|
10
|
+
s.description = "Peddler is a Ruby wrapper to the Amazon Inventory Management API."
|
11
|
+
s.authors = ["Hakan Senol Ensari"]
|
12
|
+
s.add_dependency "xml-simple"
|
13
|
+
s.add_development_dependency "rspec"
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
data/VERSION.yml
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'peddler'
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# = Peddler
|
2
|
+
# Peddler is a Ruby wrapper to the Amazon Inventory management API.
|
3
|
+
#
|
4
|
+
# Peddler::Client has some detailed explanation and examples of usage.
|
5
|
+
module Peddler
|
6
|
+
# This is the public interface of the Peddler library.
|
7
|
+
class Client
|
8
|
+
# Creates a client instance.
|
9
|
+
#
|
10
|
+
# client = Peddler::Client.new :username => "foo@bar.com",
|
11
|
+
# :password => "secret",
|
12
|
+
# :region => "us"
|
13
|
+
#
|
14
|
+
def initialize(params={})
|
15
|
+
params.each_pair { |key, value| self.send("#{key}=", value) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def username=(username)
|
19
|
+
self.transport.username = username
|
20
|
+
end
|
21
|
+
|
22
|
+
def password=(password)
|
23
|
+
self.transport.password = password
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets Amazon region.
|
27
|
+
#
|
28
|
+
# Possible regions: ["us", "uk", "de", "ca", "fr", "jp"]
|
29
|
+
def region=(region)
|
30
|
+
self.transport.region = region
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates an inventory batch.
|
34
|
+
#
|
35
|
+
# batch = client.new_inventory_batch
|
36
|
+
# book = new_inventory_item(
|
37
|
+
# :product_id => "1234567890",
|
38
|
+
# :sku => "SKU-001",
|
39
|
+
# :price => 10.00,
|
40
|
+
# :quantity => 1)
|
41
|
+
# batch << book
|
42
|
+
# batch.upload
|
43
|
+
# report = client.new_report :upload, :id => batch.id
|
44
|
+
# p report.body
|
45
|
+
# => "Feed Processing Summary:\n\tNumber of records processed\t\t1\n\tNumber of records successful\t\t1\n\n"
|
46
|
+
#
|
47
|
+
def new_inventory_batch
|
48
|
+
Peddler::Inventory::Batch.new(self.transport.dup)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates an inventory item. Parameter keys are lowercased and underscored but otherwise the same as
|
52
|
+
# Amazon's colum titles in their tab-delimited templates.
|
53
|
+
def new_inventory_item(params={})
|
54
|
+
Peddler::Inventory::Item.new(params)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns count of pending inventory uploads queued at Amazon.
|
58
|
+
def inventory_queue
|
59
|
+
Peddler::Inventory::Queue.count(self.transport)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates an order fulfillment batch.
|
63
|
+
#
|
64
|
+
# feed = client.new_order_fulfillment_feed
|
65
|
+
# fulfilled_order = client.new_fulfilled_order(
|
66
|
+
# :order_id => "123-1234567-1234567",
|
67
|
+
# :order_date => "2009-08-01")
|
68
|
+
# feed << fulfilled_order
|
69
|
+
# feed.upload
|
70
|
+
# feed.status
|
71
|
+
# => "_SUBMITTED_"
|
72
|
+
# sleep(60)
|
73
|
+
# feed.status!
|
74
|
+
# => "_DONE_"
|
75
|
+
# p feed.download.to_s
|
76
|
+
#
|
77
|
+
def new_order_fulfillment_feed
|
78
|
+
Peddler::Feeds::OrderFulfillment::Batch.new(self.transport.dup)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Creates an item that can then be added to an order fulfillment feed. Keys are lowercased and underscored but
|
82
|
+
# otherwise the same as Amazon's headers. See section 7.1 in the API docs.
|
83
|
+
def new_fulfilled_order(params={})
|
84
|
+
Peddler::Feeds::OrderFulfillment::Item.new(params)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates an order cancellation batch.
|
88
|
+
#
|
89
|
+
# feed = client.new_order_cancellation_feed
|
90
|
+
# cancelled_order = client.new_cancelled_order(
|
91
|
+
# :order_id => "123-1234567-1234567",
|
92
|
+
# :cancellation_reason_code => "NoInventory",
|
93
|
+
# :amazon_order_item_code => "12341234567890")
|
94
|
+
# feed << cancelled_order
|
95
|
+
# feed.upload
|
96
|
+
# feed.status
|
97
|
+
# => "_SUBMITTED_"
|
98
|
+
# sleep(60)
|
99
|
+
# feed.status!
|
100
|
+
# => "_DONE_"
|
101
|
+
# p feed.download.to_s
|
102
|
+
#
|
103
|
+
def new_order_cancellation_feed
|
104
|
+
Peddler::Feeds::OrderCancellation::Batch.new(self.transport.dup)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Creates an item that can then be added to an order cancellation feed. Keys are lowercased and underscored but
|
108
|
+
# otherwise the same as Amazon's headers. See section 7.4 in the API docs.
|
109
|
+
def new_cancelled_order(params={})
|
110
|
+
Peddler::Feeds::OrderCancellation::Item.new(params)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates a refund batch.
|
114
|
+
#
|
115
|
+
# batch = client.new_refund_batch
|
116
|
+
# refund = client.new_refund(
|
117
|
+
# :order_id => "123-1234567-1234567",
|
118
|
+
# :payments_transaction_id => "12341234567890",
|
119
|
+
# :refund_amount => 10.00,
|
120
|
+
# :reason => "CouldNotShip",
|
121
|
+
# :message => "With our apologies.")
|
122
|
+
# batch << refund
|
123
|
+
# batch.upload
|
124
|
+
# sleep(60)
|
125
|
+
# status = client.latest_reports :batch_refund, :count => 1
|
126
|
+
# report = client.new_report(
|
127
|
+
# :batch_refund,
|
128
|
+
# :id => status[0].id)
|
129
|
+
# p report.body
|
130
|
+
# => "123-1234567-1234567order-item-id: 12341234567890\tSUCCESS 10.00 is Refunded.\r\n"
|
131
|
+
#
|
132
|
+
def new_refund_batch
|
133
|
+
Peddler::Refunds::Batch.new(self.transport.dup)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Creates a refund item that can then be added to a refund batch.
|
137
|
+
#
|
138
|
+
# Possible reasons: ["GeneralAdjustment", "CouldNotShip", "DifferentItem", "MerchandiseNotReceived", "MerchandiseNotAsDescribed"]
|
139
|
+
def new_refund(params={})
|
140
|
+
Peddler::Refunds::Item.new(params)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Creates an instance for an already-generated report. Works only with what I call legacy reports, that is,
|
144
|
+
# anything that comes before section 7 in the API docs.
|
145
|
+
#
|
146
|
+
# Possible report names: [:upload, :order, :preorder, :batch_refund, :open_listings, :open_listings_lite, :open_listings_liter]
|
147
|
+
#
|
148
|
+
# You can download a specific report by using its ID. Otherwise, the instance will fetch the latest available report. One
|
149
|
+
# oddball exception: upload reports do require an ID and will return nil if you don't provide one.
|
150
|
+
#
|
151
|
+
# orders_report = client.new_report :order
|
152
|
+
# orders = client.detab(orders_report.body)
|
153
|
+
# orders[0].buyer_name
|
154
|
+
# => "John Doe"
|
155
|
+
#
|
156
|
+
# preorders_report = client.new_report(
|
157
|
+
# :preorder,
|
158
|
+
# :product_line => "Books",
|
159
|
+
# :frequency => 2)
|
160
|
+
# preorders = client.detab(preorders_report.body)
|
161
|
+
# preorders[0].average_asking_price
|
162
|
+
# => "100"
|
163
|
+
#
|
164
|
+
def new_report(name,params={})
|
165
|
+
Peddler::LegacyReports::Report.new(self.transport.dup, name, params)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Requests a report. Returns true when successful.
|
169
|
+
#
|
170
|
+
# Possible report names: [:order, :open_listings, :open_listings_lite, :open_listings_liter]
|
171
|
+
#
|
172
|
+
# client.generate_report :order, :number_of_days => 15
|
173
|
+
#
|
174
|
+
# A word of caution. Open listings may crap up with larger inventories. I will have to migrate to a cURL-based
|
175
|
+
# HTTP client to get that working again.
|
176
|
+
def generate_report(name,params={})
|
177
|
+
Peddler::LegacyReports.generate(self.transport, name, params)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Creates an unshipped order report. Takes on some optional parameters, such as :id, :starts_at, :ends_at. By default,
|
181
|
+
# it will request a new unshipped order report for the past seven days.
|
182
|
+
#
|
183
|
+
# report = client.new_unshipped_orders_report
|
184
|
+
# report.status
|
185
|
+
# => "_SUBMITTED_"
|
186
|
+
# sleep(60)
|
187
|
+
# report.status!
|
188
|
+
# => "_DONE_"
|
189
|
+
# p report.unshipped_orders
|
190
|
+
#
|
191
|
+
def new_unshipped_orders_report(params={})
|
192
|
+
Peddler::Reports::UnshippedOrdersReport.new(self.transport.dup, params)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns status of most recent reports. Optional "count" defaults to 10. Name can be [ :upload, :order, :batch_refund, :open_listings, :open_listings_lite, :open_listings_liter ].
|
196
|
+
#
|
197
|
+
# reports = client.latest_reports :order, :count => 1
|
198
|
+
# reports[0]
|
199
|
+
# => #<Peddler::LegacyReports::ReportStatus starts_at="07-29-2009:10-00-06" ...
|
200
|
+
#
|
201
|
+
def latest_reports(name,params={})
|
202
|
+
Peddler::LegacyReports.latest(self.transport, name, params)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Decodes tab-delimited content into an array of OpenStruct objects.
|
206
|
+
def detab(msg)
|
207
|
+
Peddler::Handlers::TabDelimitedHandler.decode_response(msg)
|
208
|
+
end
|
209
|
+
|
210
|
+
protected
|
211
|
+
def transport #:nodoc:all
|
212
|
+
@transport ||= Peddler::Transport.new
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Peddler
|
2
|
+
# This module includes functionality to handle the charge-when-ship-related feeds Amazon added to the API
|
3
|
+
# in its latest incarnation in 2009.
|
4
|
+
module Feeds
|
5
|
+
# Downloadable file. The processing report in case of feeds. Outputs Amazon's response verbatim.
|
6
|
+
# Will add functionality to parse the response some time down the road.
|
7
|
+
class Download
|
8
|
+
attr_accessor :id, :type, :related_reference_id, :available_at, :acknowledged
|
9
|
+
|
10
|
+
def initialize(transport, params={})
|
11
|
+
@mapped_params = {
|
12
|
+
"DownloadId" => "id",
|
13
|
+
"DownloadType" => "type",
|
14
|
+
"RelatedReferenceId" => "related_reference_id",
|
15
|
+
"AvailableDate" => "available_at",
|
16
|
+
"Acknowledged" => "acknowledged"}
|
17
|
+
@transport = transport
|
18
|
+
params.each_pair{ |key, value| self.send "#{@mapped_params[key]}=", value }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retrieves and returns report
|
22
|
+
def to_s
|
23
|
+
@body ||= download_report
|
24
|
+
end
|
25
|
+
private
|
26
|
+
def download_report
|
27
|
+
return nil if @id.nil?
|
28
|
+
@transport.modernize_request
|
29
|
+
@transport.query_params.merge!({
|
30
|
+
"Action" => "download",
|
31
|
+
"downloadId" => @id})
|
32
|
+
@transport.execute_request
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# This is the base class.
|
37
|
+
class Feed
|
38
|
+
attr_writer :file_content
|
39
|
+
attr_accessor :batch, :download, :status, :type, :id, :submitted_at, :started_processing_at, :completed_processing_at, :messages_processed, :messages_successful, :messages_with_errors, :messages_with_warnings
|
40
|
+
|
41
|
+
def initialize(transport)
|
42
|
+
@transport = transport
|
43
|
+
@batch = []
|
44
|
+
@mapped_params = {
|
45
|
+
"UploadStatus" => "status",
|
46
|
+
"UploadType" => "type",
|
47
|
+
"UploadId" => "id",
|
48
|
+
"SubmittedDate" => "submitted_at",
|
49
|
+
"StartedProcessingDate" => "started_processing_at",
|
50
|
+
"CompletedProcessingDate" => "completed_processing_at",
|
51
|
+
"CompletedProcesssingDate" => "completed_processing_at",
|
52
|
+
"MessagesProcessed" => "messages_processed",
|
53
|
+
"MessagesSuccessful" => "messages_successful",
|
54
|
+
"MessagesWithErrors" => "messages_with_errors",
|
55
|
+
"MessagesWithWarnings" => "messages_with_warnings"}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns content of the upload file.
|
59
|
+
def file_content
|
60
|
+
return @file_content if @file_content
|
61
|
+
out = @file_header
|
62
|
+
@batch.each{ |item| out << item.to_s }
|
63
|
+
@file_content = out
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns status and will also refresh if not already "done."
|
67
|
+
def status!
|
68
|
+
return @status if @status.nil? || @status =~ /_DONE_/
|
69
|
+
refresh_status
|
70
|
+
@status
|
71
|
+
end
|
72
|
+
|
73
|
+
# Uploads batch.
|
74
|
+
def upload
|
75
|
+
raise PeddlerError.new("Batch already uploaded") unless @id.nil?
|
76
|
+
@transport.modernize_request
|
77
|
+
@transport.query_params.merge!({
|
78
|
+
"Action" => "upload",
|
79
|
+
"uploadType" => @type})
|
80
|
+
@transport.body = file_content
|
81
|
+
res = @transport.execute_request
|
82
|
+
process_response(res)
|
83
|
+
@status
|
84
|
+
end
|
85
|
+
|
86
|
+
# Adds an item to the batch.
|
87
|
+
def <<(item)
|
88
|
+
@batch << item
|
89
|
+
end
|
90
|
+
private
|
91
|
+
def refresh_status
|
92
|
+
@transport.modernize_request
|
93
|
+
@transport.query_params.merge!({
|
94
|
+
"Action" => "uploadStatus",
|
95
|
+
"uploadId" => @id})
|
96
|
+
res = @transport.execute_request
|
97
|
+
process_response(res)
|
98
|
+
end
|
99
|
+
|
100
|
+
def process_response(res)
|
101
|
+
xml = Peddler::Handlers::XMLHandler.decode_response(res)
|
102
|
+
params = Peddler::Handlers::XMLHandler.parse(:upload, xml)
|
103
|
+
if params[0]
|
104
|
+
params[0].each_pair do |key, value|
|
105
|
+
if key == "RelatedDownloadsList"
|
106
|
+
params = Peddler::Handlers::XMLHandler.parse(:download, value)
|
107
|
+
@download = Peddler::Feeds::Download.new(@transport, params[0])
|
108
|
+
else
|
109
|
+
self.send "#{@mapped_params[key]}=", value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module OrderFulfillment
|
117
|
+
# This class contains methods to upload order fulfillment info to Amazon.
|
118
|
+
# See sections 7.1 through 7.3 in the API documentation for more detail.
|
119
|
+
class Batch < Peddler::Feeds::Feed
|
120
|
+
def initialize(transport)
|
121
|
+
@file_header = "order-id\torder-item-id\tquantity\tship-date\tcarrier-code\tcarrier-name\ttracking-number\tship-method\r\n"
|
122
|
+
@type = "_POST_FLAT_FILE_FULFILLMENT_DATA_"
|
123
|
+
super(transport)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# This is an order fulfillment item.
|
128
|
+
class Item
|
129
|
+
attr_accessor :order_id, :order_item_id, :quantity, :ship_date, :carrier_name, :tracking_number, :ship_method
|
130
|
+
attr_reader :carrier_code
|
131
|
+
|
132
|
+
def initialize(params={})
|
133
|
+
params.each_pair{ |key, value| send("#{key}=", value) }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Validates when setting carrier code.
|
137
|
+
def carrier_code=(carrier_code)
|
138
|
+
@carrier_code = carrier_code if %w{USPS UPS FedEx other}.include?(carrier_code)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Outputs a formatted line for the tab-delimited upload file.
|
142
|
+
def to_s
|
143
|
+
"#{@order_id}\t#{@order_item_id}\t#{@quantity}\t#{@ship_date}\t#{@carrier_code}\t#{@carrier_name}\t#{@tracking_number}\t#{@ship_method}\r\n"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# This module contains methods to upload cancelled orders to Amazon.
|
149
|
+
# See section 7.4 in the API documentation for more detail.
|
150
|
+
module OrderCancellation
|
151
|
+
class Batch < Peddler::Feeds::Feed
|
152
|
+
def initialize(transport)
|
153
|
+
@file_header = "TemplateType=OrderCancellation Version=1.0/1.0.3 This row for Amazon.com use only. Do not modify or delete.\r\n" +
|
154
|
+
"order-id\tcancellation-reason-code\tamazon-order-item-code\r\n"
|
155
|
+
@type = "_POST_FLAT_FILE_ORDER_ACKNOWLEDGEMENT_DATA_"
|
156
|
+
super(transport)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# This is a cancelled order item.
|
161
|
+
class Item
|
162
|
+
attr_accessor :order_id, :amazon_order_item_code
|
163
|
+
attr_reader :cancellation_reason_code
|
164
|
+
|
165
|
+
def initialize(params={})
|
166
|
+
params.each_pair{ |key, value| send("#{key}=", value) }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Validates when setting cancellation reason code.
|
170
|
+
def cancellation_reason_code=(cancellation_reason_code)
|
171
|
+
@cancellation_reason_code = cancellation_reason_code if %w{ BuyerCanceled CustomerExchange CustomerReturn GeneralAdjustment MerchandiseNotReceived NoInventory ShippingAddressUndeliverable }.include?(cancellation_reason_code)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Outputs a formatted line for the tab-delimited upload file.
|
175
|
+
def to_s
|
176
|
+
if @cancellation_reason_code.nil? != @amazon_order_item_code.nil?
|
177
|
+
raise PeddlerError.new("Provide codes for both cancellation reason and Amazon order item (or omit both).")
|
178
|
+
end
|
179
|
+
"#{@order_id}\t#{@cancellation_reason_code}\t#{@amazon_order_item_code}\r\n"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Peddler
|
2
|
+
module Handlers
|
3
|
+
class XMLHandler
|
4
|
+
# Decodes an XML response.
|
5
|
+
def self.decode_response(res)
|
6
|
+
XmlSimple.xml_in(res)
|
7
|
+
end
|
8
|
+
# Parses responses to uploads and status queries for feeds in Section 7 of the docs. Walks
|
9
|
+
# through lists and returns an array of hashes.
|
10
|
+
def self.parse(name, xml)
|
11
|
+
name = name.to_s.capitalize
|
12
|
+
list = xml["#{name}sStatusList"] || xml["#{name}sList"]
|
13
|
+
if list
|
14
|
+
list.collect { |s| parse_status(name, s) }
|
15
|
+
else
|
16
|
+
[ parse_status(name, xml) ]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses legacy responses to queries on statuses of generated reports and inventory uploads.
|
21
|
+
def self.parse_legacy(xml)
|
22
|
+
if xml["Batch"]
|
23
|
+
xml["Batch"].collect { |input| Peddler::LegacyReports::UploadStatus.new(input) }
|
24
|
+
elsif xml["Report"]
|
25
|
+
xml["Report"].collect { |input| Peddler::LegacyReports::ReportStatus.new(input) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
protected
|
29
|
+
def self.parse_status(name, xml)
|
30
|
+
if xml[name]
|
31
|
+
xml[name][0].inject({}) do |memo, pair|
|
32
|
+
key, value = pair
|
33
|
+
value[0] = Time.parse(value[0]) if key =~ /Date$/
|
34
|
+
memo.merge!({ key => value[0] })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class TabDelimitedHandler
|
41
|
+
# Decodes tab-delimited content into an array of OpenStruct objects.
|
42
|
+
def self.decode_response(res)
|
43
|
+
lines = res.split("\n")
|
44
|
+
if lines.size > 1
|
45
|
+
params = lines[0].split("\t").collect{ |value| value.gsub(/-/, "_") }
|
46
|
+
params_size = params.size
|
47
|
+
(1..(lines.size - 1)).collect do |line_key|
|
48
|
+
values = lines[line_key].split("\t")
|
49
|
+
data = (0..(params_size - 1)).inject({}) { |memo, key| memo.merge( { params[key] => values[key] } ) }
|
50
|
+
OpenStruct.new(data)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
res
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|