peddler 4.4.0 → 4.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 747dd42d445c711adc43f20e6a9c479bf795352512fecc67814f1a5f698c86a9
4
- data.tar.gz: 134255293900b9c32086f2291a42498e1477f29df64cbe0aa8b3da1420dfe320
3
+ metadata.gz: dbfcab3cd9afbb211acda145066e991a077d4b771faafe7a6c64dc57d46056bf
4
+ data.tar.gz: d479659f5bbc4b3787575503d3d2953fab17d193c86604da906b594295e74e5f
5
5
  SHA512:
6
- metadata.gz: 5880a513c77e79ae8523ca96c69d65697c179ede0febc18f2b4a10d932e7de9ed32cdf74c82ff13bc68ca7c15ae6df9d84ec92150d7bc018da6778e104dcb889
7
- data.tar.gz: 60b00292980fdac6da6acccac237b0ce677f7e49cb001f72584426aa70d54deac3c84d9e89abc5e380cf94aecda6db0d695d39868644380acb8c86603245e30d
6
+ metadata.gz: a80fe622c0c2a81045b7d9e96b98f5bbc7b265ba646f2ecac3f6121e35ec6d138dcd039b8630a52be64ae9c4d9547cb1bdc00cf0c9e11fff1e03581d185314f0
7
+ data.tar.gz: 7385d0a49ec8c5f714efd95d355b6cfec21924cdfc860ceec144ec615b476393ac5b0455bb47f0fe4e583b24f00686aeddd87b5742261d4cf8e417818926a2f6
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Peddler** is a Ruby interface to the [Amazon Selling Partner API (SP-API)][docs-overview]. The SP-API enables Amazon sellers and vendors to programmatically access their data on orders, shipments, payments, and more.
6
6
 
7
- Peddler is automatically generated from the Open API models provided by Amazon.
7
+ Peddler is automatically generated from the latest Open API models provided by Amazon.
8
8
 
9
9
  To begin using the Amazon SP-API, you must [register as a developer][register-as-developer] and [register your application][register-application]. Once registered, [obtain your Login with Amazon (LWA) credentials][view-credentials] to access your own or other selling partners' data.
10
10
 
@@ -27,6 +27,16 @@ And then execute:
27
27
  bundle install
28
28
  ```
29
29
 
30
+ ### Optional Dependencies
31
+
32
+ For enhanced error handling when uploading or downloading files, Peddler can parse XML error responses from Amazon S3 if Nokogiri is available:
33
+
34
+ ```ruby
35
+ gem "nokogiri"
36
+ ```
37
+
38
+ If Nokogiri is not available, S3 XML errors will still be handled gracefully, but with less detailed error information.
39
+
30
40
  ## Usage
31
41
 
32
42
  Set your LWA credentials in your environment.
@@ -102,8 +112,6 @@ Amazon's SP-API imposes [rate limits][rate-limits] on operations. Override the d
102
112
 
103
113
  Provide an optional `:retries` argument when initializing an API to specify retry attempts if throttled. Default is 0 (no retries). If set to a positive value, Peddler will retry with exponential backoff based on the rate limit.
104
114
 
105
- **Note:** This functionality requires version 6 of the [HTTP library][httprb], which is not yet released. To use rate limiting, point to their main branch on GitHub.
106
-
107
115
  ```ruby
108
116
  api = Peddler.orders_v0(aws_region, access_token, retries: 3)
109
117
  api.get_orders(
@@ -112,6 +120,87 @@ api.get_orders(
112
120
  )
113
121
  ```
114
122
 
123
+ ### Error Handling
124
+
125
+ By default, Peddler v4 maintains backward compatibility:
126
+ - **Client errors (4xx)**: Always raise `Peddler::Error` exceptions
127
+ - **Server errors (5xx)**: Return response objects (deprecated behavior)
128
+
129
+ To adopt the recommended v5.0 behavior where all errors raise exceptions:
130
+
131
+ ```ruby
132
+ Peddler.configure do |config|
133
+ config.raise_on_server_errors = true
134
+ end
135
+ ```
136
+
137
+ This ensures consistent error handling and prevents silent failures from server errors.
138
+
139
+ #### Current Default Behavior (v4.x)
140
+
141
+ ```ruby
142
+ # Server errors (5xx) return response objects by default
143
+ response = api.get_orders(marketplaceIds: ["ATVPDKIKX0DER"])
144
+
145
+ # Must check status to detect server errors
146
+ if response.status >= 500
147
+ puts "Server error: #{response.status}"
148
+ # Handle error or retry
149
+ else
150
+ orders = response.parse["payload"]["orders"]
151
+ end
152
+
153
+ # Client errors (4xx) always raise
154
+ begin
155
+ response = api.get_orders(marketplaceIds: ["INVALID"])
156
+ rescue Peddler::Error => e
157
+ puts "Client error: #{e.message}"
158
+ end
159
+ ```
160
+
161
+ #### Recommended Behavior (v5.0)
162
+
163
+ Enable consistent error handling by setting `raise_on_server_errors`:
164
+
165
+ ```ruby
166
+ # Configure once at application startup
167
+ Peddler.configure do |config|
168
+ config.raise_on_server_errors = true
169
+ end
170
+
171
+ # Now all errors raise exceptions consistently
172
+ begin
173
+ response = api.get_orders(marketplaceIds: ["ATVPDKIKX0DER"])
174
+ orders = response.parse["payload"]["orders"]
175
+ rescue Peddler::Error => e
176
+ puts "API Error: #{e.message}"
177
+ puts "Status: #{e.response.status}"
178
+
179
+ # Handle retries for server errors
180
+ if e.response.status >= 500
181
+ # Retry logic here
182
+ end
183
+ end
184
+ ```
185
+
186
+ #### S3 Error Handling
187
+
188
+ For file upload/download operations, Amazon S3 may return XML error responses. If Nokogiri is available, these are parsed for detailed error information:
189
+
190
+ ```ruby
191
+ # With Nokogiri gem
192
+ gem "nokogiri"
193
+
194
+ # Enhanced S3 error parsing
195
+ begin
196
+ api.upload_document(document_data)
197
+ rescue Peddler::Error => e
198
+ puts "S3 Error: #{e.message}" # Detailed XML-parsed error
199
+ end
200
+ ```
201
+
202
+ Without Nokogiri, S3 XML errors are still handled gracefully but with less detailed information.
203
+
115
204
  ### Available APIs
116
205
 
117
206
  Peddler provides Ruby interfaces to all Amazon SP-API endpoints. Each API is available in its respective version. Access APIs by calling methods on the Peddler module:
@@ -238,7 +327,15 @@ api.create_fulfillment_order(
238
327
  ```ruby
239
328
  api = Peddler.feeds(aws_region, access_token)
240
329
 
241
- # Create feed document
330
+ # Complete Feeds API Workflow:
331
+ # 1. Create a feed document (input feed document)
332
+ # 2. Upload content to the document
333
+ # 3. Create feed referencing the document
334
+ # 4. Wait for feed to complete (e.g., SQS notification)
335
+ # 5. Get feed document (result feed document)
336
+ # 6. Download results to see processing outcome
337
+
338
+ # Step 1: Create feed document (input document)
242
339
  document_response = api.create_feed_document(
243
340
  contentType: "text/xml; charset=UTF-8"
244
341
  )
@@ -262,11 +359,11 @@ upload_url = document_response.dig("url")
262
359
  # Access nested keys - returns nil if any key in the path is missing
263
360
  encryption_key = document_response.dig("encryptionDetails", "key")
264
361
 
265
- # Upload feed content
362
+ # Step 2: Upload feed content to the input document
266
363
  feed_content = File.read("inventory_update.xml")
267
364
  api.upload_feed_document(upload_url, feed_content, "text/xml; charset=UTF-8")
268
365
 
269
- # Create feed
366
+ # Step 3: Create feed referencing the input document
270
367
  feed_response = api.create_feed(
271
368
  feedType: "POST_INVENTORY_AVAILABILITY_DATA",
272
369
  marketplaceIds: ["ATVPDKIKX0DER"],
@@ -274,7 +371,32 @@ feed_response = api.create_feed(
274
371
  )
275
372
  feed_id = feed_response.dig("feedId")
276
373
 
277
- # Upload JSON feed
374
+ # Step 4: Wait for feed to complete (polling or SQS notification)
375
+ # Poll until status is "DONE", "FATAL", or "CANCELLED"
376
+ loop do
377
+ feed_status = api.get_feed(feed_id)
378
+ status = feed_status.dig("processingStatus")
379
+ break if ["DONE", "FATAL", "CANCELLED"].include?(status)
380
+ sleep 30 # Wait 30 seconds before checking again
381
+ end
382
+
383
+ # Step 5: Get feed document (result document with processing results)
384
+ result_document_id = feed_status.dig("resultFeedDocumentId")
385
+ result_document = api.get_feed_document(result_document_id) if result_document_id
386
+
387
+ # Step 6: Download results to see processing outcome
388
+ if result_document
389
+ download_url = result_document.dig("url")
390
+ response = HTTP.get(download_url)
391
+ content = if result_document.dig("compressionAlgorithm") == "GZIP"
392
+ Zlib::GzipReader.new(response).read
393
+ else
394
+ response.to_s
395
+ end
396
+ # Parse content to check for errors/success
397
+ end
398
+
399
+ # JSON feed example
278
400
  json_document = api.create_feed_document(
279
401
  { "contentType" => "application/json; charset=UTF-8" }
280
402
  )
@@ -297,23 +419,39 @@ json_feed_content = JSON.generate({
297
419
  ]
298
420
  })
299
421
  api.upload_feed_document(json_document.dig("url"), json_feed_content, "application/json; charset=UTF-8")
300
-
301
- # Get feed status
302
- api.get_feed(feed_id)
303
-
304
- # Get feed document content
305
- document = api.get_feed_document(feed_document_id)
306
- download_url = document.dig("url")
307
- response = HTTP.get(download_url)
308
- content = Zlib::GzipReader.new(response).read if document.dig("compressionAlgorithm") == "GZIP"
309
422
  ```
310
423
 
311
424
  #### Communication and Customer Management APIs
312
425
 
426
+ - **Customer Feedback API (2024-06-01)**: Analyze customer reviews and returns data at item and browse node levels
313
427
  - **Notifications API (v1)**: Subscribe to notifications for events like order updates
314
428
  - **Messaging API (v1)**: Send messages to customers
315
429
  - **Solicitations API (v1)**: Request customer reviews
316
430
 
431
+ ```ruby
432
+ # Customer Feedback API example
433
+ api = Peddler.customer_feedback_2024_06_01(aws_region, access_token)
434
+
435
+ # Get item review topics (most positive and negative)
436
+ review_topics = api.get_item_review_topics(
437
+ "B08N5WRWNW", # ASIN
438
+ Marketplace.id("US"),
439
+ "frequency" # Sort by frequency
440
+ )
441
+
442
+ # Get item review trends for past 6 months
443
+ review_trends = api.get_item_review_trends(
444
+ "B08N5WRWNW",
445
+ Marketplace.id("US")
446
+ )
447
+
448
+ # Get browse node return topics
449
+ return_topics = api.get_browse_node_return_topics(
450
+ "123456789", # Browse node ID
451
+ Marketplace.id("US")
452
+ )
453
+ ```
454
+
317
455
  ```ruby
318
456
  api = Peddler.notifications(aws_region, access_token)
319
457
  # Create destination
@@ -532,10 +670,9 @@ report = api.get_report(report_id)
532
670
  # Get all reports of a specific type
533
671
  reports = api.get_reports(report_types: ["GET_MERCHANTS_LISTINGS_FYP_REPORT"])
534
672
 
535
- # Download a report document
536
- document = api.get_report_document("DOCUMENT_ID")
537
- download_url = document.dig("url")
538
- # Process the downloaded report...
673
+ # Download a report document (using convenience helper)
674
+ response = api.download_report_document("DOCUMENT_ID")
675
+ # Process the downloaded report content from response.body...
539
676
  ```
540
677
 
541
678
  #### Sellers API
@@ -549,11 +686,6 @@ participations = api.get_marketplace_participations
549
686
 
550
687
  For a complete list of available APIs and their detailed documentation, refer to the [API models repository](https://github.com/amzn/selling-partner-api-models).
551
688
 
552
- ## TODO
553
-
554
- - [ ] Code generate payload parsers 🤔
555
- - [ ] Review and consider applying [these patches][patches]
556
-
557
689
  [build]: https://github.com/hakanensari/peddler/actions
558
690
  [docs-overview]: https://developer.amazonservices.com/sp-api-docs/overview
559
691
  [register-as-developer]: https://developer-docs.amazon.com/sp-api/docs/registering-as-a-developer
@@ -564,4 +696,3 @@ For a complete list of available APIs and their detailed documentation, refer to
564
696
  [authorization]: https://developer-docs.amazon.com/sp-api/docs/authorizing-selling-partner-api-applications
565
697
  [rate-limits]: https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits
566
698
  [httprb]: https://github.com/httprb/http
567
- [patches]: https://github.com/bizon/selling-partner-api-sdk/tree/master/codegen/patches
data/lib/peddler/api.rb CHANGED
@@ -4,7 +4,6 @@ require "http"
4
4
  require "uri"
5
5
 
6
6
  require "peddler/endpoint"
7
- require "peddler/error"
8
7
  require "peddler/marketplace"
9
8
  require "peddler/response"
10
9
  require "peddler/version"
@@ -79,8 +78,6 @@ module Peddler
79
78
  def meter(requests_per_second)
80
79
  return self if retries.zero?
81
80
 
82
- # HTTP v6.0 will implement retriable. Until then, point to their GitHub repo, or it's a no-op.
83
- # https://github.com/httprb/http/pull/790
84
81
  delay = sandbox? ? 0.2 : 1.0 / requests_per_second
85
82
  retriable(delay:, tries: retries + 1, retry_statuses: [429])
86
83
 
@@ -107,7 +104,7 @@ module Peddler
107
104
  # @return [self]
108
105
  [:via, :use, :retriable].each do |method|
109
106
  define_method(method) do |*args, **kwargs, &block|
110
- @http = http.send(method, *args, **kwargs, &block) if http.respond_to?(method)
107
+ @http = http.send(method, *args, **kwargs, &block)
111
108
  self
112
109
  end
113
110
  end
@@ -124,13 +121,7 @@ module Peddler
124
121
  end
125
122
 
126
123
  response = http.send(method, uri, **options)
127
-
128
- if response.status.client_error?
129
- error = Error.build(response)
130
- raise error if error
131
- end
132
-
133
- Response.decorate(response, parser:)
124
+ Response.wrap(response, parser:)
134
125
  end
135
126
  end
136
127
 
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "peddler/api"
4
+
5
+ module Peddler
6
+ class << self
7
+ def customer_feedback_2024_06_01(...)
8
+ APIs::CustomerFeedback20240601.new(...)
9
+ end
10
+ end
11
+
12
+ module APIs
13
+ # The Selling Partner API for CustomerFeedback
14
+ #
15
+ # The Selling Partner API for Customer Feedback (Customer Feedback API) provides information about customer reviews
16
+ # and returns at both the item and browse node level.
17
+ class CustomerFeedback20240601 < API
18
+ # Retrieve an item's ten most positive and ten most negative review topics.
19
+ #
20
+ # @note This operation can make a static sandbox call.
21
+ # @param asin [String] The Amazon Standard Identification Number (ASIN) is the unique identifier of a product
22
+ # within a marketplace. The value must be a child ASIN.
23
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
24
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
25
+ # @param sort_by [String] The metric by which to sort data in the response.
26
+ # @return [Peddler::Response] The API response
27
+ def get_item_review_topics(asin, marketplace_id, sort_by)
28
+ path = "/customerFeedback/2024-06-01/items/#{percent_encode(asin)}/reviews/topics"
29
+ params = {
30
+ "marketplaceId" => marketplace_id,
31
+ "sortBy" => sort_by,
32
+ }.compact
33
+
34
+ get(path, params:)
35
+ end
36
+
37
+ # This API returns the associated browse node of the requested ASIN. A browse node is a location in a browse tree
38
+ # that is used for navigation, product classification, and website content on the Amazon retail website.
39
+ #
40
+ # @note This operation can make a static sandbox call.
41
+ # @param asin [String] The Amazon Standard Identification Number (ASIN) is the unique identifier of a product
42
+ # within a marketplace.
43
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
44
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
45
+ # @return [Peddler::Response] The API response
46
+ def get_item_browse_node(asin, marketplace_id)
47
+ path = "/customerFeedback/2024-06-01/items/#{percent_encode(asin)}/browseNode"
48
+ params = {
49
+ "marketplaceId" => marketplace_id,
50
+ }.compact
51
+
52
+ get(path, params:)
53
+ end
54
+
55
+ # Retrieve a browse node's ten most positive and ten most negative review topics.
56
+ #
57
+ # @note This operation can make a static sandbox call.
58
+ # @param browse_node_id [String] The ID of a browse node. A browse node is a named location in a browse tree that
59
+ # is used for navigation, product classification, and website content.
60
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
61
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
62
+ # @param sort_by [String] The metric by which to sort the data in the response.
63
+ # @return [Peddler::Response] The API response
64
+ def get_browse_node_review_topics(browse_node_id, marketplace_id, sort_by)
65
+ path = "/customerFeedback/2024-06-01/browseNodes/#{percent_encode(browse_node_id)}/reviews/topics"
66
+ params = {
67
+ "marketplaceId" => marketplace_id,
68
+ "sortBy" => sort_by,
69
+ }.compact
70
+
71
+ get(path, params:)
72
+ end
73
+
74
+ # Retrieve an item's positive and negative review trends for the past six months.
75
+ #
76
+ # @note This operation can make a static sandbox call.
77
+ # @param asin [String] The Amazon Standard Identification Number (ASIN) is the unique identifier of a product
78
+ # within a marketplace. This API takes child ASIN as an input.
79
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
80
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
81
+ # @return [Peddler::Response] The API response
82
+ def get_item_review_trends(asin, marketplace_id)
83
+ path = "/customerFeedback/2024-06-01/items/#{percent_encode(asin)}/reviews/trends"
84
+ params = {
85
+ "marketplaceId" => marketplace_id,
86
+ }.compact
87
+
88
+ get(path, params:)
89
+ end
90
+
91
+ # Retrieve the positive and negative review trends of items in a browse node for the past six months.
92
+ #
93
+ # @note This operation can make a static sandbox call.
94
+ # @param browse_node_id [String] A browse node ID is a unique identifier of a browse node. A browse node is a
95
+ # named location in a browse tree that is used for navigation, product classification, and website content.
96
+ # @param marketplace_id [String] The marketplace ID is the globally unique identifier of a marketplace. For more
97
+ # information, refer to [Marketplace IDs](https://developer-docs.amazon.com/sp-api/docs/marketplace-ids).
98
+ # @return [Peddler::Response] The API response
99
+ def get_browse_node_review_trends(browse_node_id, marketplace_id)
100
+ path = "/customerFeedback/2024-06-01/browseNodes/#{percent_encode(browse_node_id)}/reviews/trends"
101
+ params = {
102
+ "marketplaceId" => marketplace_id,
103
+ }.compact
104
+
105
+ get(path, params:)
106
+ end
107
+
108
+ # Retrieve the topics that customers mention when they return items in a browse node.
109
+ #
110
+ # @note This operation can make a static sandbox call.
111
+ # @param browse_node_id [String] A browse node ID is a unique identifier for a browse node. A browse node is a
112
+ # named location in a browse tree that is used for navigation, product classification, and website content.
113
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
114
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
115
+ # @return [Peddler::Response] The API response
116
+ def get_browse_node_return_topics(browse_node_id, marketplace_id)
117
+ path = "/customerFeedback/2024-06-01/browseNodes/#{percent_encode(browse_node_id)}/returns/topics"
118
+ params = {
119
+ "marketplaceId" => marketplace_id,
120
+ }.compact
121
+
122
+ get(path, params:)
123
+ end
124
+
125
+ # Retrieve the trends of topics that customers mention when they return items in a browse node.
126
+ #
127
+ # @note This operation can make a static sandbox call.
128
+ # @param browse_node_id [String] A browse node ID is a unique identifier of a browse node. A browse node is a
129
+ # named location in a browse tree that is used for navigation, product classification, and website content.
130
+ # @param marketplace_id [String] The MarketplaceId is the globally unique identifier of a marketplace, you can
131
+ # refer to the marketplaceId here : https://developer-docs.amazon.com/sp-api/docs/marketplace-ids.
132
+ # @return [Peddler::Response] The API response
133
+ def get_browse_node_return_trends(browse_node_id, marketplace_id)
134
+ path = "/customerFeedback/2024-06-01/browseNodes/#{percent_encode(browse_node_id)}/returns/trends"
135
+ params = {
136
+ "marketplaceId" => marketplace_id,
137
+ }.compact
138
+
139
+ get(path, params:)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -16,27 +16,35 @@ module Peddler
16
16
  # can obtain financial events for a given order or date range without having to wait until a statement period
17
17
  # closes.
18
18
  class Finances20240619 < API
19
- # Returns transactions for the given parameters. It may take up to 48 hours for transactions to appear in your
20
- # transaction events.
19
+ # Returns transactions for the given parameters. Financial events might not include orders from the last 48 hours.
21
20
  #
22
21
  # @note This operation can make a static sandbox call.
23
- # @param posted_after [String] A date used for selecting transactions posted after (or at) a specified time. The
24
- # date-time must be no later than two minutes before the request was submitted, in ISO 8601 date time format.
22
+ # @param posted_after [String] The response includes financial events posted on or after this date. This date must
23
+ # be in [ISO 8601](https://developer-docs.amazon.com/sp-api/docs/iso-8601) date-time format. The date-time must
24
+ # be more than two minutes before the time of the request.
25
25
  # @param posted_before [String] A date used for selecting transactions posted before (but not at) a specified
26
26
  # time. The date-time must be later than PostedAfter and no later than two minutes before the request was
27
27
  # submitted, in ISO 8601 date time format. If PostedAfter and PostedBefore are more than 180 days apart, no
28
28
  # transactions are returned. You must specify the PostedAfter parameter if you specify the PostedBefore
29
29
  # parameter. Default: Now minus two minutes.
30
- # @param marketplace_id [String] A string token used to select Marketplace ID.
30
+ # @param marketplace_id [String] The identifier of the marketplace from which you want to retrieve transactions.
31
+ # The marketplace ID is the globally unique identifier of a marketplace. To find the ID for your marketplace,
32
+ # refer to [Marketplace IDs](https://developer-docs.amazon.com/sp-api/docs/marketplace-ids).
33
+ # @param transaction_status [String] The status of the transaction. **Possible values:** * `DEFERRED`: the
34
+ # transaction is currently deferred. * `RELEASED`: the transaction is currently released. * `DEFERRED_RELEASED`:
35
+ # the transaction was deferred in the past, but is now released. The status of a deferred transaction is updated
36
+ # to `DEFERRED_RELEASED` when the transaction is released.
31
37
  # @param next_token [String] A string token returned in the response of your previous request.
32
38
  # @param rate_limit [Float] Requests per second
33
39
  # @return [Peddler::Response] The API response
34
- def list_transactions(posted_after, posted_before: nil, marketplace_id: nil, next_token: nil, rate_limit: 0.5)
40
+ def list_transactions(posted_after, posted_before: nil, marketplace_id: nil, transaction_status: nil,
41
+ next_token: nil, rate_limit: 0.5)
35
42
  path = "/finances/2024-06-19/transactions"
36
43
  params = {
37
44
  "postedAfter" => posted_after,
38
45
  "postedBefore" => posted_before,
39
46
  "marketplaceId" => marketplace_id,
47
+ "transactionStatus" => transaction_status,
40
48
  "nextToken" => next_token,
41
49
  }.compact
42
50
 
@@ -162,7 +162,7 @@ module Peddler
162
162
  # Returns buyer information for the order that you specify.
163
163
  #
164
164
  # @note This operation can make a static sandbox call.
165
- # @param order_id [String] An `orderId` is an Amazon-defined order identifier, in 3-7-7 format.
165
+ # @param order_id [String] The Amazon order identifier in 3-7-7 format.
166
166
  # @param rate_limit [Float] Requests per second
167
167
  # @return [Peddler::Response] The API response
168
168
  def get_order_buyer_info(order_id, rate_limit: 0.5)
@@ -174,7 +174,7 @@ module Peddler
174
174
  # Returns the shipping address for the order that you specify.
175
175
  #
176
176
  # @note This operation can make a static sandbox call.
177
- # @param order_id [String] An `orderId` is an Amazon-defined order identifier, in 3-7-7 format.
177
+ # @param order_id [String] The Amazon order identifier in 3-7-7 format.
178
178
  # @param rate_limit [Float] Requests per second
179
179
  # @return [Peddler::Response] The API response
180
180
  def get_order_address(order_id, rate_limit: 0.5)
@@ -238,7 +238,7 @@ module Peddler
238
238
  # Returns regulated information for the order that you specify.
239
239
  #
240
240
  # @note This operation can make a static sandbox call.
241
- # @param order_id [String] An Amazon-defined order identifier, in 3-7-7 format.
241
+ # @param order_id [String] The Amazon order identifier in 3-7-7 format.
242
242
  # @param rate_limit [Float] Requests per second
243
243
  # @return [Peddler::Response] The API response
244
244
  def get_order_regulated_info(order_id, rate_limit: 0.5)
@@ -250,7 +250,7 @@ module Peddler
250
250
  # Updates (approves or rejects) the verification status of an order containing regulated products.
251
251
  #
252
252
  # @note This operation can make a static sandbox call.
253
- # @param order_id [String] An Amazon-defined order identifier, in 3-7-7 format.
253
+ # @param order_id [String] The Amazon order identifier in 3-7-7 format.
254
254
  # @param payload [Hash] The request body for the `updateVerificationStatus` operation.
255
255
  # @param rate_limit [Float] Requests per second
256
256
  # @return [Peddler::Response] The API response
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "peddler/api"
4
+ require "peddler/helpers/reports_2021_06_30"
4
5
 
5
6
  module Peddler
6
7
  class << self
@@ -15,6 +16,8 @@ module Peddler
15
16
  # The Selling Partner API for Reports lets you retrieve and manage a variety of reports that can help selling
16
17
  # partners manage their businesses.
17
18
  class Reports20210630 < API
19
+ include Peddler::Helpers::Reports20210630
20
+
18
21
  # Returns report details for the reports that match the filters that you specify.
19
22
  #
20
23
  # @note This operation can make a static sandbox call.
@@ -132,7 +132,7 @@ module Peddler
132
132
  #
133
133
  # @param limit [Integer] The limit to the number of records returned. Default value is 50 records.
134
134
  # @param sort_order [String] Sort the list by shipment label creation date in ascending or descending order.
135
- # @param next_token [String] A token that is used to retrieve the next page of results. The response includes
135
+ # @param next_token [String] A token that you use to retrieve the next page of results. The response includes
136
136
  # `nextToken` when the number of results exceeds the specified `pageSize` value. To get the next page of
137
137
  # results, call the operation with this token and include the same arguments as the call that produced the
138
138
  # token. To get a complete list, call this operation until `nextToken` is null. Note that this operation can
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Peddler
4
+ class << self
5
+ attr_writer :raise_on_server_errors
6
+
7
+ def raise_on_server_errors
8
+ return @raise_on_server_errors if defined?(@raise_on_server_errors)
9
+
10
+ @raise_on_server_errors = false # Default to v4 behavior
11
+ end
12
+
13
+ def configure
14
+ yield self
15
+ end
16
+ end
17
+ end
data/lib/peddler/error.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Peddler
4
6
  class Error < StandardError
5
7
  attr_reader :response
@@ -7,7 +9,11 @@ module Peddler
7
9
  # @!visibility private
8
10
  class << self
9
11
  def build(response)
10
- payload = JSON.parse(response)
12
+ payload = begin
13
+ JSON.parse(response)
14
+ rescue JSON::ParserError
15
+ parse_xml_error(response)
16
+ end
11
17
 
12
18
  if payload.key?("error")
13
19
  class_name = normalize_class_name(payload["error"])
@@ -15,6 +21,9 @@ module Peddler
15
21
  elsif payload.key?("errors")
16
22
  class_name = normalize_class_name(payload.dig("errors", 0, "code"))
17
23
  message = payload.dig("errors", 0, "message")
24
+ elsif payload.key?("Code")
25
+ class_name = payload["Code"]
26
+ message = payload["Message"]
18
27
  else
19
28
  return
20
29
  end
@@ -22,10 +31,7 @@ module Peddler
22
31
  klass = if Errors.const_defined?(class_name)
23
32
  Errors.const_get(class_name)
24
33
  else
25
- Errors.const_set(
26
- class_name,
27
- Class.new(Error),
28
- )
34
+ Errors.const_set(class_name, Class.new(Error))
29
35
  end
30
36
 
31
37
  klass.new(message, response)
@@ -35,6 +41,14 @@ module Peddler
35
41
 
36
42
  private
37
43
 
44
+ def parse_xml_error(response)
45
+ require "nokogiri"
46
+ doc = Nokogiri::XML(response)
47
+ Hash[doc.root.elements.collect { |e| [e.name, e.text] }]
48
+ rescue LoadError, NoMethodError
49
+ {}
50
+ end
51
+
38
52
  def normalize_class_name(code)
39
53
  if code.match?(/\A([a-z_]+|[A-Z_]+)\z/)
40
54
  code.split("_").map(&:capitalize).join
@@ -51,6 +65,7 @@ module Peddler
51
65
  end
52
66
 
53
67
  module Errors
68
+ class AccessDenied < Error; end
54
69
  class InvalidGrant < Error; end
55
70
  class InvalidInput < Error; end
56
71
  class InvalidRequest < Error; end
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "http"
4
+
5
+ require "peddler/response"
6
+
3
7
  module Peddler
4
8
  module Helpers
5
9
  module Feeds20210630
6
- # Uploads feed_content to a signed upload_url previously provided by
7
- # create_feed_document. The upload_url is signed, the Host and content-type
8
- # headers must match the signing.
10
+ # Convenience method to upload feed content to a signed upload_url previously
11
+ # provided by create_feed_document. This is step 2 of the 6-step Feeds API workflow.
12
+ # See README.md for the complete workflow documentation.
13
+ #
14
+ # The upload_url is signed, the Host and content-type headers must match the signing.
9
15
  # @param upload_url [String] The signed url from the `create_feed_document` response.
10
16
  # @param feed_content [String] The body of the content to upload.
11
17
  # @param content_type [String] The content type of the upload,
@@ -14,12 +20,20 @@ module Peddler
14
20
  def upload_feed_document(upload_url, feed_content, content_type)
15
21
  response = HTTP.headers("content-type" => content_type).put(upload_url, body: feed_content)
16
22
 
17
- if response.status.client_error?
18
- error = Error.build(response)
19
- raise error if error
20
- end
23
+ Response.wrap(response, parser:)
24
+ end
25
+
26
+ # Convenience method to download result feed content from a signed download_url
27
+ # provided by get_feed_document. This is step 6 of the 6-step Feeds API workflow.
28
+ # See README.md for the complete workflow documentation.
29
+ #
30
+ # The download_url is signed and provides access to the processed feed results.
31
+ # @param download_url [String] The signed url from the `get_feed_document` response.
32
+ # @return [HTTP::Response] The API response containing the feed result document
33
+ def download_result_feed_document(download_url)
34
+ response = HTTP.get(download_url)
21
35
 
22
- response
36
+ Response.wrap(response, parser:)
23
37
  end
24
38
  end
25
39
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+
5
+ require "peddler/response"
6
+ require "peddler/error"
7
+
8
+ module Peddler
9
+ module Helpers
10
+ module Reports20210630
11
+ # Convenience method to download a report by its document ID or URL.
12
+ # This method can handle both document IDs and direct download URLs.
13
+ #
14
+ # @param report_document_id_or_url [String] The report document identifier or download URL
15
+ # @return [HTTP::Response] The API response containing the report content
16
+ def download_report_document(report_document_id_or_url)
17
+ # If it looks like a URL, use direct download
18
+ if report_document_id_or_url.start_with?("http")
19
+ return download_report_document_from_url(report_document_id_or_url)
20
+ end
21
+
22
+ # Otherwise, treat it as a document ID and get the download URL first
23
+ document_info = get_report_document(report_document_id_or_url)
24
+ download_url = document_info.dig("url")
25
+
26
+ download_report_document_from_url(download_url)
27
+ end
28
+
29
+ private
30
+
31
+ def download_report_document_from_url(download_url)
32
+ response = HTTP.get(download_url)
33
+
34
+ Response.wrap(response, parser:)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -77,6 +77,20 @@ module Peddler
77
77
  new(**values, country_code: country_code)
78
78
  end
79
79
  end
80
+
81
+ # Dynamically generate shorthand methods for each country code
82
+ # e.g., Marketplace.us returns the US marketplace
83
+ MARKETPLACE_IDS.each_key do |country_code|
84
+ method_name = country_code.downcase
85
+ define_method(method_name) do
86
+ find(country_code)
87
+ end
88
+ end
89
+
90
+ # Special alias for GB (Great Britain) -> UK
91
+ define_method(:gb) do
92
+ find("GB")
93
+ end
80
94
  end
81
95
 
82
96
  # @return [Peddler::Endpoint]
@@ -3,6 +3,9 @@
3
3
  require "delegate"
4
4
  require "forwardable"
5
5
 
6
+ require "peddler/config"
7
+ require "peddler/error"
8
+
6
9
  module Peddler
7
10
  # Wraps HTTP::Response to allow custom parsing
8
11
  class Response < SimpleDelegator
@@ -17,16 +20,60 @@ module Peddler
17
20
  def_delegator :to_h, :dig
18
21
 
19
22
  class << self
20
- # Decorates an HTTP::Response
23
+ # Wraps an HTTP::Response with parsing capabilities
21
24
  #
22
25
  # @param [HTTP::Response] response
23
26
  # @param [nil, #call] parser (if any)
24
- # @return [ResponseDecorator]
25
- def decorate(response, parser: nil)
26
- new(response).tap do |decorator|
27
- decorator.parser = parser
27
+ # @return [Response]
28
+ # @raise [Error] if response status indicates an error (>= 400)
29
+ def wrap(response, parser: nil)
30
+ # Check for HTTP errors and raise custom Peddler errors
31
+ if response.status >= 400
32
+ # Client errors (4xx) always raise
33
+ if response.status < 500
34
+ error = Error.build(response)
35
+ raise error || Error.new(response.status, response)
36
+ # Server errors (5xx) - check configuration
37
+ elsif Peddler.raise_on_server_errors
38
+ error = Error.build(response)
39
+ raise error || Error.new(response.status, response)
40
+ else
41
+ # Emit deprecation warning for v4 behavior
42
+ warn_about_server_error_handling
43
+ end
44
+ end
45
+
46
+ new(response).tap do |wrapper|
47
+ wrapper.parser = parser
28
48
  end
29
49
  end
50
+
51
+ # @deprecated Use {.wrap} instead
52
+ def decorate(...)
53
+ warn("Response.decorate is deprecated and will be removed in v5.0. Use Response.wrap instead.", uplevel: 1)
54
+ wrap(...)
55
+ end
56
+
57
+ private
58
+
59
+ def warn_about_server_error_handling
60
+ return if @deprecation_warned
61
+
62
+ @deprecation_warned = true
63
+
64
+ warn(<<~MSG)
65
+ [DEPRECATION] Peddler v4 behavior: Server errors (5xx) are returning response objects instead of raising exceptions.
66
+ This behavior is deprecated and will change in Peddler v5.0.
67
+
68
+ To adopt the new behavior now and silence this warning:
69
+
70
+ Peddler.configure do |config|
71
+ config.raise_on_server_errors = true
72
+ end
73
+
74
+ For more information, see: https://github.com/hakanensari/peddler/blob/main/CHANGELOG.md
75
+ MSG
76
+ end
30
77
  end
31
78
 
32
79
  # @return [#call]
data/lib/peddler/token.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "http"
4
4
 
5
- require "peddler/error"
5
+ require "peddler/response"
6
6
 
7
7
  module Peddler
8
8
  # Requests refresh and access tokens that authorize your application to take actions on behalf of a selling partner.
@@ -31,13 +31,7 @@ module Peddler
31
31
 
32
32
  def request
33
33
  response = HTTP.post(URL, form: params)
34
-
35
- if response.status.client_error?
36
- error = Error.build(response)
37
- raise error if error
38
- end
39
-
40
- response
34
+ Response.wrap(response)
41
35
  end
42
36
 
43
37
  def grant_type
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Peddler
4
- VERSION = "4.4.0"
4
+ VERSION = "4.5.0"
5
5
  end
data/lib/peddler.rb CHANGED
@@ -7,6 +7,7 @@ require "peddler/apis/application_management_2023_11_30"
7
7
  require "peddler/apis/catalog_items_2020_12_01"
8
8
  require "peddler/apis/catalog_items_2022_04_01"
9
9
  require "peddler/apis/catalog_items_v0"
10
+ require "peddler/apis/customer_feedback_2024_06_01"
10
11
  require "peddler/apis/data_kiosk_2023_11_15"
11
12
  require "peddler/apis/easy_ship_2022_03_23"
12
13
  require "peddler/apis/fba_inbound_eligibility_v1"
@@ -57,6 +58,7 @@ require "peddler/apis/vendor_invoices_v1"
57
58
  require "peddler/apis/vendor_orders_v1"
58
59
  require "peddler/apis/vendor_shipments_v1"
59
60
  require "peddler/apis/vendor_transaction_status_v1"
61
+ require "peddler/config"
60
62
  require "peddler/token"
61
63
 
62
64
  module Peddler
@@ -66,6 +68,7 @@ module Peddler
66
68
  alias_method :application_integrations, :application_integrations_2024_04_01
67
69
  alias_method :application_management, :application_management_2023_11_30
68
70
  alias_method :catalog_items, :catalog_items_2022_04_01
71
+ alias_method :customer_feedback, :customer_feedback_2024_06_01
69
72
  alias_method :data_kiosk, :data_kiosk_2023_11_15
70
73
  alias_method :easy_ship, :easy_ship_2022_03_23
71
74
  alias_method :fba_inbound_eligibility, :fba_inbound_eligibility_v1
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peddler
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hakan Ensari
@@ -13,22 +13,16 @@ dependencies:
13
13
  name: http
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '5.0'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '7.0'
18
+ version: '5.3'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
25
22
  requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- version: '5.0'
29
- - - "<"
23
+ - - "~>"
30
24
  - !ruby/object:Gem::Version
31
- version: '7.0'
25
+ version: '5.3'
32
26
  email:
33
27
  - hakanensari@gmail.com
34
28
  executables: []
@@ -46,6 +40,7 @@ files:
46
40
  - lib/peddler/apis/catalog_items_2020_12_01.rb
47
41
  - lib/peddler/apis/catalog_items_2022_04_01.rb
48
42
  - lib/peddler/apis/catalog_items_v0.rb
43
+ - lib/peddler/apis/customer_feedback_2024_06_01.rb
49
44
  - lib/peddler/apis/data_kiosk_2023_11_15.rb
50
45
  - lib/peddler/apis/easy_ship_2022_03_23.rb
51
46
  - lib/peddler/apis/fba_inbound_eligibility_v1.rb
@@ -96,9 +91,11 @@ files:
96
91
  - lib/peddler/apis/vendor_orders_v1.rb
97
92
  - lib/peddler/apis/vendor_shipments_v1.rb
98
93
  - lib/peddler/apis/vendor_transaction_status_v1.rb
94
+ - lib/peddler/config.rb
99
95
  - lib/peddler/endpoint.rb
100
96
  - lib/peddler/error.rb
101
97
  - lib/peddler/helpers/feeds_2021_06_30.rb
98
+ - lib/peddler/helpers/reports_2021_06_30.rb
102
99
  - lib/peddler/marketplace.rb
103
100
  - lib/peddler/response.rb
104
101
  - lib/peddler/token.rb