snl-peddler 0.1.0

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 ADDED
@@ -0,0 +1 @@
1
+ .DS_Store
data/History.txt ADDED
@@ -0,0 +1,2 @@
1
+ == 0.1.0 / 2009-08-14
2
+ * First public release.
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,104 @@
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 then upload:
26
+
27
+ batch.upload
28
+
29
+ The batch now should have an upload ID assigned to it. Let's check its 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 uploading your inventory and now want to download some reports from Amazon. Let's go ahead and fetch a preorder list for the past two days to see if there is anything you can fill:
38
+
39
+ preorders_report = client.new_report(
40
+ :preorder,
41
+ :product_line => "Books",
42
+ :frequency => 2)
43
+ preorders = Peddler::Handlers::TabDelimitedHandler.decode_response(preorders_report.body)
44
+ p preorders.size
45
+ => 2000
46
+ p preorders[0].asin
47
+ => "1234567890"
48
+ p preorders[0].average_asking_price
49
+ => "100"
50
+
51
+ Maybe you're probably wondering if you have any new orders:
52
+
53
+ orders_report = client.new_report :order
54
+ orders = Peddler::Handlers::TabDelimitedHandler.decode_response(orders_report.body)
55
+ p orders.size
56
+ => 1500
57
+ p orders[0].item_name
58
+ => "A Thousand Plateaus: Capitalism and Schizophrenia (Paperback) by Gilles Deleuze"
59
+
60
+ You have processed the orders and want to post back the results to Amazon, now that they're "charge when ship." Let's start with sending shipment info:
61
+
62
+ feed = client.new_order_fulfillment_feed
63
+ fulfilled_order = new_fulfilled_order(
64
+ :order_id => "123-1234567-1234567",
65
+ :order_date => "2009-08-01",
66
+ :carrier_code => "USPS",
67
+ :tracking_number => "0308 0330 0000 0000 0000")
68
+ feed << fulfilled_order
69
+
70
+ Again, repeat until done and upload:
71
+
72
+ feed.upload
73
+
74
+ Curious to see the processing report?
75
+
76
+ p feed.status
77
+ => "_SUBMITTED_"
78
+
79
+ Refresh until you see:
80
+
81
+ p feed.status!
82
+ => "_DONE_"
83
+
84
+ Finally, check the report:
85
+
86
+ p feed.download.to_s
87
+ => ...
88
+
89
+ Sadly, you also have an order you can't fulfill. No problem. The workflow is quite similar:
90
+
91
+ feed = client.new_order_cancellation_feed
92
+ cancelled_order = new_cancelled_order(
93
+ :order_id => "123-1234567-1234567")
94
+ feed << cancelled_order
95
+ feed.upload
96
+ sleep(60)
97
+ feed.status!
98
+ => "_DONE_"
99
+ p feed.download.to_s
100
+ => ...
101
+
102
+ Run rdoc and check the source for more detailed info.
103
+
104
+ Copyright © 2009 Hakan Senol Ensari, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ begin
4
+ require "jeweler"
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "peddler"
7
+ s.summary = "Peddler is a Ruby wrapper to the Amazon Inventory Management API."
8
+ s.email = "hakan.ensari@papercavalier.com"
9
+ s.homepage = "http://github.com/snl/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 "xmlsimple"
13
+ s.add_development_dependency "rspec"
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
data/lib/peddler.rb ADDED
@@ -0,0 +1,25 @@
1
+ # Peddler is a Ruby wrapper to the Amazon Inventory management API.
2
+ module Peddler
3
+ VERSION = "0.1"
4
+ end
5
+
6
+ class String #:nodoc: all
7
+ def camelize
8
+ self.gsub(/(^|_)(.)/) { $2.upcase }
9
+ end
10
+ end
11
+
12
+ require "net/https"
13
+ require "ostruct"
14
+ require "tempfile"
15
+ require "time"
16
+ require "xmlsimple"
17
+
18
+ require File.dirname(__FILE__) + "/peddler/client"
19
+ require File.dirname(__FILE__) + "/peddler/handlers"
20
+ require File.dirname(__FILE__) + "/peddler/feeds"
21
+ require File.dirname(__FILE__) + "/peddler/inventory"
22
+ require File.dirname(__FILE__) + "/peddler/legacy_reports"
23
+ require File.dirname(__FILE__) + "/peddler/refunds"
24
+ require File.dirname(__FILE__) + "/peddler/reports"
25
+ require File.dirname(__FILE__) + "/peddler/transport"
@@ -0,0 +1,240 @@
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. Works with [ "us", "uk", "de", "ca", "fr", "jp" ].
27
+ def region=(region)
28
+ self.transport.region = region
29
+ end
30
+
31
+ # Creates an inventory batch.
32
+ #
33
+ # A sample workflow:
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
+ #
44
+ # The batch should now have an upload id assigned to it by Amazon. Once processed, you can use this to
45
+ # check the error log:
46
+ #
47
+ # report = client.new_report :upload, :id => batch.id
48
+ # report.body
49
+ #
50
+ # That should output something like the following, assuming everything went well with the upload:
51
+ #
52
+ # "Feed Processing Summary:\n\tNumber of records processed\t\t1\n\tNumber of records successful\t\t1\n\n"
53
+ #
54
+ def new_inventory_batch
55
+ Peddler::Inventory::Batch.new(self.transport.dup)
56
+ end
57
+
58
+ # Creates an inventory item. Parameter keys are lowercased and underscored but otherwise the same as
59
+ # Amazon's colum titles in their tab-delimited templates.
60
+ def new_inventory_item(params={})
61
+ Peddler::Inventory::Item.new(params)
62
+ end
63
+
64
+ # Returns count of pending inventory uploads queued at Amazon.
65
+ def inventory_queue
66
+ Peddler::Inventory::Queue.count(self.transport)
67
+ end
68
+
69
+ # Creates an order fulfillment batch.
70
+ #
71
+ # A sample workflow:
72
+ #
73
+ # feed = client.new_order_fulfillment_feed
74
+ # fulfilled_order = new_fulfilled_order(
75
+ # :order_id => "123-1234567-1234567",
76
+ # :order_date => "2009-08-01")
77
+ # feed << fulfilled_order
78
+ # feed.upload
79
+ # feed.status
80
+ # => "_SUBMITTED_"
81
+ #
82
+ # Now, refresh the status until you see:
83
+ #
84
+ # feed.status!
85
+ # => "_DONE_"
86
+ #
87
+ # Finally, check the processing report:
88
+ #
89
+ # feed.download.to_s
90
+ #
91
+ def new_order_fulfillment_feed
92
+ Peddler::Feeds::OrderFulfillment::Batch.new(self.transport.dup)
93
+ end
94
+
95
+ # Creates an item that can then be added to an order fulfillment feed. Keys are lowercased and underscored but
96
+ # otherwise the same as Amazon's headers. See section 7.1 in the API docs.
97
+ def new_fulfilled_order(params={})
98
+ Peddler::Feeds::OrderFulfillment::Item.new(params)
99
+ end
100
+
101
+ # Creates an order cancellation batch.
102
+ #
103
+ # A sample workflow:
104
+ #
105
+ # feed = client.new_order_cancellation_feed
106
+ # cancelled_order = new_cancelled_order(
107
+ # :order_id => "123-1234567-1234567")
108
+ # feed << cancelled_order
109
+ # feed.upload
110
+ # feed.status
111
+ # => "_SUBMITTED_"
112
+ #
113
+ # Now, refresh the status until you see:
114
+ #
115
+ # feed.status!
116
+ # => "_DONE_"
117
+ #
118
+ # Finally, check the processing report:
119
+ #
120
+ # feed.download.to_s
121
+ #
122
+ def new_order_cancellation_feed
123
+ Peddler::Feeds::OrderCancellation::Batch.new(self.transport.dup)
124
+ end
125
+
126
+ # Creates an item that can then be added to an order cancellation feed. Keys are lowercased and underscored but
127
+ # otherwise the same as Amazon's headers. See section 7.4 in the API docs.
128
+ def new_cancelled_order(params={})
129
+ Peddler::Feeds::OrderCancellation::Item.new(params)
130
+ end
131
+
132
+ # Creates a refund batch.
133
+ #
134
+ # batch = client.new_refund_batch
135
+ # refund = client.new_refund(
136
+ # :order_id => "123-1234567-1234567",
137
+ # :payments_transaction_id => "12341234567890",
138
+ # :refund_amount => 10.00,
139
+ # :reason => "CouldNotShip",
140
+ # :message => "With our apologies.")
141
+ # batch << refund
142
+ # batch.upload
143
+ #
144
+ # To follow up on the status of the upload a little while afterwards:
145
+ #
146
+ # status = client.latest_reports :batch_refund, :count => 1
147
+ # report = client.new_report(
148
+ # :batch_refund,
149
+ # :id => status[0].id)
150
+ #
151
+ # Assuming the refund was successfully processed, report.body should now output:
152
+ #
153
+ # "123-1234567-1234567order-item-id: 12341234567890\tSUCCESS 10.00 is Refunded.\r\n"
154
+ #
155
+ def new_refund_batch
156
+ Peddler::Refunds::Batch.new(self.transport.dup)
157
+ end
158
+
159
+ # Creates a refund item that can then be added to a refund batch.
160
+ #
161
+ # Reasons can be [ "GeneralAdjustment" "CouldNotShip" "DifferentItem" "MerchandiseNotReceived" "MerchandiseNotAsDescribed" ].
162
+ def new_refund(params={})
163
+ Peddler::Refunds::Item.new(params)
164
+ end
165
+
166
+ # Creates an instance for an already-generated report. Works only with what I call legacy reports, that is,
167
+ # anything that comes before section 7 in the API docs. Report names can be [ :upload, :order, :preorder, :batch_refund, :open_listings, :open_listings_lite, :open_listings_liter ].
168
+ #
169
+ # You can download a specific report by using its ID. Otherwise, the instance will fetch the latest available report. One
170
+ # oddball exception: upload reports do require an ID and will return nil if you don't provide one.
171
+ #
172
+ # orders_report = client.new_report :order
173
+ # orders = Peddler::Handlers::TabDelimitedHandler.decode_response(orders_report.body)
174
+ # orders[0].buyer_name
175
+ # => "John Doe"
176
+ #
177
+ # preorders_report = client.new_report(
178
+ #  :preorder,
179
+ # :product_line => "Books",
180
+ # :frequency => 2)
181
+ # preorders = Peddler::Handlers::TabDelimitedHandler.decode_response(preorders_report.body)
182
+ # preorders[0].average_asking_price
183
+ # => "100"
184
+ #
185
+ def new_report(name,params={})
186
+ Peddler::LegacyReports::Report.new(self.transport.dup, name, params)
187
+ end
188
+
189
+ # Requests a report. Returns true when successful. Name can be [ :order, :open_listings, :open_listings_lite, :open_listings_liter ].
190
+ #
191
+ # Here's some sample usage:
192
+ #
193
+ # client.generate_report :order, :number_of_days => 15
194
+ #
195
+ # client.generate_report :open_listings
196
+ #
197
+ # A word of caution. Open listings may crap up with larger inventories. I will have to migrate to a cURL-based
198
+ # HTTP client to get that working again.
199
+ def generate_report(name,params={})
200
+ Peddler::LegacyReports.generate(self.transport, name, params)
201
+ end
202
+
203
+ # Creates an unshipped order report. Takes on some optional parameters, such as :id, :starts_at, :ends_at. By default,
204
+ # it will request a new unshipped order report for the past seven days.
205
+ #
206
+ # report = client.new_unshipped_order_report
207
+ # report.status
208
+ # => "_SUBMITTED_"
209
+ #
210
+ # Now, refresh the status until you see:
211
+ #
212
+ # report.status!
213
+ # => "_DONE_"
214
+ #
215
+ # Once done, you'll get a nifty array of unshipped orders:
216
+ #
217
+ # report.unshipped_orders
218
+ #
219
+ def new_unshipped_orders_report(params={})
220
+ Peddler::Reports::UnshippedOrdersReport.new(self.transport.dup, params)
221
+ end
222
+
223
+ # 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 ].
224
+ #
225
+ # Some sample usage:
226
+ #
227
+ # reports = client.latest_reports :order, :count => 1
228
+ # reports[0]
229
+ # => #<Peddler::LegacyReports::ReportStatus starts_at="07-29-2009:10-00-06" ...
230
+ #
231
+ def latest_reports(name,params={})
232
+ Peddler::LegacyReports.latest(self.transport, name, params)
233
+ end
234
+
235
+ protected
236
+ def transport #:nodoc:all
237
+ @transport ||= Peddler::Transport.new
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,181 @@
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
+ "#{@order_id}\t#{@cancellation_reason_code}\t#{@amazon_order_item_code}\r\n"
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end