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.
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