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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +161 -0
- data/LICENSE +21 -0
- data/README.md +284 -0
- data/lib/amazon_sp_clients/adapter_loader.rb +28 -0
- data/lib/amazon_sp_clients/api_client.rb +309 -0
- data/lib/amazon_sp_clients/api_error.rb +37 -0
- data/lib/amazon_sp_clients/api_response.rb +54 -0
- data/lib/amazon_sp_clients/configuration.rb +144 -0
- data/lib/amazon_sp_clients/marketplaces.rb +63 -0
- data/lib/amazon_sp_clients/middlewares/raise_error.rb +166 -0
- data/lib/amazon_sp_clients/session.rb +124 -0
- data/lib/amazon_sp_clients/sp_fba_inventory.rb +8 -0
- data/lib/amazon_sp_clients/sp_feeds_2021.rb +8 -0
- data/lib/amazon_sp_clients/sp_fulfillment_outbound_2020.rb +8 -0
- data/lib/amazon_sp_clients/sp_listings_items_2021.rb +8 -0
- data/lib/amazon_sp_clients/sp_orders_v0.rb +8 -0
- data/lib/amazon_sp_clients/sp_reports_2021.rb +8 -0
- data/lib/amazon_sp_clients/sp_tokens_2021.rb +8 -0
- data/lib/amazon_sp_clients/sp_vdf_inventory_v1.rb +8 -0
- data/lib/amazon_sp_clients/sp_vdf_orders_v1.rb +8 -0
- data/lib/amazon_sp_clients/sp_vdf_shipping_v1.rb +8 -0
- data/lib/amazon_sp_clients/sp_vendor_invoices.rb +8 -0
- data/lib/amazon_sp_clients/sp_vendor_orders.rb +8 -0
- data/lib/amazon_sp_clients/sp_vendor_transaction_status.rb +8 -0
- data/lib/amazon_sp_clients/sp_vendors_shipments.rb +8 -0
- data/lib/amazon_sp_clients/token_exchange_auth.rb +82 -0
- data/lib/amazon_sp_clients/uploader.rb +77 -0
- data/lib/amazon_sp_clients/v2/api.rb +43 -0
- data/lib/amazon_sp_clients/v2/apis/fba_inventory.rb +80 -0
- data/lib/amazon_sp_clients/v2/apis/feeds_2021.rb +86 -0
- data/lib/amazon_sp_clients/v2/apis/fulfillment_outbound_2020.rb +184 -0
- data/lib/amazon_sp_clients/v2/apis/listings_items_2021.rb +144 -0
- data/lib/amazon_sp_clients/v2/apis/orders_v0.rb +165 -0
- data/lib/amazon_sp_clients/v2/apis/reports_2021.rb +117 -0
- data/lib/amazon_sp_clients/v2/apis/tokens_2021.rb +25 -0
- data/lib/amazon_sp_clients/v2/apis/vdf_inventory_v1.rb +26 -0
- data/lib/amazon_sp_clients/v2/apis/vdf_orders_v1.rb +61 -0
- data/lib/amazon_sp_clients/v2/apis/vdf_shipping_v1.rb +139 -0
- data/lib/amazon_sp_clients/v2/apis/vendor_invoices.rb +25 -0
- data/lib/amazon_sp_clients/v2/apis/vendor_orders.rb +106 -0
- data/lib/amazon_sp_clients/v2/apis/vendor_transaction_status.rb +25 -0
- data/lib/amazon_sp_clients/v2/apis/vendors_shipments.rb +120 -0
- data/lib/amazon_sp_clients/v2/apis.rb +82 -0
- data/lib/amazon_sp_clients/v2/client.rb +198 -0
- data/lib/amazon_sp_clients/v2/config.rb +85 -0
- data/lib/amazon_sp_clients/v2/credentials.rb +91 -0
- data/lib/amazon_sp_clients/v2/documents.rb +115 -0
- data/lib/amazon_sp_clients/v2/error_mapper.rb +235 -0
- data/lib/amazon_sp_clients/v2/errors.rb +117 -0
- data/lib/amazon_sp_clients/v2/lwa.rb +100 -0
- data/lib/amazon_sp_clients/v2/rdt.rb +104 -0
- data/lib/amazon_sp_clients/v2/token.rb +39 -0
- data/lib/amazon_sp_clients/v2.rb +33 -0
- data/lib/amazon_sp_clients/version.rb +3 -0
- data/lib/amazon_sp_clients.rb +69 -0
- data/lib/sp_api_clients.rb +6 -0
- data/sp_api_clients.gemspec +53 -0
- data/vendor/fba_inventory/lib/sp_fba_inventory/api/fba_inventory_api.rb +289 -0
- data/vendor/fba_inventory/lib/sp_fba_inventory.rb +12 -0
- data/vendor/feeds_2021/lib/sp_feeds_2021/api/feeds_api.rb +367 -0
- data/vendor/feeds_2021/lib/sp_feeds_2021.rb +12 -0
- data/vendor/fulfillment_outbound_2020/lib/sp_fulfillment_outbound_2020/api/fba_outbound_api.rb +845 -0
- data/vendor/fulfillment_outbound_2020/lib/sp_fulfillment_outbound_2020.rb +12 -0
- data/vendor/listings_items_2021/lib/sp_listings_items_2021/api/listings_api.rb +480 -0
- data/vendor/listings_items_2021/lib/sp_listings_items_2021.rb +12 -0
- data/vendor/orders_v0/lib/sp_orders_v0/api/orders_v0_api.rb +593 -0
- data/vendor/orders_v0/lib/sp_orders_v0/api/shipment_api.rb +83 -0
- data/vendor/orders_v0/lib/sp_orders_v0.rb +13 -0
- data/vendor/reports_2021/lib/sp_reports_2021/api/reports_api.rb +530 -0
- data/vendor/reports_2021/lib/sp_reports_2021.rb +12 -0
- data/vendor/tokens_2021/lib/sp_tokens_2021/api/tokens_api.rb +77 -0
- data/vendor/tokens_2021/lib/sp_tokens_2021.rb +12 -0
- data/vendor/vdf_inventory_v1/lib/sp_vdf_inventory_v1/api/update_inventory_api.rb +83 -0
- data/vendor/vdf_inventory_v1/lib/sp_vdf_inventory_v1.rb +12 -0
- data/vendor/vdf_orders_v1/lib/sp_vdf_orders_v1/api/vendor_orders_api.rb +217 -0
- data/vendor/vdf_orders_v1/lib/sp_vdf_orders_v1.rb +12 -0
- data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/customer_invoices_api.rb +152 -0
- data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/vendor_shipping_api.rb +264 -0
- data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1/api/vendor_shipping_labels_api.rb +208 -0
- data/vendor/vdf_shipping_v1/lib/sp_vdf_shipping_v1.rb +14 -0
- data/vendor/vendor_invoices/lib/sp_vendor_invoices/api/vendor_payments_api.rb +77 -0
- data/vendor/vendor_invoices/lib/sp_vendor_invoices.rb +12 -0
- data/vendor/vendor_orders/lib/sp_vendor_orders/api/vendor_orders_api.rb +323 -0
- data/vendor/vendor_orders/lib/sp_vendor_orders.rb +12 -0
- data/vendor/vendor_transaction_status/lib/sp_vendor_transaction_status/api/vendor_transaction_api.rb +75 -0
- data/vendor/vendor_transaction_status/lib/sp_vendor_transaction_status.rb +12 -0
- data/vendor/vendors_shipments/lib/sp_vendors_shipments/api/vendor_shipping_api.rb +337 -0
- data/vendor/vendors_shipments/lib/sp_vendors_shipments.rb +12 -0
- 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,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
|