peddler 2.0.4 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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