-
Notifications
You must be signed in to change notification settings - Fork 48
/
client.rb
326 lines (286 loc) · 10.8 KB
/
client.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# rubocop:disable Metrics/ParameterLists
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/ClassLength
require 'faraday'
require 'json'
require_relative './errors'
require_relative './result'
module Adyen
class Client
attr_accessor :ws_user, :ws_password, :api_key, :oauth_token, :client, :adapter
attr_reader :env, :connection_options, :adapter_options
def initialize(ws_user: nil, ws_password: nil, api_key: nil, oauth_token: nil, env: :live, adapter: nil, mock_port: 3001,
live_url_prefix: nil, mock_service_url_base: nil, connection_options: nil, adapter_options: nil)
@ws_user = ws_user
@ws_password = ws_password
@api_key = api_key
@oauth_token = oauth_token
@env = env
@adapter = adapter || Faraday.default_adapter
if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.1')
# for faraday 2.1 and higher
@adapter_options = adapter_options || Faraday.default_adapter_options
else
# for faraday 1.x and 2.0
@adapter_options = adapter_options || {}
end
@mock_service_url_base = mock_service_url_base || "http://localhost:#{mock_port}"
@live_url_prefix = live_url_prefix
@connection_options = connection_options || Faraday::ConnectionOptions.new
end
# make sure that env can only be :live, :test, or :mock
def env=(value)
raise ArgumentError, "Invalid value for Client.env: '#{value}'' - must be one of [:live, :test, :mock]" unless %i[
live test mock
].include? value
@env = value
end
# remove 'https' from live_url_prefix if necessary
def live_url_prefix=(value)
value['https://'] = '' unless value['https://'].nil?
@live_url_prefix = value
end
# base URL for API given service and @env
def service_url_base(service)
if @env == :mock
@mock_service_url_base
else
case service
when 'Checkout'
url = "https://checkout-#{@env}.adyen.com"
supports_live_url_prefix = true
when 'Account', 'Fund', 'Notification', 'Hop'
url = "https://cal-#{@env}.adyen.com/cal/services/#{service}"
supports_live_url_prefix = false
when 'Recurring', 'Payment', 'Payout', 'BinLookup', 'StoredValue', 'BalanceControlService'
url = "https://pal-#{@env}.adyen.com/pal/servlet/#{service}"
supports_live_url_prefix = true
when 'PosTerminalManagement'
url = "https://postfmapi-#{@env}.adyen.com/postfmapi/terminal"
supports_live_url_prefix = false
when 'DataProtectionService', 'DisputeService'
url = "https://ca-#{@env}.adyen.com/ca/services/#{service}"
supports_live_url_prefix = false
when 'LegalEntityManagement'
url = "https://kyc-#{@env}.adyen.com/lem"
supports_live_url_prefix = false
when 'BalancePlatform'
url = "https://balanceplatform-api-#{@env}.adyen.com/bcl"
supports_live_url_prefix = false
when 'Transfers'
url = "https://balanceplatform-api-#{@env}.adyen.com/btl"
supports_live_url_prefix = false
when 'Management'
url = "https://management-#{@env}.adyen.com"
supports_live_url_prefix = false
when 'TerminalCloudAPI'
url = "https://terminal-api-#{@env}.adyen.com"
supports_live_url_prefix = false
else
raise ArgumentError, 'Invalid service specified'
end
if @live_url_prefix.nil? && (@env == :live) && supports_live_url_prefix
raise ArgumentError,
"Please set Client.live_url_prefix to the portion \
of your merchant-specific URL prior to '-[service]-live.adyenpayments.com'"
end
if @env == :live && supports_live_url_prefix
url.insert(8, "#{@live_url_prefix}-")
url['adyen.com'] = 'adyenpayments.com'
end
url
end
end
# construct full URL from service and endpoint
def service_url(service, action, version)
if service == "Checkout" && @env == :live
return "#{service_url_base(service)}/checkout/v#{version}/#{action}"
elsif version == nil
return "#{service_url_base(service)}/#{action}"
else
return "#{service_url_base(service)}/v#{version}/#{action}"
end
end
# send request to adyen API
def call_adyen_api(service, action, request_data, headers, version, _with_application_info: false)
# get URL for requested endpoint
url = service_url(service, action.is_a?(String) ? action : action.fetch(:url), version)
auth_type = auth_type(service, request_data)
# initialize Faraday connection object
conn = Faraday.new(url, @connection_options) do |faraday|
faraday.adapter @adapter, **@adapter_options
faraday.headers['Content-Type'] = 'application/json'
faraday.headers['User-Agent'] = "#{Adyen::NAME}/#{Adyen::VERSION}"
# set header based on auth_type and service
auth_header(auth_type, faraday)
# add optional headers if specified in request
# will overwrite default headers if overlapping
headers.map do |key, value|
faraday.headers[key] = value
end
# add library headers
faraday.headers['adyen-library-name'] = Adyen::NAME
faraday.headers['adyen-library-version'] = Adyen::VERSION
end
# if json string convert to hash
# needed to add applicationInfo
request_data = JSON.parse(request_data) if request_data.is_a?(String)
# convert to json
request_data = request_data.to_json
if action.is_a?(::Hash)
if action.fetch(:method) == 'get'
begin
response = conn.get
rescue Faraday::ConnectionFailed => e
raise e, "Connection to #{url} failed"
end
end
if action.fetch(:method) == 'delete'
begin
response = conn.delete
rescue Faraday::ConnectionFailed => e
raise e, "Connection to #{url} failed"
end
end
if action.fetch(:method) == 'patch'
begin
response = conn.patch do |req|
req.body = request_data
end
rescue Faraday::ConnectionFailed => e
raise e, "Connection to #{url} failed"
end
end
if action.fetch(:method) == 'post'
# post request to Adyen
begin
response = conn.post do |req|
req.body = request_data
end
rescue Faraday::ConnectionFailed => e
raise e, "Connection to #{url} failed"
end
end
else
begin
response = conn.post do |req|
req.body = request_data
end
rescue Faraday::ConnectionFailed => e
raise e, "Connection to #{url} failed"
end
end
# check for API errors
case response.status
when 401
raise Adyen::AuthenticationError.new(
'Invalid API authentication; https://docs.adyen.com/user-management/how-to-get-the-api-key', request_data
)
when 403
raise Adyen::PermissionError.new('Missing user permissions; https://docs.adyen.com/user-management/user-roles',
request_data, response.body)
end
# delete has no response.body (unless it throws an error)
if response.body.nil? || response.body === ''
AdyenResult.new('{}', response.headers, response.status)
# terminal API async call returns always 'ok'
elsif response.body === 'ok'
AdyenResult.new('{}', response.headers, response.status)
else
AdyenResult.new(response.body, response.headers, response.status)
end
end
# services
def checkout
@checkout ||= Adyen::Checkout.new(self)
end
def payment
@payment ||= Adyen::Payment.new(self)
end
def payout
@payout ||= Adyen::Payout.new(self)
end
def recurring
@recurring ||= Adyen::Recurring.new(self)
end
def marketpay
@marketpay ||= Adyen::Marketpay::Marketpay.new(self)
end
def pos_terminal_management
@pos_terminal_management ||= Adyen::PosTerminalManagement.new(self)
end
def data_protection
@data_protection ||= Adyen::DataProtection.new(self)
end
def dispute
@dispute ||= Adyen::Dispute.new(self)
end
def bin_lookup
@bin_lookup ||= Adyen::BinLookup.new(self)
end
def legal_entity_management
@legal_entity_management ||= Adyen::LegalEntityManagement.new(self)
end
def balance_platform
@balance_platform ||= Adyen::BalancePlatform.new(self)
end
def transfers
@transfers ||= Adyen::Transfers.new(self)
end
def management
@management ||= Adyen::Management.new(self)
end
def stored_value
@stored_value ||= Adyen::StoredValue.new(self)
end
def balance_control_service
@balance_control_service ||= Adyen::BalanceControlService.new(self)
end
def terminal_cloud_api
@terminal_cloud_api ||= Adyen::TerminalCloudAPI.new(self)
end
private
def auth_header(auth_type, faraday)
case auth_type
when "basic"
if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.0')
# for faraday 2.0 and higher
faraday.request :authorization, :basic, @ws_user, @ws_password
else
# for faraday 1.x
faraday.basic_auth(@ws_user, @ws_password)
end
when "api-key"
faraday.headers["x-api-key"] = @api_key
when "oauth"
faraday.headers["Authorization"] = "Bearer #{@oauth_token}"
end
end
def auth_type(service, request_data)
# make sure valid authentication has been provided
validate_auth_type(service, request_data)
# Will prioritize authentication methods in this order:
# api-key, oauth, basic
return "api-key" unless @api_key.nil?
return "oauth" unless @oauth_token.nil?
"basic"
end
def validate_auth_type(service, request_data)
# ensure authentication has been provided
if @api_key.nil? && @oauth_token.nil? && (@ws_password.nil? || @ws_user.nil?)
raise Adyen::AuthenticationError.new(
'No authentication found - please set api_key, oauth_token, or ws_user and ws_password',
request_data
)
end
if service == "PaymentSetupAndVerification" && @api_key.nil? && @oauth_token.nil? && @ws_password.nil? && @ws_user.nil?
raise Adyen::AuthenticationError.new('Checkout service requires API-key or oauth_token', request_data),
'Checkout service requires API-key or oauth_token'
end
end
end
end
# rubocop:enable all