peddler 2.0.4 → 2.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -18
  3. data/lib/mws/fulfillment_inbound_shipment/client.rb +18 -0
  4. data/lib/mws/fulfillment_outbound_shipment/client.rb +0 -11
  5. data/lib/mws/reports/parser.rb +35 -0
  6. data/lib/peddler/errors/builder.rb +2 -1
  7. data/lib/peddler/errors/class_generator.rb +1 -0
  8. data/lib/peddler/flat_file_parser.rb +1 -0
  9. data/lib/peddler/headers.rb +4 -0
  10. data/lib/peddler/marketplace.rb +1 -0
  11. data/lib/peddler/operation.rb +12 -2
  12. data/lib/peddler/version.rb +1 -1
  13. data/lib/peddler/xml_parser.rb +1 -0
  14. data/lib/peddler/xml_response_parser.rb +1 -1
  15. data/test/helper.rb +1 -0
  16. data/test/integration/test_feeds.rb +3 -3
  17. data/test/integration/test_fulfillment_inbound_shipment.rb +3 -3
  18. data/test/integration/test_fulfillment_inventory.rb +2 -2
  19. data/test/integration/test_fulfillment_outbound_shipment.rb +1 -1
  20. data/test/integration/test_merchant_fulfillment.rb +57 -1
  21. data/test/integration/test_multibyte_queries.rb +1 -1
  22. data/test/integration/test_mws_headers.rb +1 -1
  23. data/test/integration/test_off_amazon_payments.rb +1 -1
  24. data/test/integration/test_orders.rb +2 -2
  25. data/test/integration/test_products.rb +12 -12
  26. data/test/integration/test_recommendations.rb +2 -2
  27. data/test/integration/test_reports.rb +6 -6
  28. data/test/integration/test_sellers.rb +1 -1
  29. data/test/integration/test_subscriptions.rb +3 -3
  30. data/test/integration_helper.rb +9 -7
  31. data/test/unit/mws/test_feeds_client.rb +6 -6
  32. data/test/unit/mws/test_finances_client.rb +5 -5
  33. data/test/unit/mws/test_fulfillment_inbound_shipment_client.rb +40 -24
  34. data/test/unit/mws/test_fulfillment_inventory_client.rb +3 -3
  35. data/test/unit/mws/test_fulfillment_outbound_shipment_client.rb +11 -11
  36. data/test/unit/mws/test_merchant_fulfillment_client.rb +5 -5
  37. data/test/unit/mws/test_off_amazon_payments_client.rb +17 -17
  38. data/test/unit/mws/test_orders_client.rb +7 -7
  39. data/test/unit/mws/test_products_client.rb +15 -15
  40. data/test/unit/mws/test_recommendations_client.rb +4 -4
  41. data/test/unit/mws/test_reports_client.rb +14 -14
  42. data/test/unit/mws/test_sellers_client.rb +3 -3
  43. data/test/unit/mws/test_subscriptions_client.rb +11 -11
  44. data/test/unit/peddler/errors/test_builder.rb +4 -4
  45. data/test/unit/peddler/errors/test_class_generator.rb +1 -1
  46. data/test/unit/peddler/errors/test_error.rb +5 -5
  47. data/test/unit/peddler/errors/test_parser.rb +4 -4
  48. data/test/unit/peddler/test_client.rb +20 -20
  49. data/test/unit/peddler/test_flat_file_parser.rb +11 -11
  50. data/test/unit/peddler/test_headers.rb +1 -1
  51. data/test/unit/peddler/test_marketplace.rb +1 -1
  52. data/test/unit/peddler/test_operation.rb +18 -12
  53. data/test/unit/peddler/test_parser.rb +3 -3
  54. data/test/unit/peddler/test_structured_list.rb +5 -5
  55. data/test/unit/peddler/test_vcr_matcher.rb +4 -4
  56. data/test/unit/peddler/test_xml_parser.rb +3 -3
  57. data/test/unit/peddler/test_xml_response_parser.rb +4 -4
  58. data/test/unit/test_mws.rb +1 -1
  59. data/test/vcr_cassettes/FulfillmentInboundShipment.yml +36 -36
  60. data/test/vcr_cassettes/MerchantFulfillment.yml +309 -1
  61. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1496350487deacc5345571d16968c6a0c9dca63692e2f915decb8b28ab613344
4
- data.tar.gz: 68bc82232267280f4d1c07ee70299c2167d97f5e736d2a74116af6bb7256489b
3
+ metadata.gz: 7f41b36a62c193efceb4d464ce3447e4934c8ca97907ca6940c431914f0b1406
4
+ data.tar.gz: f28498baf2e3c8aa67bd983e74f9c4ca4daffcb15424a25ededee6b72fa3606a
5
5
  SHA512:
6
- metadata.gz: 8cd79c46a5272e0aeb344d08c3f2e7d484dda9b037d1994ea113359a18fec63ee83a6e6f22a0997d60681751eb41f7d889096af2be9b1caa984b9a9ddd80afd7
7
- data.tar.gz: 7af09716baa2480fb55e0a70984e7d9d1cc738e3d2ee1bd1ccfcb52868d7d7be953d4dfbcca5c7eb68de19abf4a0794a3b095e6177d7cbf1dd0faf549aa1ff3e
6
+ metadata.gz: b62d1f3d5eec15b6e2a7d3fee02445a3edc2596a6c8bafbf25deac633a01f401467cbe7d4214a4e1bf6a3b8958ec437b4ac1ed1f7d5a1c7f3638cdfcb80815ba
7
+ data.tar.gz: d5c7e57ed43bdf7f1aeba013a499d10b5bdf18361b1f5edf2e25eb82703ddec81eafe596aa0ae57fc920a1591d5ab399ef2c7093bb350c1f319e3e3709a857f8
data/README.md CHANGED
@@ -8,9 +8,7 @@
8
8
 
9
9
  To use Amazon MWS, you must have an eligible seller account and register as an [application developer](https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Registering.html#DG_Registering__RegisteringAsADeveloper).
10
10
 
11
- Amazon has [multiple regions](https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Endpoints.html). Each region requires application developers to register individually.
12
-
13
- Some MWS API sections may require additional authorisation from Amazon.
11
+ Amazon has [multiple regions](https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Endpoints.html). Each region requires application developers to register individually. Some MWS API sections may require additional authorisation from Amazon.
14
12
 
15
13
  ![Peddler](http://f.cl.ly/items/1G1N0A0a0v3a3h241c1Q/peddler.jpg)
16
14
 
@@ -22,7 +20,7 @@ Require the library.
22
20
  require "peddler"
23
21
  ```
24
22
 
25
- A client requires the AWS credentials of the application developer. If you are working within a single MWS region, you can set them globally in your shell.
23
+ A client requires the AWS credentials of the application developer. If you are working in a single MWS region, you can set them globally.
26
24
 
27
25
 
28
26
  ```bash
@@ -30,32 +28,28 @@ export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID
30
28
  export AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
31
29
  ```
32
30
 
33
- Now, you can create a client with the Amazon marketplace you as application developer signed up on and a merchant ID. Peddler provides a class for each API section under an eponymous namespace.
31
+ Now, create a client with the Amazon marketplace you signed up on and a merchant ID. Peddler provides a class for each API section under an eponymous namespace.
34
32
 
35
33
  ```ruby
36
- MWS::Orders::Client.new(marketplace: "ATVPDKIKX0DER",
37
- merchant_id: "123")
38
-
39
- # or the shorthand
40
- MWS.orders(marketplace: "ATVPDKIKX0DER",
41
- merchant_id: "123")
34
+ client = MWS.orders(marketplace: "ATVPDKIKX0DER",
35
+ merchant_id: "123")
42
36
 
43
- # another shortcut
44
- MWS.orders(marketplace: "US",
45
- merchant_id: "123")
37
+ # or a shorthand
38
+ client = MWS.orders(marketplace: "US",
39
+ merchant_id: "123")
46
40
  ```
47
41
 
48
42
  If you are creating a [client for another seller](https://developer.amazonservices.com/gp/mws/faq.html#developForSeller), pass an MWS Auth Token as well.
49
43
 
50
44
  ```ruby
51
- MWS.orders(marketplace: "ATVPDKIKX0DER",
52
- merchant_id: "123",
53
- auth_token: "123")
45
+ client = MWS.orders(marketplace: "ATVPDKIKX0DER",
46
+ merchant_id: "123",
47
+ auth_token: "123")
54
48
  ```
55
49
 
56
50
  You won't be able to create a client for another seller if you are in different regions.
57
51
 
58
- If you are working with sellers across multiple regions, a single set of credentials will not be enough. In that case, you can skip using global environment variables and pass your AWS credentials when creating the client.
52
+ If you are working with sellers across multiple regions, a single set of credentials will not be enough. In that case, do not use global environment variables and pass your AWS credentials when creating the client.
59
53
 
60
54
  ```ruby
61
55
  client = MWS.orders(marketplace: "ATVPDKIKX0DER",
@@ -152,6 +152,14 @@ module MWS
152
152
  .structure!('ASINList', 'Id')
153
153
 
154
154
  run
155
+ # Work around a bug upstream
156
+ #
157
+ # @see https://github.com/hakanensari/peddler/issues/122
158
+ rescue Peddler::Errors::Error => error
159
+ raise unless error.message.include?("Value null at 'asinList'")
160
+
161
+ get_prep_instructions_for_asin_with_bad_params(ship_to_country_code,
162
+ *asin_list)
155
163
  end
156
164
 
157
165
  # Sends transportation information to Amazon about an inbound shipment
@@ -365,6 +373,16 @@ module MWS
365
373
  .structure!('InboundShipmentItems', 'member')
366
374
  .structure!('PrepDetailsList', 'member')
367
375
  end
376
+
377
+ def get_prep_instructions_for_asin_with_bad_params(_ship_to_country_code,
378
+ *asin_list)
379
+ operation
380
+ .add('AsinList' => asin_list)
381
+ .structure!('AsinList', 'Id')
382
+ .delete_if { |key, _val| key.include?('ASINList') }
383
+
384
+ run
385
+ end
368
386
  end
369
387
  end
370
388
  end
@@ -28,12 +28,6 @@ module MWS
28
28
  # @option opts [Boolean] :include_cod_fulfillment_preview
29
29
  # @return [Peddler::XMLParser]
30
30
  def get_fulfillment_preview(address, items, opts = {})
31
- opts = opts.dup
32
- if opts.key?(:include_cod_fulfillment_preview)
33
- opts['IncludeCODFulfillmentPreview'] =
34
- opts.delete(:include_cod_fulfillment_preview)
35
- end
36
-
37
31
  operation('GetFulfillmentPreview')
38
32
  .add(opts)
39
33
  .add('Address' => address, 'Items' => items)
@@ -66,11 +60,6 @@ module MWS
66
60
  displayable_order_comment,
67
61
  shipping_speed_category,
68
62
  destination_address, items, opts = {})
69
- opts = opts.dup
70
- if opts.key?(:cod_settings)
71
- opts['CODSettings'] = opts.delete(:cod_settings)
72
- end
73
-
74
63
  operation('CreateFulfillmentOrder')
75
64
  .add(opts)
76
65
  .add('SellerFulfillmentOrderId' => seller_fulfillment_order_id,
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'peddler/flat_file_parser'
4
+ require 'peddler/xml_response_parser'
5
+
6
+ module MWS
7
+ module Reports
8
+ # @api private
9
+ module Parser
10
+ class << self
11
+ # We're massaging data produced by a motley army of developers. It's
12
+ # messy.
13
+ def new(res, encoding)
14
+ # Don't parse if there's no body
15
+ return res unless res.body
16
+
17
+ if xml?(res)
18
+ XMLResponseParser.new(res)
19
+ else
20
+ # Amazon returns a variety of content types for flat files. I simply
21
+ # assume anything not XML is a flat file.
22
+ FlatFileParser.new(res, encoding)
23
+ end
24
+ end
25
+
26
+ def xml?(res)
27
+ return true if res.headers['Content-Type'].start_with?('text/xml')
28
+ return true if res.body.start_with?('<?xml')
29
+
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -11,7 +11,7 @@ module Peddler
11
11
  class Builder
12
12
  extend Forwardable
13
13
 
14
- DIGIT_AS_FIRST_CHAR = /^\d/
14
+ DIGIT_AS_FIRST_CHAR = /^\d/.freeze
15
15
  private_constant :DIGIT_AS_FIRST_CHAR
16
16
 
17
17
  def_delegator :error, :response
@@ -29,6 +29,7 @@ module Peddler
29
29
  def build
30
30
  parse_original_response
31
31
  return if bad_class_name?
32
+
32
33
  error_class.new(error_message, error)
33
34
  end
34
35
 
@@ -20,6 +20,7 @@ module Peddler
20
20
  def generate(name)
21
21
  with_mutex do
22
22
  return Errors.const_get(name) if Errors.const_defined?(name)
23
+
23
24
  Errors.const_set(name, Class.new(Error))
24
25
  end
25
26
  end
@@ -38,6 +38,7 @@ module Peddler
38
38
 
39
39
  def scrub_body!(encoding)
40
40
  return if body.encoding == Encoding::UTF_8
41
+
41
42
  self.body = body.dup.force_encoding(encoding)
42
43
  end
43
44
 
@@ -7,6 +7,7 @@ module Peddler
7
7
  # @return [Integer, nil]
8
8
  def mws_quota_max
9
9
  return unless headers['x-mws-quota-max']
10
+
10
11
  headers['x-mws-quota-max'].to_i
11
12
  end
12
13
 
@@ -14,6 +15,7 @@ module Peddler
14
15
  # @return [Integer, nil]
15
16
  def mws_quota_remaining
16
17
  return unless headers['x-mws-quota-remaining']
18
+
17
19
  headers['x-mws-quota-remaining'].to_i
18
20
  end
19
21
 
@@ -21,6 +23,7 @@ module Peddler
21
23
  # @return [Time, nil]
22
24
  def mws_quota_resets_on
23
25
  return unless headers['x-mws-quota-resetsOn']
26
+
24
27
  Time.parse(headers['x-mws-quota-resetsOn'])
25
28
  end
26
29
 
@@ -34,6 +37,7 @@ module Peddler
34
37
  # @return [Time, nil]
35
38
  def mws_timestamp
36
39
  return unless headers['x-mws-timestamp']
40
+
37
41
  Time.parse(headers['x-mws-timestamp'])
38
42
  end
39
43
 
@@ -63,6 +63,7 @@ module Peddler
63
63
  ['A13V1IB3VIYZZH', 'FR', 'mws-eu.amazonservices.com'],
64
64
  ['APJ6JRA9NG5V4', 'IT', 'mws-eu.amazonservices.com'],
65
65
  ['A1F83G8C2ARO7P', 'GB', 'mws-eu.amazonservices.com'],
66
+ ['A33AVAJ2PDY3EV', 'TR', 'mws-eu.amazonservices.com'],
66
67
  ['A21TJRUUN4KGV', 'IN', 'mws.amazonservices.in'],
67
68
  ['A39IBJ37TRP1C6', 'AU', 'mws.amazonservices.com.au'],
68
69
  ['A1VC38T7YXB528', 'JP', 'mws.amazonservices.jp'],
@@ -7,7 +7,9 @@ require 'peddler/structured_list'
7
7
  module Peddler
8
8
  # @api private
9
9
  class Operation < SimpleDelegator
10
- CAPITAL_LETTERS = /[A-Z]/
10
+ CAPITAL_LETTERS = /[A-Z]/.freeze
11
+ ALL_CAPS = %w[sku cod].freeze
12
+ private_constant :CAPITAL_LETTERS, :ALL_CAPS
11
13
 
12
14
  def initialize(action)
13
15
  super('Action' => action)
@@ -55,8 +57,16 @@ module Peddler
55
57
  sym
56
58
  .to_s
57
59
  .split('_')
58
- .map { |token| token == 'sku' ? 'SKU' : token.capitalize }
60
+ .map { |token| capitalize(token) }
59
61
  .join
60
62
  end
63
+
64
+ def capitalize(word)
65
+ if ALL_CAPS.any? { |val| val == word }
66
+ word.upcase
67
+ else
68
+ word.capitalize
69
+ end
70
+ end
61
71
  end
62
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Peddler
4
- VERSION = '2.0.4'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -25,6 +25,7 @@ module Peddler
25
25
 
26
26
  def valid?
27
27
  return unless headers['Content-Length']
28
+
28
29
  headers['Content-Length'].to_i == body.size
29
30
  end
30
31
 
@@ -5,7 +5,7 @@ require 'peddler/xml_parser'
5
5
  module Peddler
6
6
  # @api private
7
7
  class XMLResponseParser < XMLParser
8
- MATCHER = /^Message$|Report|Result/
8
+ MATCHER = /^Message$|Report|Result/.freeze
9
9
  private_constant :MATCHER
10
10
 
11
11
  def next_token
data/test/helper.rb CHANGED
@@ -11,4 +11,5 @@ require 'minitest/focus'
11
11
  begin
12
12
  require 'pry'
13
13
  rescue LoadError
14
+ nil
14
15
  end
@@ -4,21 +4,21 @@ require 'integration_helper'
4
4
  require 'mws/feeds'
5
5
 
6
6
  class TestFeeds < IntegrationTest
7
- def test_gets_feed_submission_count
7
+ def test_getting_feed_submission_count
8
8
  clients.each do |client|
9
9
  res = client.get_feed_submission_count
10
10
  refute_empty res.parse
11
11
  end
12
12
  end
13
13
 
14
- def test_gets_feed_submission_list
14
+ def test_getting_feed_submission_list
15
15
  clients.each do |client|
16
16
  res = client.get_feed_submission_list
17
17
  refute_empty res.parse
18
18
  end
19
19
  end
20
20
 
21
- def test_submits_feeds
21
+ def test_submitting_feeds
22
22
  feed_content = "sku\tprice\tquantity\nwidget\t\t0\n"
23
23
  feed_type = '_POST_FLAT_FILE_PRICEANDQUANTITYONLY_UPDATE_DATA_'
24
24
 
@@ -8,7 +8,7 @@ class TestFulfillmentInboundShipment < IntegrationTest
8
8
  :postal_code, :country_code)
9
9
  Item = Struct.new(:seller_sku, :quantity)
10
10
 
11
- def test_creates_inbound_shipment_plan
11
+ def test_creating_inbound_shipment_plan
12
12
  address = Address.new('John', '1 Main St', 'New York', 'NY', '10001', 'US')
13
13
  item = Item.new('123', 1)
14
14
  clients.each do |client|
@@ -17,14 +17,14 @@ class TestFulfillmentInboundShipment < IntegrationTest
17
17
  end
18
18
  end
19
19
 
20
- def test_gets_service_status
20
+ def test_getting_service_status
21
21
  clients.each do |client|
22
22
  res = client.get_service_status
23
23
  refute_empty res.parse
24
24
  end
25
25
  end
26
26
 
27
- def test_handles_large_requests
27
+ def test_handling_large_requests
28
28
  address = Address.new('John', '1 Main St', 'New York', 'NY', '10001', 'US')
29
29
  items = Array.new(100) { |i| Item.new(i, 1) }
30
30
  clients.each do |client|
@@ -4,14 +4,14 @@ require 'integration_helper'
4
4
  require 'mws/fulfillment_inventory'
5
5
 
6
6
  class TestFulfillmentInventory < IntegrationTest
7
- def test_lists_inventory_supply
7
+ def test_listing_inventory_supply
8
8
  clients.each do |client|
9
9
  res = client.list_inventory_supply(query_start_date_time: Date.today - 30)
10
10
  refute_empty res.parse
11
11
  end
12
12
  end
13
13
 
14
- def test_gets_service_status
14
+ def test_getting_service_status
15
15
  clients.each do |client|
16
16
  res = client.get_service_status
17
17
  refute_empty res.parse
@@ -4,7 +4,7 @@ require 'integration_helper'
4
4
  require 'mws/fulfillment_outbound_shipment'
5
5
 
6
6
  class TestFulfillmentOutboundShipment < IntegrationTest
7
- def test_gets_service_status
7
+ def test_getting_service_status
8
8
  clients.each do |client|
9
9
  res = client.get_service_status
10
10
  refute_empty res.parse
@@ -4,10 +4,66 @@ require 'integration_helper'
4
4
  require 'mws/merchant_fulfillment'
5
5
 
6
6
  class TestMerchantFulfillment < IntegrationTest
7
- def test_gets_service_status
7
+ def test_getting_eligible_shipments_in_the_us
8
+ client = clients.us
9
+ res = client.get_eligible_shipping_services(shipment_request_details)
10
+ refute res.dig('ShippingServiceList', 'ShippingService').count.zero?
11
+ end
12
+
13
+ def test_shipping_in_the_us
14
+ client = clients.us
15
+ res = client.create_shipment(shipment_request_details, 'UPS_PTP_GND')
16
+ label = res.dig('Shipment', 'Label')
17
+ data_compressed = Base64.decode64(label['FileContents']['Contents'])
18
+ # data = Zlib.gunzip(data_compressed)
19
+ data = Zlib::GzipReader.new(StringIO.new(data_compressed)).read
20
+ assert_equal label['FileContents']['Checksum'], Digest::MD5.base64digest(data)
21
+ res = client.cancel_shipment(res.dig('Shipment', 'ShipmentId'))
22
+ assert_equal 'RefundPending', res.dig('Shipment', 'Status')
23
+ end
24
+
25
+ def test_getting_service_status
8
26
  clients.each do |client|
9
27
  res = client.get_service_status
10
28
  refute_empty res.parse
11
29
  end
12
30
  end
31
+
32
+ private
33
+
34
+ def shipment_request_details
35
+ {
36
+ amazon_order_id: '123-1234567-1234567',
37
+ item_list: [
38
+ {
39
+ order_item_id: '12345678901234',
40
+ quantity: 1
41
+ }
42
+ ],
43
+ ship_from_address: {
44
+ name: 'John Doe',
45
+ address_line_1: '10 Jay St',
46
+ email: 'john@example.com',
47
+ city: 'Brooklyn',
48
+ state_or_province_code: 'NY',
49
+ postal_code: '11201',
50
+ country_code: 'US',
51
+ phone: '7181231234'
52
+ },
53
+ package_dimensions: {
54
+ length: 40,
55
+ width: 30,
56
+ height: 10,
57
+ unit: 'centimeters'
58
+ },
59
+ weight: {
60
+ value: 1000,
61
+ unit: 'grams'
62
+ },
63
+ shipping_service_options: {
64
+ carrier_will_pick_up: false,
65
+ delivery_experience: 'DeliveryConfirmationWithoutSignature'
66
+ }
67
+ }
68
+ end
13
69
  end