paypal-rest-api 0.0.3 → 0.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.
- checksums.yaml +4 -4
- data/README.md +320 -44
- data/VERSION +1 -1
- data/lib/paypal-api/access_token.rb +16 -2
- data/lib/paypal-api/api_collection.rb +29 -0
- data/lib/paypal-api/api_collections/authorized_payments.rb +4 -4
- data/lib/paypal-api/api_collections/captured_payments.rb +2 -2
- data/lib/paypal-api/api_collections/catalog_products.rb +2 -2
- data/lib/paypal-api/api_collections/disputes.rb +188 -0
- data/lib/paypal-api/api_collections/invoice_templates.rb +82 -0
- data/lib/paypal-api/api_collections/invoices.rb +196 -0
- data/lib/paypal-api/api_collections/orders.rb +7 -7
- data/lib/paypal-api/api_collections/payout_items.rb +47 -0
- data/lib/paypal-api/api_collections/payouts.rb +47 -0
- data/lib/paypal-api/api_collections/referenced_payout_items.rb +47 -0
- data/lib/paypal-api/api_collections/referenced_payouts.rb +47 -0
- data/lib/paypal-api/api_collections/refunds.rb +1 -1
- data/lib/paypal-api/api_collections/shipment_tracking.rb +2 -2
- data/lib/paypal-api/api_collections/subscription_plans.rb +106 -0
- data/lib/paypal-api/api_collections/subscriptions.rb +16 -98
- data/lib/paypal-api/api_collections/user_info.rb +47 -0
- data/lib/paypal-api/api_collections/users.rb +96 -0
- data/lib/paypal-api/api_collections/webhook_events.rb +78 -0
- data/lib/paypal-api/api_collections/webhook_lookups.rb +68 -0
- data/lib/paypal-api/api_collections/webhooks.rb +5 -108
- data/lib/paypal-api/client.rb +129 -5
- data/lib/paypal-api/config.rb +6 -2
- data/lib/paypal-api/request.rb +53 -21
- data/lib/paypal-api/request_executor.rb +81 -66
- data/lib/paypal-api/response.rb +42 -5
- data/lib/paypal-api/webhook_verifier/certs_cache.rb +75 -0
- data/lib/paypal-api/webhook_verifier.rb +104 -0
- data/lib/paypal-api.rb +120 -12
- 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
|
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
|
data/lib/paypal-api/client.rb
CHANGED
@@ -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:
|
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.
|
283
|
+
RequestExecutor.new(self, request).call
|
160
284
|
end
|
161
285
|
end
|
162
286
|
end
|
data/lib/paypal-api/config.rb
CHANGED
@@ -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
|
|
data/lib/paypal-api/request.rb
CHANGED
@@ -19,8 +19,8 @@ module PaypalAPI
|
|
19
19
|
# @return [Net::HTTPRequest] Generated Net::HTTPRequest
|
20
20
|
attr_reader :http_request
|
21
21
|
|
22
|
-
# @return [
|
23
|
-
attr_accessor :
|
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
|
-
@
|
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 =
|
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
|
-
|
54
|
-
|
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
|
60
|
-
headers
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
102
|
+
def build_http_body(http_request, body)
|
68
103
|
return unless body
|
69
104
|
|
70
|
-
|
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
|
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
|
80
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
29
|
-
|
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
|
-
|
33
|
+
def execute_request(retry_number: 0)
|
34
|
+
callbacks_context[:retry_number] = retry_number
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
67
|
+
Response.new(http_response, request: request)
|
68
|
+
end
|
55
69
|
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
76
|
+
def retries_limit_reached?(retry_number)
|
77
|
+
retry_number >= retries[:count]
|
78
|
+
end
|
63
79
|
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
85
|
+
def retryable?(response, retry_number)
|
86
|
+
response.failed? &&
|
87
|
+
!retries_limit_reached?(retry_number) &&
|
88
|
+
retryable_request?(response)
|
89
|
+
end
|
72
90
|
|
73
|
-
|
74
|
-
|
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
|
-
|
80
|
-
|
94
|
+
retry_unauthorized?(response)
|
95
|
+
end
|
81
96
|
|
82
|
-
|
83
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
101
|
+
# set new access-token
|
102
|
+
request.http_request["authorization"] = client.refresh_access_token.authorization_string
|
103
|
+
true
|
104
|
+
end
|
88
105
|
|
89
|
-
|
90
|
-
|
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
|