peddler 0.1.3
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.
- 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
|