paypal-rest-api 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +320 -44
  3. data/VERSION +1 -1
  4. data/lib/paypal-api/access_token.rb +16 -2
  5. data/lib/paypal-api/api_collection.rb +29 -0
  6. data/lib/paypal-api/api_collections/authorized_payments.rb +4 -4
  7. data/lib/paypal-api/api_collections/captured_payments.rb +2 -2
  8. data/lib/paypal-api/api_collections/catalog_products.rb +2 -2
  9. data/lib/paypal-api/api_collections/disputes.rb +188 -0
  10. data/lib/paypal-api/api_collections/invoice_templates.rb +82 -0
  11. data/lib/paypal-api/api_collections/invoices.rb +196 -0
  12. data/lib/paypal-api/api_collections/orders.rb +7 -7
  13. data/lib/paypal-api/api_collections/payout_items.rb +47 -0
  14. data/lib/paypal-api/api_collections/payouts.rb +47 -0
  15. data/lib/paypal-api/api_collections/referenced_payout_items.rb +47 -0
  16. data/lib/paypal-api/api_collections/referenced_payouts.rb +47 -0
  17. data/lib/paypal-api/api_collections/refunds.rb +1 -1
  18. data/lib/paypal-api/api_collections/shipment_tracking.rb +2 -2
  19. data/lib/paypal-api/api_collections/subscription_plans.rb +106 -0
  20. data/lib/paypal-api/api_collections/subscriptions.rb +16 -98
  21. data/lib/paypal-api/api_collections/user_info.rb +47 -0
  22. data/lib/paypal-api/api_collections/users.rb +96 -0
  23. data/lib/paypal-api/api_collections/webhook_events.rb +78 -0
  24. data/lib/paypal-api/api_collections/webhook_lookups.rb +68 -0
  25. data/lib/paypal-api/api_collections/webhooks.rb +5 -108
  26. data/lib/paypal-api/client.rb +129 -5
  27. data/lib/paypal-api/config.rb +6 -2
  28. data/lib/paypal-api/request.rb +53 -21
  29. data/lib/paypal-api/request_executor.rb +81 -66
  30. data/lib/paypal-api/response.rb +42 -5
  31. data/lib/paypal-api/webhook_verifier/certs_cache.rb +75 -0
  32. data/lib/paypal-api/webhook_verifier.rb +104 -0
  33. data/lib/paypal-api.rb +120 -12
  34. metadata +17 -3
@@ -46,7 +46,7 @@ module PaypalAPI
46
46
  # @macro request
47
47
  #
48
48
  def show(webhook_id, query: nil, body: nil, headers: nil)
49
- client.get("/v1/notifications/webhooks/#{webhook_id}", query: query, body: body, headers: headers)
49
+ client.get("/v1/notifications/webhooks/#{encode(webhook_id)}", query: query, body: body, headers: headers)
50
50
  end
51
51
 
52
52
  #
@@ -58,7 +58,7 @@ module PaypalAPI
58
58
  # @macro request
59
59
  #
60
60
  def update(webhook_id, query: nil, body: nil, headers: nil)
61
- client.patch("/v1/notifications/webhooks/#{webhook_id}", query: query, body: body, headers: headers)
61
+ client.patch("/v1/notifications/webhooks/#{encode(webhook_id)}", query: query, body: body, headers: headers)
62
62
  end
63
63
 
64
64
  #
@@ -70,7 +70,7 @@ module PaypalAPI
70
70
  # @macro request
71
71
  #
72
72
  def delete(webhook_id, query: nil, body: nil, headers: nil)
73
- client.delete("/v1/notifications/webhooks/#{webhook_id}", query: query, body: body, headers: headers)
73
+ client.delete("/v1/notifications/webhooks/#{encode(webhook_id)}", query: query, body: body, headers: headers)
74
74
  end
75
75
 
76
76
  #
@@ -81,54 +81,8 @@ module PaypalAPI
81
81
  # @param webhook_id [String] Webhook ID
82
82
  # @macro request
83
83
  #
84
- def list_event_types(webhook_id, query: nil, body: nil, headers: nil)
85
- client.get("/v1/notifications/webhooks/#{webhook_id}/event-types", query: query, body: body, headers: headers)
86
- end
87
-
88
- #
89
- # Create webhook lookup
90
- #
91
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-lookup_post
92
- #
93
- # @macro request
94
- #
95
- def create_lookup(query: nil, body: nil, headers: nil)
96
- client.post("/v1/notifications/webhooks-lookup", query: query, body: body, headers: headers)
97
- end
98
-
99
- #
100
- # List webhook lookups
101
- #
102
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-lookup_list
103
- #
104
- # @macro request
105
- #
106
- def list_lookups(query: nil, body: nil, headers: nil)
107
- client.get("/v1/notifications/webhooks-lookup", query: query, body: body, headers: headers)
108
- end
109
-
110
- #
111
- # Show webhook lookup details
112
- #
113
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-lookup_get
114
- #
115
- # @param webhook_lookup_id [String] Webhook lookup ID
116
- # @macro request
117
- #
118
- def show_lookup(webhook_lookup_id, query: nil, body: nil, headers: nil)
119
- client.get("/v1/notifications/webhooks-lookup/#{webhook_lookup_id}", query: query, body: body, headers: headers)
120
- end
121
-
122
- #
123
- # Delete webhook lookup
124
- #
125
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-lookup_delete
126
- #
127
- # @param webhook_lookup_id [String] Webhook lookup ID
128
- # @macro request
129
- #
130
- def delete_lookup(webhook_lookup_id, query: nil, body: nil, headers: nil)
131
- client.delete("/v1/notifications/webhooks-lookup/#{webhook_lookup_id}", query: query, body: body, headers: headers)
84
+ def event_types(webhook_id, query: nil, body: nil, headers: nil)
85
+ client.get("/v1/notifications/webhooks/#{encode(webhook_id)}/event-types", query: query, body: body, headers: headers)
132
86
  end
133
87
 
134
88
  #
@@ -141,63 +95,6 @@ module PaypalAPI
141
95
  def verify(query: nil, body: nil, headers: nil)
142
96
  client.post("/v1/notifications/verify-webhook-signature", query: query, body: body, headers: headers)
143
97
  end
144
-
145
- #
146
- # Lists available events to which any webhook can subscribe
147
- #
148
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-event-types_list
149
- #
150
- # @macro request
151
- #
152
- def list_available_events(query: nil, body: nil, headers: nil)
153
- client.get("/v1/notifications/webhooks-event-types", query: query, body: body, headers: headers)
154
- end
155
-
156
- #
157
- # List event notifications
158
- #
159
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-events_list
160
- #
161
- # @macro request
162
- #
163
- def list_events(query: nil, body: nil, headers: nil)
164
- client.get("/v1/notifications/webhooks-events", query: query, body: body, headers: headers)
165
- end
166
-
167
- #
168
- # Show event notification details
169
- #
170
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-events_get
171
- #
172
- # @param event_id [String] Event ID
173
- # @macro request
174
- #
175
- def show_event(event_id, query: nil, body: nil, headers: nil)
176
- client.get("/v1/notifications/webhooks-events/#{event_id}", query: query, body: body, headers: headers)
177
- end
178
-
179
- #
180
- # Resend event notification
181
- #
182
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-events_resend
183
- #
184
- # @param event_id [String] Event ID
185
- # @macro request
186
- #
187
- def resend_event(event_id, query: nil, body: nil, headers: nil)
188
- client.post("/v1/notifications/webhooks-events/#{event_id}/resend", query: query, body: body, headers: headers)
189
- end
190
-
191
- #
192
- # Simulate webhook event
193
- #
194
- # @see https://developer.paypal.com/docs/api/webhooks/v1/#simulate-event_post
195
- #
196
- # @macro request
197
- #
198
- def simulate_event(query: nil, body: nil, headers: nil)
199
- client.post("/v1/notifications/simulate-event", query: query, body: body, headers: headers)
200
- end
201
98
  end
202
99
 
203
100
  include APIs
@@ -5,9 +5,10 @@ module PaypalAPI
5
5
  # PaypalAPI Client
6
6
  #
7
7
  class Client
8
- attr_reader :config
8
+ attr_reader :config, :callbacks
9
9
 
10
10
  # Initializes Client
11
+ # @api public
11
12
  #
12
13
  # @param client_id [String] PayPal client id
13
14
  # @param client_secret [String] PayPal client secret
@@ -17,18 +18,49 @@ module PaypalAPI
17
18
  #
18
19
  # @return [Client] Initialized client
19
20
  #
20
- def initialize(client_id:, client_secret:, live: nil, http_opts: nil, retries: nil)
21
+ def initialize(client_id:, client_secret:, live: nil, http_opts: nil, retries: nil, cache: nil)
21
22
  @config = PaypalAPI::Config.new(
22
23
  client_id: client_id,
23
24
  client_secret: client_secret,
24
25
  live: live,
25
26
  http_opts: http_opts,
26
- retries: retries
27
+ retries: retries,
28
+ cache: cache
27
29
  )
28
30
 
31
+ @callbacks = {
32
+ before: [],
33
+ after_success: [],
34
+ after_fail: [],
35
+ after_network_error: []
36
+ }.freeze
37
+
29
38
  @access_token = nil
30
39
  end
31
40
 
41
+ # Registers callback
42
+ #
43
+ # @param callback_name [Symbol] Callback name.
44
+ # Allowed values: :before, :after_success, :after_faile, :after_network_error
45
+ #
46
+ # @param block [Proc] Block that must be call
47
+ # For `:before` callback proc should accept 2 params -
48
+ # request [Request], context [Hash]
49
+ #
50
+ # For `:after_success` callback proc should accept 3 params -
51
+ # request [Request], context [Hash], response [Response]
52
+ #
53
+ # For `:after_fail` callback proc should accept 3 params -
54
+ # request [Request], context [Hash], error [StandardError]
55
+ #
56
+ # For `:after_network_error` callback proc should accept 3 params -
57
+ # request [Request], context [Hash], response [Response]
58
+ #
59
+ # @return [void]
60
+ def add_callback(callback_name, &block)
61
+ callbacks.fetch(callback_name) << block
62
+ end
63
+
32
64
  #
33
65
  # Checks cached access token is expired and returns it or generates new one
34
66
  #
@@ -44,16 +76,48 @@ module PaypalAPI
44
76
  # @return [AccessToken] new AccessToken object
45
77
  #
46
78
  def refresh_access_token
79
+ requested_at = Time.now
47
80
  response = authentication.generate_access_token
48
81
 
49
82
  @access_token = AccessToken.new(
50
- requested_at: response.requested_at,
83
+ requested_at: requested_at,
51
84
  expires_in: response.fetch(:expires_in),
52
85
  access_token: response.fetch(:access_token),
53
86
  token_type: response.fetch(:token_type)
54
87
  )
55
88
  end
56
89
 
90
+ #
91
+ # Verifies Webhook
92
+ # It requires one-time request to download and cache certificate.
93
+ # If local verification returns false it tries to verify webhook online.
94
+ #
95
+ # @api public
96
+ # @example
97
+ #
98
+ # class Webhooks::PaypalController < ApplicationController
99
+ # def create
100
+ # webhook_id = ENV['PAYPAL_WEBHOOK_ID'] # PayPal registered webhook ID for current URL
101
+ # headers = request.headers # must be a Hash
102
+ # body = request.raw_post # must be a raw String body
103
+ #
104
+ # webhook_is_valid = PaypalAPI.verify_webhook(webhook_id: webhook_id, headers: headers, body: body)
105
+ # webhook_is_valid ? handle_webhook_event(body) : log_error(webhook_id, headers, body)
106
+ #
107
+ # head :no_content
108
+ # end
109
+ # end
110
+ #
111
+ # @param webhook_id [String] webhook_id provided by PayPal when webhook was registered
112
+ # @param headers [Hash,#[]] webhook request headers
113
+ # @param raw_body [String] webhook request raw body string
114
+ #
115
+ # @return [Boolean] webhook event is valid
116
+ #
117
+ def verify_webhook(webhook_id:, headers:, raw_body:)
118
+ WebhookVerifier.new(self).call(webhook_id: webhook_id, headers: headers, raw_body: raw_body)
119
+ end
120
+
57
121
  # @!macro [new] request
58
122
  # @param path [String] Request path
59
123
  # @param query [Hash, nil] Request query parameters
@@ -127,16 +191,51 @@ module PaypalAPI
127
191
  CatalogProducts.new(self)
128
192
  end
129
193
 
194
+ # @return [Disputes] Disputes APIs collection
195
+ def disputes
196
+ Disputes.new(self)
197
+ end
198
+
199
+ # @return [InvoiceTemplates] InvoiceTemplates APIs collection
200
+ def invoice_templates
201
+ InvoiceTemplates.new(self)
202
+ end
203
+
204
+ # @return [Invoices] Invoices APIs collection
205
+ def invoices
206
+ Invoices.new(self)
207
+ end
208
+
130
209
  # @return [Orders] Orders APIs collection
131
210
  def orders
132
211
  Orders.new(self)
133
212
  end
134
213
 
214
+ # @return [PayoutItems] PayoutItems APIs collection
215
+ def payout_items
216
+ PayoutItems.new(self)
217
+ end
218
+
219
+ # @return [Payouts] Payouts APIs collection
220
+ def payouts
221
+ Payouts.new(self)
222
+ end
223
+
135
224
  # @return [Redunds] Refunds APIs collection
136
225
  def refunds
137
226
  Refunds.new(self)
138
227
  end
139
228
 
229
+ # @return [ReferencedPayoutItems] ReferencedPayoutItems APIs collection
230
+ def referenced_payout_items
231
+ ReferencedPayoutItems.new(self)
232
+ end
233
+
234
+ # @return [ReferencedPayouts] ReferencedPayouts APIs collection
235
+ def referenced_payouts
236
+ ReferencedPayouts.new(self)
237
+ end
238
+
140
239
  # @return [ShipmentTracking] Shipment Tracking APIs collection
141
240
  def shipment_tracking
142
241
  ShipmentTracking.new(self)
@@ -147,16 +246,41 @@ module PaypalAPI
147
246
  Subscriptions.new(self)
148
247
  end
149
248
 
249
+ # @return [SubscriptionPlans] Subscription Plans APIs collection
250
+ def subscription_plans
251
+ SubscriptionPlans.new(self)
252
+ end
253
+
254
+ # @return [UserInfo] User Info APIs collection
255
+ def user_info
256
+ UserInfo.new(self)
257
+ end
258
+
259
+ # @return [Users] Users Management APIs collection
260
+ def users
261
+ Users.new(self)
262
+ end
263
+
150
264
  # @return [Webhooks] Webhooks APIs collection
151
265
  def webhooks
152
266
  Webhooks.new(self)
153
267
  end
154
268
 
269
+ # @return [WebhookEvents] Webhook Events APIs collection
270
+ def webhook_lookups
271
+ WebhookLookups.new(self)
272
+ end
273
+
274
+ # @return [WebhookEvents] Webhook Lookups APIs collection
275
+ def webhook_events
276
+ WebhookEvents.new(self)
277
+ end
278
+
155
279
  private
156
280
 
157
281
  def execute_request(http_method, path, query: nil, body: nil, headers: nil)
158
282
  request = Request.new(self, http_method, path, query: query, body: body, headers: headers)
159
- RequestExecutor.call(request)
283
+ RequestExecutor.new(self, request).call
160
284
  end
161
285
  end
162
286
  end
@@ -18,7 +18,7 @@ module PaypalAPI
18
18
  retries: {enabled: true, count: 3, sleep: [0.25, 0.75, 1.5].freeze}.freeze
19
19
  }.freeze
20
20
 
21
- attr_reader :client_id, :client_secret, :live, :http_opts, :retries
21
+ attr_reader :client_id, :client_secret, :live, :http_opts, :retries, :certs_cache
22
22
 
23
23
  # Initializes Config
24
24
  #
@@ -27,15 +27,19 @@ module PaypalAPI
27
27
  # @param live [Boolean] PayPal live/sandbox mode
28
28
  # @param http_opts [Hash] Net::Http opts for all requests
29
29
  # @param retries [Hash] Retries configuration
30
+ # @param cache [#read, nil] Application cache to store certificates to validate webhook events locally.
31
+ # Must respond to #read(key) and #write(key, expires_in: Integer)
30
32
  #
31
33
  # @return [Client] Initialized config object
32
34
  #
33
- def initialize(client_id:, client_secret:, live: nil, http_opts: nil, retries: nil)
35
+ def initialize(client_id:, client_secret:, live: nil, http_opts: nil, retries: nil, cache: nil)
34
36
  @client_id = client_id
35
37
  @client_secret = client_secret
36
38
  @live = with_default(:live, live)
37
39
  @http_opts = with_default(:http_opts, http_opts)
38
40
  @retries = with_default(:retries, retries)
41
+ @certs_cache = WebhookVerifier::CertsCache.new(cache)
42
+
39
43
  freeze
40
44
  end
41
45
 
@@ -19,8 +19,8 @@ module PaypalAPI
19
19
  # @return [Net::HTTPRequest] Generated Net::HTTPRequest
20
20
  attr_reader :http_request
21
21
 
22
- # @return [Time, nil] Time when request was sent
23
- attr_accessor :requested_at
22
+ # @return [Hash, nil, Object] Custom context that can be set/changed in callbacks
23
+ attr_accessor :context
24
24
 
25
25
  # rubocop:disable Metrics/ParameterLists
26
26
 
@@ -40,48 +40,80 @@ module PaypalAPI
40
40
  def initialize(client, request_type, path, body: nil, query: nil, headers: nil)
41
41
  @client = client
42
42
  @http_request = build_http_request(request_type, path, body: body, query: query, headers: headers)
43
- @requested_at = nil
43
+ @context = nil
44
44
  end
45
45
  # rubocop:enable Metrics/ParameterLists
46
46
 
47
+ # @return [String] HTTP request method name
48
+ def method
49
+ http_request.method
50
+ end
51
+
52
+ # @return [String] HTTP request method name
53
+ def path
54
+ http_request.path
55
+ end
56
+
57
+ # @return [URI] HTTP request URI
58
+ def uri
59
+ http_request.uri
60
+ end
61
+
62
+ # @return [String] HTTP request body
63
+ def body
64
+ http_request.body
65
+ end
66
+
67
+ # @return [Hash] HTTP request headers
68
+ def headers
69
+ http_request.each_header.to_h
70
+ end
71
+
47
72
  private
48
73
 
49
74
  def build_http_request(request_type, path, body:, query:, headers:)
50
- uri = request_uri(path, query)
51
- http_request = request_type.new(uri)
75
+ uri = build_http_uri(path, query)
76
+ http_request = request_type.new(uri, "accept-encoding" => nil)
52
77
 
53
- add_headers(http_request, headers || {})
54
- add_body(http_request, body)
78
+ build_http_headers(http_request, body, headers || {})
79
+ build_http_body(http_request, body)
55
80
 
56
81
  http_request
57
82
  end
58
83
 
59
- def add_headers(http_request, headers)
60
- headers.each { |key, value| http_request[key] = value }
84
+ def build_http_headers(http_request, body, headers)
85
+ headers = normalize_headers(headers)
86
+
87
+ unless headers.key?("authorization")
88
+ http_request["authorization"] = client.access_token.authorization_string
89
+ end
90
+
91
+ unless headers.key?("content-type")
92
+ http_request["content-type"] = "application/json" if body
93
+ end
61
94
 
62
- http_request["content-type"] ||= "application/json"
63
- http_request["authorization"] ||= client.access_token.authorization_string
64
- http_request["paypal-request-id"] ||= SecureRandom.uuid if idempotent?(http_request)
95
+ unless headers.key?("paypal-request-id")
96
+ http_request["paypal-request-id"] = SecureRandom.uuid unless http_request.is_a?(Net::HTTP::Get)
97
+ end
98
+
99
+ headers.each { |key, value| http_request[key] = value }
65
100
  end
66
101
 
67
- def add_body(http_request, body)
102
+ def build_http_body(http_request, body)
68
103
  return unless body
69
104
 
70
- json?(http_request) ? http_request.body = JSON.dump(body) : http_request.set_form_data(body)
105
+ is_json = http_request["content-type"].include?("json")
106
+ is_json ? http_request.body = JSON.dump(body) : http_request.set_form_data(body)
71
107
  end
72
108
 
73
- def request_uri(path, query)
109
+ def build_http_uri(path, query)
74
110
  uri = URI.join(client.config.url, path)
75
111
  uri.query = URI.encode_www_form(query) if query && !query.empty?
76
112
  uri
77
113
  end
78
114
 
79
- def idempotent?(http_request)
80
- http_request.method != Net::HTTP::Get::METHOD
81
- end
82
-
83
- def json?(http_request)
84
- http_request["content-type"].include?("json")
115
+ def normalize_headers(headers)
116
+ headers.empty? ? headers : headers.transform_keys { |key| key.to_s.downcase }
85
117
  end
86
118
  end
87
119
  end
@@ -5,91 +5,106 @@ module PaypalAPI
5
5
  # Executes PaypalAPI::Request and returns PaypalAPI::Response or raises PaypalAPI::Error
6
6
  #
7
7
  class RequestExecutor
8
- # List of Net::HTTP responses that must be retried
9
- RETRYABLE_RESPONSES = [
10
- Net::HTTPServerError, # 5xx
11
- Net::HTTPConflict, # 409
12
- Net::HTTPTooManyRequests # 429
13
- ].freeze
14
-
15
- class << self
16
- #
17
- # Executes prepared Request, handles retries and preparation of errors
18
- #
19
- # @param [Request] request
20
- #
21
- # @return [Response] Response
22
- #
23
- def call(request)
24
- http_response = execute(request)
25
- response = Response.new(http_response, requested_at: request.requested_at)
26
- raise FailedRequestErrorBuilder.call(request: request, response: response) unless http_response.is_a?(Net::HTTPSuccess)
8
+ attr_reader :client, :request, :http_opts, :context, :retries, :callbacks, :callbacks_context
9
+
10
+ def initialize(client, request)
11
+ @client = client
12
+ @request = request
13
+ @http_opts = {use_ssl: request.uri.is_a?(URI::HTTPS), **client.config.http_opts}
14
+ @retries = client.config.retries
15
+ @callbacks = client.callbacks
16
+ @callbacks_context = {retries_count: retries[:count]}
17
+ end
27
18
 
28
- response
29
- end
19
+ #
20
+ # Executes prepared Request, handles retries and preparation of errors
21
+ #
22
+ # @return [Response] Response
23
+ #
24
+ def call
25
+ response = execute_request
26
+ raise FailedRequestErrorBuilder.call(request: request, response: response) if response.failed?
27
+
28
+ response
29
+ end
30
+
31
+ private
30
32
 
31
- private
33
+ def execute_request(retry_number: 0)
34
+ callbacks_context[:retry_number] = retry_number
32
35
 
33
- def execute(request, retry_number: 0)
34
- http_response = execute_http_request(request)
35
- rescue *NetworkErrorBuilder::ERRORS => error
36
- retry_on_network_error(request, error, retry_number)
36
+ run_callbacks(:before)
37
+ response = execute_net_http_request
38
+ rescue *NetworkErrorBuilder::ERRORS => error
39
+ will_retry = !retries_limit_reached?(retry_number)
40
+ callbacks_context[:will_retry] = will_retry
41
+ run_callbacks(:after_network_error, error)
42
+ raise NetworkErrorBuilder.call(request: request, error: error) unless will_retry
43
+
44
+ retry_request(retry_number)
45
+ else
46
+ if response.success?
47
+ callbacks_context.delete(:will_retry)
48
+ run_callbacks(:after_success, response)
49
+ response
37
50
  else
38
- retryable?(request, http_response, retry_number) ? retry_request(request, retry_number) : http_response
51
+ will_retry = retryable?(response, retry_number)
52
+ callbacks_context[:will_retry] = will_retry
53
+ run_callbacks(:after_fail, response)
54
+ will_retry ? retry_request(retry_number) : response
39
55
  end
56
+ end
40
57
 
41
- def execute_http_request(request)
42
- http_request = request.http_request
43
- http_opts = request.client.config.http_opts
44
- uri = http_request.uri
45
- request.requested_at = Time.now
58
+ def execute_net_http_request
59
+ uri = request.uri
46
60
 
47
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, **http_opts) do |http|
61
+ http_response =
62
+ Net::HTTP.start(uri.hostname, uri.port, **http_opts) do |http|
48
63
  http.max_retries = 0 # we have custom retries logic
49
- http.request(http_request)
64
+ http.request(request.http_request)
50
65
  end
51
- end
52
66
 
53
- def retry_on_network_error(request, error, retry_number)
54
- raise NetworkErrorBuilder.call(request: request, error: error) if retries_limit_reached?(request, retry_number)
67
+ Response.new(http_response, request: request)
68
+ end
55
69
 
56
- retry_request(request, retry_number)
57
- end
70
+ def retry_request(current_retry_number)
71
+ sleep_time = retry_sleep_seconds(current_retry_number)
72
+ sleep(sleep_time) if sleep_time.positive?
73
+ execute_request(retry_number: current_retry_number + 1)
74
+ end
58
75
 
59
- def retry_request(request, current_retry_number)
60
- sleep(retry_sleep_seconds(request, current_retry_number))
61
- execute(request, retry_number: current_retry_number + 1)
62
- end
76
+ def retries_limit_reached?(retry_number)
77
+ retry_number >= retries[:count]
78
+ end
63
79
 
64
- def retries_limit_reached?(request, retry_number)
65
- retry_number >= request.client.config.retries[:count]
66
- end
80
+ def retry_sleep_seconds(current_retry_number)
81
+ seconds_per_retry = retries[:sleep]
82
+ seconds_per_retry[current_retry_number] || seconds_per_retry.last || 1
83
+ end
67
84
 
68
- def retry_sleep_seconds(request, current_retry_number)
69
- seconds_per_retry = request.client.config.retries[:sleep]
70
- seconds_per_retry[current_retry_number] || seconds_per_retry.last || 1
71
- end
85
+ def retryable?(response, retry_number)
86
+ response.failed? &&
87
+ !retries_limit_reached?(retry_number) &&
88
+ retryable_request?(response)
89
+ end
72
90
 
73
- def retryable?(request, http_response, retry_number)
74
- !http_response.is_a?(Net::HTTPSuccess) &&
75
- !retries_limit_reached?(request, retry_number) &&
76
- retryable_request?(request, http_response)
77
- end
91
+ def retryable_request?(response)
92
+ return true if response.retryable?
78
93
 
79
- def retryable_request?(request, http_response)
80
- return true if RETRYABLE_RESPONSES.any? { |retryable_class| http_response.is_a?(retryable_class) }
94
+ retry_unauthorized?(response)
95
+ end
81
96
 
82
- retry_unauthorized?(request, http_response)
83
- end
97
+ def retry_unauthorized?(response)
98
+ return false unless response.unauthorized? # 401
99
+ return false if request.path == Authentication::PATH # it's already an Authentication request
84
100
 
85
- def retry_unauthorized?(request, http_response)
86
- return false unless http_response.is_a?(Net::HTTPUnauthorized) # 401
87
- return false if http_response.uri.path == Authentication::PATH # it's already an Authentication request
101
+ # set new access-token
102
+ request.http_request["authorization"] = client.refresh_access_token.authorization_string
103
+ true
104
+ end
88
105
 
89
- # set new access-token
90
- request.http_request["authorization"] = request.client.refresh_access_token.authorization_string
91
- true
92
- end
106
+ def run_callbacks(callback_name, resp = nil)
107
+ callbacks[callback_name].each { |callback| callback.call(request, context, resp) }
93
108
  end
94
109
  end
95
110
  end