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
@@ -7,22 +7,29 @@ module PaypalAPI
7
7
  # PaypalAPI::Response object
8
8
  #
9
9
  class Response
10
+ # List of Net::HTTP responses that can be retried
11
+ RETRYABLE_RESPONSES = [
12
+ Net::HTTPServerError, # 5xx
13
+ Net::HTTPConflict, # 409
14
+ Net::HTTPTooManyRequests # 429
15
+ ].freeze
16
+
10
17
  # @return [Net::HTTP::Response] Original Net::HTTP::Response object
11
18
  attr_reader :http_response
12
19
 
13
- # @return [Time] Time when request was sent
14
- attr_reader :requested_at
20
+ # @return [Request] Request object
21
+ attr_reader :request
15
22
 
16
23
  #
17
24
  # Initializes Response object
18
25
  #
19
26
  # @param http_response [Net::HTTP::Response] original response
20
- # @param requested_at [Time] Time when original response was requested
27
+ # @param request [Request] Request that generates this response
21
28
  #
22
29
  # @return [Response] Initialized Response object
23
30
  #
24
- def initialize(http_response, requested_at:)
25
- @requested_at = requested_at
31
+ def initialize(http_response, request:)
32
+ @request = request
26
33
  @http_response = http_response
27
34
  @http_status = nil
28
35
  @http_headers = nil
@@ -64,6 +71,36 @@ module PaypalAPI
64
71
  data.fetch(key.to_sym)
65
72
  end
66
73
 
74
+ # Checks http status code is 2xx
75
+ #
76
+ # @return [Boolean] Returns true if response has success status code (2xx)
77
+ def success?
78
+ http_response.is_a?(Net::HTTPSuccess)
79
+ end
80
+
81
+ # Checks http status code is not 2xx
82
+ #
83
+ # @return [Boolean] Returns true if response has not success status code
84
+ def failed?
85
+ !success?
86
+ end
87
+
88
+ # Checks if response status code is retriable (5xx, 409, 429)
89
+ # @api private
90
+ #
91
+ # @return [Boolean] Returns true if status code is retriable (5xx, 409, 429)
92
+ def retryable?
93
+ failed? && RETRYABLE_RESPONSES.any? { |retryable_class| http_response.is_a?(retryable_class) }
94
+ end
95
+
96
+ # Checks if response status code is unauthorized (401)
97
+ # @api private
98
+ #
99
+ # @return [Boolean] Returns true if status code is retriable (5xx, 409, 429)
100
+ def unauthorized?
101
+ http_response.is_a?(Net::HTTPUnauthorized)
102
+ end
103
+
67
104
  #
68
105
  # Instance representation string. Default was overwritten to hide secrets
69
106
  #
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaypalAPI
4
+ class WebhookVerifier
5
+ #
6
+ # Stores certifiactes in-memory.
7
+ #
8
+ # New values are added to this in-memory cache and application cache.
9
+ # When fetching value it firstly looks to the memory cache and then to the app cache.
10
+ #
11
+ # @api private
12
+ #
13
+ class CertsCache
14
+ # Current application cache
15
+ # @api private
16
+ attr_reader :app_cache
17
+
18
+ # Hash storage of certificate public keys
19
+ # @api private
20
+ attr_reader :storage
21
+
22
+ # Initializes certificates cache
23
+ #
24
+ # @param app_cache [#fetch, nil] Application cache that can store
25
+ # certificates between redeploys
26
+ #
27
+ # @return [CertsCache]
28
+ def initialize(app_cache)
29
+ @app_cache = app_cache || NullCache
30
+ @storage = {}
31
+ end
32
+
33
+ # Fetches value from cache
34
+ #
35
+ # @param key [String] Cache key
36
+ # @param block [Proc] Proc to fetch certificate text
37
+ #
38
+ # @return [OpenSSL::PKey::PKey] Certificate Public Key
39
+ def fetch(key, &block)
40
+ openssl_pub_key = read(key)
41
+ return openssl_pub_key if openssl_pub_key
42
+
43
+ cert_string = app_cache.fetch(key, &block)
44
+ cert = OpenSSL::X509::Certificate.new(cert_string)
45
+
46
+ write(key, cert.public_key)
47
+ end
48
+
49
+ private
50
+
51
+ def write(key, value)
52
+ storage[key] = value
53
+ end
54
+
55
+ def read(key)
56
+ storage[key]
57
+ end
58
+ end
59
+
60
+ #
61
+ # Null-object cache class.
62
+ # Implements only #read and #write method.
63
+ #
64
+ # @api private
65
+ #
66
+ class NullCache
67
+ # Just calls provided block
68
+ # @param _key [String] Cache key
69
+ # @return [String] block result
70
+ def self.fetch(_key)
71
+ yield
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module PaypalAPI
6
+ #
7
+ # Webhook Verifier
8
+ # @api private
9
+ #
10
+ class WebhookVerifier
11
+ # Error that can happen when verifying webhook when some required headers are missing
12
+ class MissingHeader < StandardError
13
+ end
14
+
15
+ # @return [Client] current client
16
+ attr_reader :client
17
+
18
+ def initialize(client)
19
+ @client = client
20
+ end
21
+
22
+ # Verifies Webhook.
23
+ #
24
+ # It requires one-time request to download and cache certificate.
25
+ # If local verification returns false it tries to verify webhook online.
26
+ #
27
+ # @param webhook_id [String] Webhook ID provided by PayPal when registering webhook for your URL
28
+ # @param headers [Hash] webhook request headers
29
+ # @param raw_body [String] webhook request raw body string
30
+ #
31
+ # @return [Boolean]
32
+ def call(webhook_id:, headers:, raw_body:)
33
+ args = {
34
+ webhook_id: webhook_id,
35
+ raw_body: raw_body,
36
+ auth_algo: paypal_auth_algo(headers),
37
+ transmission_sig: paypal_transmission_sig(headers),
38
+ transmission_id: paypal_transmission_id(headers),
39
+ transmission_time: paypal_transmission_time(headers),
40
+ cert_url: paypal_cert_url(headers)
41
+ }
42
+
43
+ offline(**args) || online(**args)
44
+ end
45
+
46
+ private
47
+
48
+ def offline(webhook_id:, auth_algo:, transmission_sig:, transmission_id:, transmission_time:, cert_url:, raw_body:)
49
+ return false unless auth_algo.downcase.start_with?("sha256")
50
+
51
+ signature = paypal_signature(transmission_sig)
52
+ checked_data = "#{transmission_id}|#{transmission_time}|#{webhook_id}|#{Zlib.crc32(raw_body)}"
53
+ openssl_pub_key = download_and_cache_certificate(cert_url)
54
+
55
+ digest = OpenSSL::Digest.new("sha256", signature)
56
+ openssl_pub_key.verify(digest, signature, checked_data)
57
+ end
58
+
59
+ def online(webhook_id:, auth_algo:, transmission_sig:, transmission_id:, transmission_time:, cert_url:, raw_body:)
60
+ body = {
61
+ webhook_id: webhook_id,
62
+ auth_algo: auth_algo,
63
+ cert_url: cert_url,
64
+ transmission_id: transmission_id,
65
+ transmission_sig: transmission_sig,
66
+ transmission_time: transmission_time,
67
+ webhook_event: JSON.parse(raw_body)
68
+ }
69
+
70
+ response = client.webhooks.verify(body: body)
71
+ response[:verification_status] == "SUCCESS"
72
+ end
73
+
74
+ def paypal_signature(transmission_sig)
75
+ transmission_sig.unpack1("m") # decode base64
76
+ end
77
+
78
+ def paypal_transmission_sig(headers)
79
+ headers["paypal-transmission-sig"] || (raise MissingHeader, "No `paypal-transmission-sig` header")
80
+ end
81
+
82
+ def paypal_transmission_id(headers)
83
+ headers["paypal-transmission-id"] || (raise MissingHeader, "No `paypal-transmission-id` header")
84
+ end
85
+
86
+ def paypal_transmission_time(headers)
87
+ headers["paypal-transmission-time"] || (raise MissingHeader, "No `paypal-transmission-time` header")
88
+ end
89
+
90
+ def paypal_cert_url(headers)
91
+ headers["paypal-cert-url"] || (raise MissingHeader, "No `paypal-cert-url` header")
92
+ end
93
+
94
+ def paypal_auth_algo(headers)
95
+ headers["paypal-auth-algo"] || (raise MissingHeader, "No `paypal-auth-algo` header")
96
+ end
97
+
98
+ def download_and_cache_certificate(cert_url)
99
+ client.config.certs_cache.fetch("PaypalRestAPI.certificate.#{cert_url}") do
100
+ client.get(cert_url, headers: {"authorization" => nil}).http_body
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/paypal-api.rb CHANGED
@@ -3,25 +3,26 @@
3
3
  #
4
4
  # PaypalAPI is a main gem module.
5
5
  #
6
- # It can store global PaypalAPI::Client for easier access to APIs.
7
- #
8
- # @example Initializing new global client
9
- # PaypalAPI.client = PaypalAPI::Client.new(
10
- # client_id: ENV.fetch('PAYPAL_CLIENT_ID'),
11
- # client_secret: ENV.fetch('PAYPAL_CLIENT_SECRET'),
12
- # live: false
13
- # )
14
- #
15
- # # And then call any APIs without mentioning the client
16
- # PaypalAPI::Webhooks.list # or PaypalAPI.webhooks.list
17
- #
18
6
  module PaypalAPI
19
7
  class << self
20
8
  # Sets client
9
+ # @api public
10
+ #
11
+ # @example Initializing new global client
12
+ # PaypalAPI.client = PaypalAPI::Client.new(
13
+ # client_id: ENV.fetch('PAYPAL_CLIENT_ID'),
14
+ # client_secret: ENV.fetch('PAYPAL_CLIENT_SECRET'),
15
+ # live: false
16
+ # )
17
+ #
18
+ # @return [Client] PaypalAPI client
21
19
  attr_writer :client
22
20
 
23
21
  # @!macro [new] request
24
22
  #
23
+ # @api public
24
+ # @example
25
+ # PaypalAPI.$0("/path1/path2", query: query, body: body, headers: headers)
25
26
  # @param path [String] Request path
26
27
  # @param query [Hash, nil] Request query parameters
27
28
  # @param body [Hash, nil] Request body parameters
@@ -61,44 +62,133 @@ module PaypalAPI
61
62
  end
62
63
  end
63
64
 
65
+ #
66
+ # Verifies Webhook
67
+ #
68
+ # It requires one-time request to download and cache certificate.
69
+ # If local verification returns false it tries to verify webhook online.
70
+ #
71
+ # @see Client#verify_webhook
72
+ #
73
+ def verify_webhook(webhook_id:, headers:, raw_body:)
74
+ client.verify_webhook(webhook_id: webhook_id, headers: headers, raw_body: raw_body)
75
+ end
76
+
77
+ #
78
+ # @!macro [new] api_collection
79
+ # $0 APIs collection
80
+ # @api public
81
+ # @example
82
+ # PaypalAPI.$0
83
+ #
84
+
64
85
  #
65
86
  # @!method authentication
87
+ # @macro api_collection
66
88
  # @return [Authentication]
67
89
  #
68
90
  # @!method authorized_payments
91
+ # @macro api_collection
69
92
  # @return [AuthorizedPayments]
70
93
  #
71
94
  # @!method captured_payments
95
+ # @macro api_collection
72
96
  # @return [CapturedPayments]
73
97
  #
74
98
  # @!method catalog_products
99
+ # @macro api_collection
75
100
  # @return [CatalogProducts]
76
101
  #
102
+ # @!method disputes
103
+ # @macro api_collection
104
+ # @return [Disputes]
105
+ #
106
+ # @!method invoice_templates
107
+ # @macro api_collection
108
+ # @return [InvoiceTemplates]
109
+ #
110
+ # @!method invoices
111
+ # @macro api_collection
112
+ # @return [Invoices]
113
+ #
77
114
  # @!method orders
115
+ # @macro api_collection
78
116
  # @return [Orders]
79
117
  #
118
+ # @!method payout_items
119
+ # @macro api_collection
120
+ # @return [PayoutItems]
121
+ #
122
+ # @!method payouts
123
+ # @macro api_collection
124
+ # @return [Payouts]
125
+ #
80
126
  # @!method refunds
127
+ # @macro api_collection
81
128
  # @return [Refunds]
82
129
  #
130
+ # @!method referenced_payout_items
131
+ # @macro api_collection
132
+ # @return [ReferencedPayoutItems]
133
+ #
134
+ # @!method referenced_payouts
135
+ # @macro api_collection
136
+ # @return [ReferencedPayouts]
137
+ #
83
138
  # @!method shipment_tracking
139
+ # @macro api_collection
84
140
  # @return [ShipmentTracking]
85
141
  #
86
142
  # @!method subscriptions
143
+ # @macro api_collection
87
144
  # @return [Subscriptions]
88
145
  #
146
+ # @!method subscription_plans
147
+ # @macro api_collection
148
+ # @return [SubscriptionPlans]
149
+ #
150
+ # @!method user_info
151
+ # @macro api_collection
152
+ # @return [UserInfo]
153
+ #
154
+ # @!method users
155
+ # @macro api_collection
156
+ # @return [Users]
157
+ #
89
158
  # @!method webhooks
159
+ # @macro api_collection
90
160
  # @return [Webhooks]
91
161
  #
162
+ # @!method webhook_events
163
+ # @macro api_collection
164
+ # @return [WebhookEvents]
165
+ #
166
+ # @!method webhook_lookups
167
+ # @macro api_collection
168
+ # @return [WebhookLookups]
169
+ #
92
170
  %i[
93
171
  authentication
94
172
  authorized_payments
95
173
  captured_payments
96
174
  catalog_products
175
+ disputes
176
+ invoice_templates
177
+ invoices
97
178
  orders
179
+ payout_items
180
+ payouts
98
181
  refunds
182
+ referenced_payout_items
183
+ referenced_payouts
99
184
  shipment_tracking
100
185
  subscriptions
186
+ subscription_plans
187
+ user_info
188
+ users
101
189
  webhooks
190
+ webhook_events
191
+ webhook_lookups
102
192
  ].each do |method_name|
103
193
  define_method(method_name) do
104
194
  client.public_send(method_name)
@@ -106,6 +196,10 @@ module PaypalAPI
106
196
  end
107
197
 
108
198
  # Globally set Client object
199
+ # @api public
200
+ # @example
201
+ # PaypalAPI.client
202
+ # @return [Client]
109
203
  def client
110
204
  raise "#{name}.client must be set" unless @client
111
205
 
@@ -124,12 +218,26 @@ require_relative "paypal-api/network_error_builder"
124
218
  require_relative "paypal-api/request"
125
219
  require_relative "paypal-api/request_executor"
126
220
  require_relative "paypal-api/response"
221
+ require_relative "paypal-api/webhook_verifier"
222
+ require_relative "paypal-api/webhook_verifier/certs_cache"
127
223
  require_relative "paypal-api/api_collections/authentication"
128
224
  require_relative "paypal-api/api_collections/authorized_payments"
129
225
  require_relative "paypal-api/api_collections/captured_payments"
130
226
  require_relative "paypal-api/api_collections/catalog_products"
227
+ require_relative "paypal-api/api_collections/disputes"
228
+ require_relative "paypal-api/api_collections/invoice_templates"
229
+ require_relative "paypal-api/api_collections/invoices"
131
230
  require_relative "paypal-api/api_collections/orders"
231
+ require_relative "paypal-api/api_collections/payout_items"
232
+ require_relative "paypal-api/api_collections/payouts"
132
233
  require_relative "paypal-api/api_collections/refunds"
234
+ require_relative "paypal-api/api_collections/referenced_payout_items"
235
+ require_relative "paypal-api/api_collections/referenced_payouts"
133
236
  require_relative "paypal-api/api_collections/shipment_tracking"
134
237
  require_relative "paypal-api/api_collections/subscriptions"
238
+ require_relative "paypal-api/api_collections/subscription_plans"
239
+ require_relative "paypal-api/api_collections/user_info"
240
+ require_relative "paypal-api/api_collections/users"
135
241
  require_relative "paypal-api/api_collections/webhooks"
242
+ require_relative "paypal-api/api_collections/webhook_events"
243
+ require_relative "paypal-api/api_collections/webhook_lookups"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paypal-rest-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-12 00:00:00.000000000 Z
11
+ date: 2024-08-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: PayPal REST API with no dependencies.
14
14
  email:
@@ -26,10 +26,22 @@ files:
26
26
  - lib/paypal-api/api_collections/authorized_payments.rb
27
27
  - lib/paypal-api/api_collections/captured_payments.rb
28
28
  - lib/paypal-api/api_collections/catalog_products.rb
29
+ - lib/paypal-api/api_collections/disputes.rb
30
+ - lib/paypal-api/api_collections/invoice_templates.rb
31
+ - lib/paypal-api/api_collections/invoices.rb
29
32
  - lib/paypal-api/api_collections/orders.rb
33
+ - lib/paypal-api/api_collections/payout_items.rb
34
+ - lib/paypal-api/api_collections/payouts.rb
35
+ - lib/paypal-api/api_collections/referenced_payout_items.rb
36
+ - lib/paypal-api/api_collections/referenced_payouts.rb
30
37
  - lib/paypal-api/api_collections/refunds.rb
31
38
  - lib/paypal-api/api_collections/shipment_tracking.rb
39
+ - lib/paypal-api/api_collections/subscription_plans.rb
32
40
  - lib/paypal-api/api_collections/subscriptions.rb
41
+ - lib/paypal-api/api_collections/user_info.rb
42
+ - lib/paypal-api/api_collections/users.rb
43
+ - lib/paypal-api/api_collections/webhook_events.rb
44
+ - lib/paypal-api/api_collections/webhook_lookups.rb
33
45
  - lib/paypal-api/api_collections/webhooks.rb
34
46
  - lib/paypal-api/client.rb
35
47
  - lib/paypal-api/config.rb
@@ -40,6 +52,8 @@ files:
40
52
  - lib/paypal-api/request_executor.rb
41
53
  - lib/paypal-api/response.rb
42
54
  - lib/paypal-api/version.rb
55
+ - lib/paypal-api/webhook_verifier.rb
56
+ - lib/paypal-api/webhook_verifier/certs_cache.rb
43
57
  - lib/paypal-rest-api.rb
44
58
  homepage: https://github.com/aglushkov/paypal-api
45
59
  licenses:
@@ -63,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
77
  - !ruby/object:Gem::Version
64
78
  version: '0'
65
79
  requirements: []
66
- rubygems_version: 3.5.17
80
+ rubygems_version: 3.5.18
67
81
  signing_key:
68
82
  specification_version: 4
69
83
  summary: PayPal REST API