Skip to content

Commit a511b74

Browse files
authored
Merge pull request #310 from Adyen/extend-error-handling
Improve error handling
2 parents 17abb01 + 69aa873 commit a511b74

File tree

3 files changed

+142
-15
lines changed

3 files changed

+142
-15
lines changed

lib/adyen/client.rb

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ def service_url_base(service)
113113

114114
if @live_url_prefix.nil? && (@env == :live) && supports_live_url_prefix
115115
raise ArgumentError,
116-
"Please set Client.live_url_prefix to the portion \
117-
of your merchant-specific URL prior to '-[service]-live.adyenpayments.com'"
116+
"Please set Client.live_url_prefix to the portion \n of your merchant-specific URL prior to '-[service]-live.adyenpayments.com'"
118117
end
119118

120119
if @env == :live && supports_live_url_prefix
@@ -215,13 +214,21 @@ def call_adyen_api(service, action, request_data, headers, version, _with_applic
215214
end
216215
# check for API errors
217216
case response.status
218-
when 401
219-
raise Adyen::AuthenticationError.new(
220-
'Invalid API authentication; https://docs.adyen.com/user-management/how-to-get-the-api-key', request_data
221-
)
222-
when 403
223-
raise Adyen::PermissionError.new('Missing user permissions; https://docs.adyen.com/user-management/user-roles',
224-
request_data, response.body)
217+
when 400
218+
full_message = build_error_message(response.body, 'Invalid format or fields')
219+
raise Adyen::FormatError.new(full_message, request_data, response.body)
220+
when 401
221+
full_message = build_error_message(response.body, 'Authentication error')
222+
raise Adyen::AuthenticationError.new(full_message, request_data)
223+
when 403
224+
full_message = build_error_message(response.body, 'Authorisation error')
225+
raise Adyen::PermissionError.new(full_message, request_data, response.body)
226+
when 422
227+
full_message = build_error_message(response.body, 'Validation error')
228+
raise Adyen::ValidationError.new(full_message, request_data, response.body)
229+
when 500..599
230+
full_message = build_error_message(response.body, 'Internal server error')
231+
raise Adyen::ServerError.new(full_message, request_data, response.body)
225232
end
226233

227234
# delete has no response.body (unless it throws an error)
@@ -346,6 +353,25 @@ def validate_auth_type(service, request_data)
346353
'Checkout service requires API-key or oauth_token'
347354
end
348355
end
356+
357+
# build the error message from the response payload
358+
def build_error_message(response_body, default_message)
359+
full_message = default_message
360+
begin
361+
error_details = response_body
362+
# check different attributes to support both RFC 7807 and legacy models
363+
message = error_details[:detail] || error_details[:message]
364+
error_code = error_details[:errorCode]
365+
if message && error_code
366+
full_message = "#{message} ErrorCode: #{error_code}"
367+
elsif message
368+
full_message = message
369+
end
370+
rescue JSON::ParserError
371+
# If the body isn't valid JSON, we fall back to the default message
372+
end
373+
full_message
374+
end
349375
end
350376
end
351377
# rubocop:enable all

lib/adyen/errors.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,15 @@ def initialize(msg, request, response)
7979
end
8080
end
8181

82+
# when JSON payload is invalid
8283
class FormatError < AdyenError
84+
def initialize(msg, request, response)
85+
super(request, response, msg, 400)
86+
end
87+
end
88+
89+
# when JSON payload cannot be processed (violates business rules)
90+
class ValidationError < AdyenError
8391
def initialize(msg, request, response)
8492
super(request, response, msg, 422)
8593
end
@@ -97,12 +105,6 @@ def initialize(msg, request)
97105
end
98106
end
99107

100-
class ValidationError < AdyenError
101-
def initialize(msg, request)
102-
super(request, nil, msg, nil)
103-
end
104-
end
105-
106108
# catchall for errors which don't have more specific classes
107109
class APIError < AdyenError
108110
def initialize(msg, request, response, code)

spec/client_spec.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,103 @@
302302
expect(client.service_url_base('Disputes'))
303303
.to eq('https://ca-test.adyen.com/ca/services/DisputesService')
304304
end
305+
306+
it 'raises FormatError on 400 response and checks content' do
307+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
308+
mock_faraday_connection = double(Faraday::Connection)
309+
error_body = {
310+
status: 400,
311+
errorCode: "702",
312+
message: "Structure of CreateCheckoutSessionRequest contains the following unknown fields: [paymentMethod]",
313+
errorType: "validation"
314+
}
315+
mock_response = Faraday::Response.new(status: 400, body: error_body)
316+
317+
allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
318+
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
319+
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)
320+
321+
expect {
322+
client.checkout.payments_api.payments({})
323+
}.to raise_error(Adyen::FormatError) do |error|
324+
expect(error.code).to eq(400)
325+
expect(error.msg).to eq('Structure of CreateCheckoutSessionRequest contains the following unknown fields: [paymentMethod] ErrorCode: 702')
326+
expect(error.response[:errorCode]).to eq('702')
327+
end
328+
end
329+
330+
it 'raises ValidationError on 422 response with RestServiceError (based on RFC 7807)' do
331+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
332+
mock_faraday_connection = double(Faraday::Connection)
333+
error_body = {
334+
type: "https://docs.adyen.com/errors/validation",
335+
title: "The request is missing required fields or contains invalid data.",
336+
status: 422,
337+
detail: "It is mandatory to specify a legalEntityId when creating a new account holder.",
338+
invalidFields: [{ "name" => "legalEntityId", "message" => "legalEntityId is not provided" }],
339+
errorCode: "30_011"
340+
}
341+
mock_response = Faraday::Response.new(status: 422, body: error_body)
342+
343+
allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
344+
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
345+
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)
346+
347+
expect {
348+
client.checkout.payments_api.payments({})
349+
}.to raise_error(Adyen::ValidationError) do |error|
350+
expect(error.code).to eq(422)
351+
expect(error.msg).to eq('It is mandatory to specify a legalEntityId when creating a new account holder. ErrorCode: 30_011')
352+
expect(error.response[:errorCode]).to eq('30_011')
353+
expect(error.response[:invalidFields]).to have_attributes(size: 1)
354+
end
355+
end
356+
357+
it 'raises ValidationError on 422 response with ServiceError (legacy)' do
358+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
359+
mock_faraday_connection = double(Faraday::Connection)
360+
error_body = {
361+
status: 422,
362+
errorCode: "14_030",
363+
message: "Return URL is missing.",
364+
errorType: "validation",
365+
pspReference: "8816118280275544"
366+
}
367+
mock_response = Faraday::Response.new(status: 422, body: error_body)
368+
369+
allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
370+
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
371+
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)
372+
373+
expect {
374+
client.checkout.payments_api.payments({})
375+
}.to raise_error(Adyen::ValidationError) do |error|
376+
expect(error.code).to eq(422)
377+
expect(error.msg).to eq('Return URL is missing. ErrorCode: 14_030')
378+
expect(error.response[:errorCode]).to eq('14_030')
379+
end
380+
end
381+
382+
it 'raises ServerError on 500 response and checks content' do
383+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
384+
mock_faraday_connection = double(Faraday::Connection)
385+
error_body = {
386+
status: 500,
387+
errorCode: "999",
388+
message: "Unexpected error.",
389+
errorType: "server error"
390+
}
391+
mock_response = Faraday::Response.new(status: 500, body: error_body)
392+
393+
allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
394+
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
395+
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)
396+
397+
expect {
398+
client.checkout.payments_api.payments({})
399+
}.to raise_error(Adyen::ServerError) do |error|
400+
expect(error.code).to eq(500)
401+
expect(error.msg).to eq('Unexpected error. ErrorCode: 999')
402+
end
403+
end
305404
end

0 commit comments

Comments
 (0)