sp_api_clients 2.0.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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +161 -0
  3. data/LICENSE +21 -0
  4. data/README.md +284 -0
  5. data/lib/amazon_sp_clients/adapter_loader.rb +28 -0
  6. data/lib/amazon_sp_clients/api_client.rb +309 -0
  7. data/lib/amazon_sp_clients/api_error.rb +37 -0
  8. data/lib/amazon_sp_clients/api_response.rb +54 -0
  9. data/lib/amazon_sp_clients/configuration.rb +144 -0
  10. data/lib/amazon_sp_clients/marketplaces.rb +63 -0
  11. data/lib/amazon_sp_clients/middlewares/raise_error.rb +166 -0
  12. data/lib/amazon_sp_clients/session.rb +124 -0
  13. data/lib/amazon_sp_clients/sp_fba_inventory.rb +8 -0
  14. data/lib/amazon_sp_clients/sp_feeds_2021.rb +8 -0
  15. data/lib/amazon_sp_clients/sp_fulfillment_outbound_2020.rb +8 -0
  16. data/lib/amazon_sp_clients/sp_listings_items_2021.rb +8 -0
  17. data/lib/amazon_sp_clients/sp_orders_v0.rb +8 -0
  18. data/lib/amazon_sp_clients/sp_reports_2021.rb +8 -0
  19. data/lib/amazon_sp_clients/sp_tokens_2021.rb +8 -0
  20. data/lib/amazon_sp_clients/sp_vdf_inventory_v1.rb +8 -0
  21. data/lib/amazon_sp_clients/sp_vdf_orders_v1.rb +8 -0
  22. data/lib/amazon_sp_clients/sp_vdf_shipping_v1.rb +8 -0
  23. data/lib/amazon_sp_clients/sp_vendor_invoices.rb +8 -0
  24. data/lib/amazon_sp_clients/sp_vendor_orders.rb +8 -0
  25. data/lib/amazon_sp_clients/sp_vendor_transaction_status.rb +8 -0
  26. data/lib/amazon_sp_clients/sp_vendors_shipments.rb +8 -0
  27. data/lib/amazon_sp_clients/token_exchange_auth.rb +82 -0
  28. data/lib/amazon_sp_clients/uploader.rb +77 -0
  29. data/lib/amazon_sp_clients/v2/api.rb +43 -0
  30. data/lib/amazon_sp_clients/v2/apis/fba_inventory.rb +80 -0
  31. data/lib/amazon_sp_clients/v2/apis/feeds_2021.rb +86 -0
  32. data/lib/amazon_sp_clients/v2/apis/fulfillment_outbound_2020.rb +184 -0
  33. data/lib/amazon_sp_clients/v2/apis/listings_items_2021.rb +144 -0
  34. data/lib/amazon_sp_clients/v2/apis/orders_v0.rb +165 -0
  35. data/lib/amazon_sp_clients/v2/apis/reports_2021.rb +117 -0
  36. data/lib/amazon_sp_clients/v2/apis/tokens_2021.rb +25 -0
  37. data/lib/amazon_sp_clients/v2/apis/vdf_inventory_v1.rb +26 -0
  38. data/lib/amazon_sp_clients/v2/apis/vdf_orders_v1.rb +61 -0
  39. data/lib/amazon_sp_clients/v2/apis/vdf_shipping_v1.rb +139 -0
  40. data/lib/amazon_sp_clients/v2/apis/vendor_invoices.rb +25 -0
  41. data/lib/amazon_sp_clients/v2/apis/vendor_orders.rb +106 -0
  42. data/lib/amazon_sp_clients/v2/apis/vendor_transaction_status.rb +25 -0
  43. data/lib/amazon_sp_clients/v2/apis/vendors_shipments.rb +120 -0
  44. data/lib/amazon_sp_clients/v2/apis.rb +82 -0
  45. data/lib/amazon_sp_clients/v2/client.rb +198 -0
  46. data/lib/amazon_sp_clients/v2/config.rb +85 -0
  47. data/lib/amazon_sp_clients/v2/credentials.rb +91 -0
  48. data/lib/amazon_sp_clients/v2/documents.rb +115 -0
  49. data/lib/amazon_sp_clients/v2/error_mapper.rb +235 -0
  50. data/lib/amazon_sp_clients/v2/errors.rb +117 -0
  51. data/lib/amazon_sp_clients/v2/lwa.rb +100 -0
  52. data/lib/amazon_sp_clients/v2/rdt.rb +104 -0
  53. data/lib/amazon_sp_clients/v2/token.rb +39 -0
  54. data/lib/amazon_sp_clients/v2.rb +33 -0
  55. data/lib/amazon_sp_clients/version.rb +3 -0
  56. data/lib/amazon_sp_clients.rb +69 -0
  57. data/lib/sp_api_clients.rb +6 -0
  58. data/sp_api_clients.gemspec +53 -0
  59. data/vendor/fba_inventory/lib/sp_fba_inventory/api/fba_inventory_api.rb +289 -0
  60. data/vendor/fba_inventory/lib/sp_fba_inventory.rb +12 -0
  61. data/vendor/feeds_2021/lib/sp_feeds_2021/api/feeds_api.rb +367 -0
  62. data/vendor/feeds_2021/lib/sp_feeds_2021.rb +12 -0
  63. data/vendor/fulfillment_outbound_2020/lib/sp_fulfillment_outbound_2020/api/fba_outbound_api.rb +845 -0
  64. data/vendor/fulfillment_outbound_2020/lib/sp_fulfillment_outbound_2020.rb +12 -0
  65. data/vendor/listings_items_2021/lib/sp_listings_items_2021/api/listings_api.rb +480 -0
  66. data/vendor/listings_items_2021/lib/sp_listings_items_2021.rb +12 -0
  67. data/vendor/orders_v0/lib/sp_orders_v0/api/orders_v0_api.rb +593 -0
  68. data/vendor/orders_v0/lib/sp_orders_v0/api/shipment_api.rb +83 -0
  69. data/vendor/orders_v0/lib/sp_orders_v0.rb +13 -0
  70. data/vendor/reports_2021/lib/sp_reports_2021/api/reports_api.rb +530 -0
  71. data/vendor/reports_2021/lib/sp_reports_2021.rb +12 -0
  72. data/vendor/tokens_2021/lib/sp_tokens_2021/api/tokens_api.rb +77 -0
  73. data/vendor/tokens_2021/lib/sp_tokens_2021.rb +12 -0
  74. data/vendor/vdf_inventory_v1/lib/sp_vdf_inventory_v1/api/update_inventory_api.rb +83 -0
  75. data/vendor/vdf_inventory_v1/lib/sp_vdf_inventory_v1.rb +12 -0
  76. data/vendor/vdf_orders_v1/lib/sp_vdf_orders_v1/api/vendor_orders_api.rb +217 -0
  77. data/vendor/vdf_orders_v1/lib/sp_vdf_orders_v1.rb +12 -0
  78. data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/customer_invoices_api.rb +152 -0
  79. data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/vendor_shipping_api.rb +264 -0
  80. data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/vendor_shipping_labels_api.rb +208 -0
  81. data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1.rb +14 -0
  82. data/vendor/vendor_invoices/lib/sp_vendor_invoices/api/vendor_payments_api.rb +77 -0
  83. data/vendor/vendor_invoices/lib/sp_vendor_invoices.rb +12 -0
  84. data/vendor/vendor_orders/lib/sp_vendor_orders/api/vendor_orders_api.rb +323 -0
  85. data/vendor/vendor_orders/lib/sp_vendor_orders.rb +12 -0
  86. data/vendor/vendor_transaction_status/lib/sp_vendor_transaction_status/api/vendor_transaction_api.rb +75 -0
  87. data/vendor/vendor_transaction_status/lib/sp_vendor_transaction_status.rb +12 -0
  88. data/vendor/vendors_shipments/lib/sp_vendors_shipments/api/vendor_shipping_api.rb +337 -0
  89. data/vendor/vendors_shipments/lib/sp_vendors_shipments.rb +12 -0
  90. metadata +321 -0
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ # Faraday::RetriableResponse (raised on 429) lives in the faraday-retry
5
+ # gem under Faraday 2; a no-op under Faraday 1, which loads it itself.
6
+ require 'faraday/retry'
7
+ require 'json'
8
+
9
+ # Based on https://github.com/lostisland/faraday/blob/main/lib/faraday/response/raise_error.rb
10
+ module AmazonSpClients
11
+ module Middlewares
12
+ # RaiseError is a Faraday middleware that raises exceptions on common HTTP
13
+ # client or server error responses.
14
+ # Subclasses Faraday::Middleware (not Faraday::Response::Middleware,
15
+ # which Faraday 2 removed) so one class works on Faraday 1.10 and 2.x.
16
+ class RaiseError < Faraday::Middleware
17
+ # rubocop:disable Naming/ConstantName
18
+ ClientErrorStatuses = (400...500)
19
+ ServerErrorStatuses = (500...600)
20
+
21
+ # rubocop:enable Naming/ConstantName
22
+
23
+ VALID_SERVICE = %i[token spapi uploads].freeze
24
+
25
+ # The error payload ends up in consumer logs; keep secrets out.
26
+ FILTERED = '[FILTERED]'
27
+ SENSITIVE_HEADERS = %w[authorization x-amz-access-token x-amz-security-token].freeze
28
+
29
+ def initialize(app, options = {})
30
+ super
31
+ @service = options.fetch(:service)
32
+ raise unless VALID_SERVICE.include?(@service)
33
+ end
34
+
35
+ def on_complete(env)
36
+ case env[:status]
37
+ when 400
38
+ raise Faraday::BadRequestError.new(
39
+ error_message(env),
40
+ response_values(env)
41
+ )
42
+ when 401
43
+ raise Faraday::UnauthorizedError.new(
44
+ error_message(env),
45
+ response_values(env)
46
+ )
47
+ when 403
48
+ raise Faraday::ForbiddenError.new(
49
+ error_message(env),
50
+ response_values(env)
51
+ )
52
+ when 404
53
+ raise Faraday::ResourceNotFound.new(
54
+ error_message(env),
55
+ response_values(env)
56
+ )
57
+ when 407
58
+ # mimic the behavior that we get with proxy requests with HTTPS
59
+ msg = '407 "Proxy Authentication Required"'
60
+ raise Faraday::ProxyAuthError.new(msg, response_values(env))
61
+ when 409
62
+ raise Faraday::ConflictError.new(
63
+ error_message(env),
64
+ response_values(env)
65
+ )
66
+ when 422
67
+ raise Faraday::UnprocessableEntityError.new(
68
+ error_message(env),
69
+ response_values(env)
70
+ )
71
+ when 429
72
+ # This assumes you will handle throttling/retires yourself
73
+ raise Faraday::RetriableResponse.new(
74
+ error_message(env),
75
+ response_values(env)
76
+ )
77
+ when ClientErrorStatuses
78
+ raise Faraday::ClientError.new(
79
+ error_message(env),
80
+ response_values(env)
81
+ )
82
+ when ServerErrorStatuses
83
+ raise Faraday::ServerError.new(
84
+ error_message(env),
85
+ response_values(env)
86
+ )
87
+ when nil
88
+ raise Faraday::NilStatusError.new(
89
+ error_message(env),
90
+ response_values(env)
91
+ )
92
+ end
93
+ end
94
+
95
+ def error_message(env)
96
+ body = env.body
97
+
98
+ unless !body.nil? && !body.empty?
99
+ return "the server responded with status #{env[:status]} (no body)"
100
+ end
101
+
102
+ case @service
103
+ when :token
104
+ body = ::JSON.parse(body) if body.is_a?(String)
105
+
106
+ "Service 'token' ERR: error: #{body['error']} description: #{body['error_description']}"
107
+ when :spapi
108
+ body = ::JSON.parse(body, symbolize_names: true) if body.is_a?(String)
109
+
110
+ err = AmazonSpClients::ApiError.new(body)
111
+
112
+ "Service 'spapi' ERR: #{err.full_messages}"
113
+ else
114
+ "the server responded with status #{env[:status]}"
115
+ end
116
+ rescue StandardError => e
117
+ "the server responded with status #{env[:status]} (#{e})"
118
+ end
119
+
120
+ def response_values(env)
121
+ {
122
+ service: @service,
123
+ response: {
124
+ status: env.status,
125
+ headers: env.response_headers,
126
+ body: env.body
127
+ },
128
+ request: {
129
+ method: env.method,
130
+ url_path: env.url.path,
131
+ url: redacted_url(env),
132
+ headers: redacted_request_headers(env.request_headers),
133
+ body: redacted_request_body(env)
134
+ }
135
+ }
136
+ end
137
+
138
+ private
139
+
140
+ # Returns a copy that keeps Faraday's case-insensitive lookup.
141
+ def redacted_request_headers(headers)
142
+ redacted = Faraday::Utils::Headers.new(headers || {})
143
+ SENSITIVE_HEADERS.each do |name|
144
+ redacted[name] = FILTERED unless redacted[name].nil?
145
+ end
146
+ redacted
147
+ end
148
+
149
+ # Presigned S3 urls (uploads service) carry their credential in
150
+ # the query string.
151
+ def redacted_url(env)
152
+ return env.url unless @service == :uploads
153
+
154
+ env.url.dup.tap { |url| url.query = nil }
155
+ end
156
+
157
+ # The LWA token request body carries client_secret and
158
+ # refresh_token as form fields.
159
+ def redacted_request_body(env)
160
+ return FILTERED if @service == :token
161
+
162
+ env.request_body
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'amazon_sp_clients/sp_tokens_2021'
5
+
6
+ module AmazonSpClients
7
+ class Session
8
+ RESTRICTED_OPS = {
9
+ orders: {
10
+ restrictedResources: [
11
+ { method: 'GET', path: '/orders/v0/orders', dataElements: %w[buyerInfo shippingAddress] }
12
+ ]
13
+ },
14
+ orders_and_items: {
15
+ restrictedResources: [
16
+ { method: 'GET', path: '/orders/v0/orders', dataElements: %w[buyerInfo shippingAddress] },
17
+ { method: 'GET', path: '/orders/v0/orders/{orderId}/orderItems',
18
+ dataElements: ['buyerInfo'] }
19
+ ]
20
+ }
21
+ }.freeze
22
+
23
+ attr_reader :access_token, :restricted_data_token
24
+
25
+ def initialize(config = Configuration.default, &block)
26
+ @config = config
27
+
28
+ @refresh_token = nil
29
+ @access_token = nil
30
+ @access_token_expires_at = nil
31
+ @restricted_data_token = {}
32
+ @restricted_data_token_expires_at = {}
33
+ @grantless = false
34
+ @scope = nil
35
+
36
+ @callback = block
37
+ end
38
+
39
+ def with_callback(&block)
40
+ @callback = block
41
+ self
42
+ end
43
+
44
+ # @return [self]
45
+ def authenticate(refresh_token)
46
+ @refresh_token = refresh_token
47
+ @grantless = false
48
+ @scope = nil
49
+
50
+ request_access_token
51
+ self
52
+ end
53
+
54
+ # @return [self]
55
+ def authenticate_grantless(scope)
56
+ @grantless = true
57
+ @scope = scope
58
+
59
+ request_access_token
60
+ self
61
+ end
62
+
63
+ def refresh
64
+ if @callback
65
+ @access_token = @callback.call
66
+ @access_token_expires_at = nil
67
+ elsif @grantless
68
+ authenticate_grantless(@scope)
69
+ elsif !@refresh_token.nil?
70
+ authenticate(@refresh_token)
71
+ end
72
+ end
73
+
74
+ def ask_for_restricted_data_token(restricted_resource)
75
+ if !@restricted_data_token[restricted_resource].nil? &&
76
+ !expired?(@restricted_data_token_expires_at[restricted_resource])
77
+ return
78
+ end
79
+
80
+ tokens_api = AmazonSpClients::SpTokens2021::TokensApi.new(self)
81
+
82
+ token_params = if restricted_resource.is_a?(Symbol)
83
+ RESTRICTED_OPS.fetch(restricted_resource)
84
+ else
85
+ { restrictedResources: [restricted_resource] }
86
+ end
87
+ # TODO: handle errors for restricted_data_token request!
88
+ tokens_resp = tokens_api.create_restricted_data_token(token_params)
89
+
90
+ @restricted_data_token_expires_at[restricted_resource] =
91
+ duration_to_time(tokens_resp.payload[:expiresIn])
92
+ @restricted_data_token[restricted_resource] = tokens_resp.payload[:restrictedDataToken]
93
+ end
94
+
95
+ private
96
+
97
+ # Returns nil on success, error struct on error
98
+ def request_access_token
99
+ return if @access_token && !expired?(@access_token_expires_at)
100
+
101
+ resp_struct = exchange_token_request
102
+ @access_token = resp_struct.access_token
103
+ @refresh_token = resp_struct.refresh_token
104
+ @access_token_expires_at = duration_to_time(resp_struct.expires_in)
105
+ end
106
+
107
+ def exchange_token_request
108
+ auth = AmazonSpClients::TokenExchangeAuth.new(@refresh_token)
109
+
110
+ @grantless ? auth.exchange('client_credentials', @scope) : auth.exchange('refresh_token')
111
+ end
112
+
113
+ def expired?(expires)
114
+ return true if expires.nil?
115
+
116
+ # Shorten expiration time by 60s as a safety net.
117
+ Time.now.utc >= expires - 60
118
+ end
119
+
120
+ def duration_to_time(seconds)
121
+ Time.now.utc + seconds.to_i
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/fba_inventory/lib/sp_fba_inventory.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/feeds_2021/lib/sp_feeds_2021.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/fulfillment_outbound_2020/lib/sp_fulfillment_outbound_2020.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/listings_items_2021/lib/sp_listings_items_2021.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/orders_v0/lib/sp_orders_v0.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/reports_2021/lib/sp_reports_2021.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/tokens_2021/lib/sp_tokens_2021.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vdf_inventory_v1/lib/sp_vdf_inventory_v1.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vdf_orders_v1/lib/sp_vdf_orders_v1.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vendor_invoices/lib/sp_vendor_invoices.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vendor_orders/lib/sp_vendor_orders.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vendor_transaction_status/lib/sp_vendor_transaction_status.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated, do not edit it
4
+
5
+ require_relative "../../vendor/vendors_shipments/lib/sp_vendors_shipments.rb"
6
+
7
+ module AmazonSpClients
8
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'amazon_sp_clients/adapter_loader'
5
+
6
+ module AmazonSpClients
7
+ class AuthResponse < Struct.new(
8
+ :access_token,
9
+ :token_type,
10
+ :expires_in,
11
+ :refresh_token
12
+ )
13
+ end
14
+
15
+ class TokenExchangeAuth
16
+ GRANT_TYPE = %w[refresh_token client_credentials].freeze
17
+ TOKEN_HOST = 'api.amazon.com'
18
+
19
+ def initialize(refresh_token = nil, config = Configuration.default)
20
+ @refresh_token = refresh_token
21
+ @config = config
22
+
23
+ @conn =
24
+ Faraday.new("https://#{TOKEN_HOST}", request: { timeout: @config.timeout }) do |conn|
25
+ conn.use AmazonSpClients::Middlewares::RaiseError, { service: :token }
26
+ conn.adapter Faraday::Adapter::HTTPClient
27
+ end
28
+ end
29
+
30
+ # Request login with access token
31
+ # https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#step-1-request-a-login-with-amazon-access-token
32
+ #
33
+ # # REQ:
34
+ # POST /auth/o2/token HTTP/l.l
35
+ # Host: api.amazon.com
36
+ # Content-Type: application/x-www-form-urlencoded;charset=UTF-8
37
+ # grant_type=refresh_token
38
+ # &refresh_token=Aztr|...
39
+ # &client_id=foodev
40
+ # &client_secret=Y76SDl2F
41
+ #
42
+ # # RESP:
43
+ # HTTP/l.l 200 OK
44
+ # Content-Type: application/json;charset UTF-8
45
+ # Cache-Control: no-store
46
+ # Pragma:no-cache
47
+ # {
48
+ # "access_token":"Atza|IQEBLjAsAhRmHjNgHpi0U-Dme37rR6CuUpSREXAMPLE",
49
+ # "token_type":"bearer",
50
+ # "expires_in":3600,
51
+ # "refresh_token":"Atzr|IQEBLzAtAhRPpMJxdwVz2Nn6f2y-tpJX2DeXEXAMPLE"
52
+ # }
53
+ def exchange(grant_type = 'refresh_token', scope = nil)
54
+ raise 'Invalid grant_type' unless GRANT_TYPE.include?(grant_type)
55
+ raise 'Grantless operations require scope' if grant_type == 'client_credentials' && scope.nil?
56
+
57
+ params = {
58
+ grant_type: grant_type,
59
+ client_id: @config.client_id,
60
+ client_secret: @config.client_secret
61
+ }
62
+
63
+ if grant_type == 'refresh_token'
64
+ params[:refresh_token] = @refresh_token
65
+ else
66
+ params[:scope] = scope
67
+ end
68
+
69
+ resp = @conn.post '/auth/o2/token', params
70
+
71
+ body = resp.body
72
+ body = JSON.parse(body, symbolize_names: true) if body.is_a?(String)
73
+
74
+ AuthResponse.new(
75
+ body[:access_token],
76
+ body[:token_type],
77
+ body[:expires_in],
78
+ body[:refresh_token]
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'amazon_sp_clients/adapter_loader'
5
+ require 'zlib'
6
+ require 'multi_xml'
7
+
8
+ module AmazonSpClients
9
+ class Uploader
10
+ attr_reader :response
11
+
12
+ def initialize
13
+ config = AmazonSpClients.configure
14
+ @conn =
15
+ Faraday.new(request: { timeout: config.timeout }) do |c|
16
+ c.adapter Faraday::Adapter::HTTPClient
17
+ c.use AmazonSpClients::Middlewares::RaiseError, { service: :uploads }
18
+ end
19
+ end
20
+
21
+ def upload(feed_doc, doc_content_type, payload)
22
+ upload_url = feed_doc[:url]
23
+ document = payload
24
+
25
+ file = StringIO.new(document)
26
+
27
+ @response =
28
+ @conn.put(upload_url) do |req|
29
+ req.headers.merge!('Content-Type' => doc_content_type)
30
+ req.body = file
31
+ end
32
+ end
33
+ end
34
+
35
+ class Downloader
36
+ # {
37
+ # "compressionAlgorithm": "GZIP",
38
+ # "feedDocumentId": "amzn1.tortuga.3.ed4cd0d8-447b-4c22-96b5-52da8ace1207.T3YUVYPGKE9BMY",
39
+ # "url": "https://tortuga-prod-na.s3.amazonaws.com/%2FNinetyDays/amzn1.tortuga.3.920614b0-fc4c-4393-b0d9-fff175300000.T29XK4YL08B2VM?xxx
40
+ # }
41
+ def initialize(feed_processing_report)
42
+ @config = AmazonSpClients.configure
43
+ @feed_document_id = feed_processing_report[:feedDocumentId]
44
+
45
+ @url = feed_processing_report[:url]
46
+ @compression_algorithm = feed_processing_report[:compressionAlgorithm]
47
+
48
+ @conn =
49
+ Faraday.new(request: { timeout: @config.timeout }) do |c|
50
+ c.use AmazonSpClients::Middlewares::RaiseError, { service: :uploads }
51
+ end
52
+ end
53
+
54
+ def download
55
+ resp = @conn.get(@url)
56
+ decoded_body = inflate_document(resp.body)
57
+
58
+ content_type = resp.headers['content-type'].to_s.downcase
59
+
60
+ if content_type =~ /json/
61
+ JSON.parse(decoded_body)
62
+ else
63
+ MultiXml.parse(decoded_body)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def inflate_document(body)
70
+ if @compression_algorithm.to_s.downcase == 'gzip'
71
+ Zlib.gunzip(body)
72
+ else
73
+ body
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module AmazonSpClients
6
+ module V2
7
+ # Base class of the generated API classes. Holds the client and the
8
+ # small helpers the generated methods use to shape a request.
9
+ #
10
+ # class OrdersV0 < Api
11
+ # def get_order(order_id, rdt: nil)
12
+ # request(:get, "/orders/v0/orders/#{encode(order_id)}", rdt: rdt)
13
+ # end
14
+ # end
15
+ class Api
16
+ # @param client [Client]
17
+ def initialize(client)
18
+ @client = client
19
+ end
20
+
21
+ private
22
+
23
+ # @see Client#request
24
+ def request(method, path, query: {}, headers: {}, body: nil, rdt: nil)
25
+ @client.request(method, path, query: query, headers: headers, body: body, rdt: rdt)
26
+ end
27
+
28
+ # Array parameters travel comma-separated (collectionFormat csv).
29
+ # nil stays nil so the parameter is left out; an empty array sends
30
+ # an empty value, as v1 did.
31
+ def csv(value)
32
+ return nil if value.nil?
33
+
34
+ Array(value).join(',')
35
+ end
36
+
37
+ # Percent-encodes one path segment; SKUs can hold spaces and slashes.
38
+ def encode(value)
39
+ URI.encode_uri_component(value.to_s)
40
+ end
41
+ end
42
+ end
43
+ end