Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions lib/adyen/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ def service_url_base(service)

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'"
"Please set Client.live_url_prefix to the portion \n of your merchant-specific URL prior to '-[service]-live.adyenpayments.com'"
end

if @env == :live && supports_live_url_prefix
Expand Down Expand Up @@ -215,13 +214,21 @@ def call_adyen_api(service, action, request_data, headers, version, _with_applic
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)
when 400
full_message = build_error_message(response.body, 'Invalid format or fields')
raise Adyen::FormatError.new(full_message, request_data, response.body)
when 401
full_message = build_error_message(response.body, 'Authentication error')
raise Adyen::AuthenticationError.new(full_message, request_data)
when 403
full_message = build_error_message(response.body, 'Authorisation error')
raise Adyen::PermissionError.new(full_message, request_data, response.body)
when 422
full_message = build_error_message(response.body, 'Validation error')
raise Adyen::ValidationError.new(full_message, request_data, response.body)
when 500..599
full_message = build_error_message(response.body, 'Internal server error')
raise Adyen::ServerError.new(full_message, request_data, response.body)
end

# delete has no response.body (unless it throws an error)
Expand Down Expand Up @@ -346,6 +353,25 @@ def validate_auth_type(service, request_data)
'Checkout service requires API-key or oauth_token'
end
end

# build the error message from the response payload
def build_error_message(response_body, default_message)
full_message = default_message
begin
error_details = response_body
# check different attributes to support both RFC 7807 and legacy models
message = error_details[:detail] || error_details[:message]
error_code = error_details[:errorCode]
if message && error_code
full_message = "#{message} ErrorCode: #{error_code}"
elsif message
full_message = message
end
rescue JSON::ParserError
# If the body isn't valid JSON, we fall back to the default message
end
full_message
end
end
end
# rubocop:enable all
14 changes: 8 additions & 6 deletions lib/adyen/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ def initialize(msg, request, response)
end
end

# when JSON payload is invalid
class FormatError < AdyenError
def initialize(msg, request, response)
super(request, response, msg, 400)
end
end

# when JSON payload cannot be processed (violates business rules)
class ValidationError < AdyenError
def initialize(msg, request, response)
super(request, response, msg, 422)
end
Expand All @@ -97,12 +105,6 @@ def initialize(msg, request)
end
end

class ValidationError < AdyenError
def initialize(msg, request)
super(request, nil, msg, nil)
end
end

# catchall for errors which don't have more specific classes
class APIError < AdyenError
def initialize(msg, request, response, code)
Expand Down
99 changes: 99 additions & 0 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,103 @@
expect(client.service_url_base('Disputes'))
.to eq('https://ca-test.adyen.com/ca/services/DisputesService')
end

it 'raises FormatError on 400 response and checks content' do
client = Adyen::Client.new(api_key: 'api_key', env: :test)
mock_faraday_connection = double(Faraday::Connection)
error_body = {
status: 400,
errorCode: "702",
message: "Structure of CreateCheckoutSessionRequest contains the following unknown fields: [paymentMethod]",
errorType: "validation"
}
mock_response = Faraday::Response.new(status: 400, body: error_body)

allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)

expect {
client.checkout.payments_api.payments({})
}.to raise_error(Adyen::FormatError) do |error|
expect(error.code).to eq(400)
expect(error.msg).to eq('Structure of CreateCheckoutSessionRequest contains the following unknown fields: [paymentMethod] ErrorCode: 702')
expect(error.response[:errorCode]).to eq('702')
end
end

it 'raises ValidationError on 422 response with RestServiceError (based on RFC 7807)' do
client = Adyen::Client.new(api_key: 'api_key', env: :test)
mock_faraday_connection = double(Faraday::Connection)
error_body = {
type: "https://docs.adyen.com/errors/validation",
title: "The request is missing required fields or contains invalid data.",
status: 422,
detail: "It is mandatory to specify a legalEntityId when creating a new account holder.",
invalidFields: [{ "name" => "legalEntityId", "message" => "legalEntityId is not provided" }],
errorCode: "30_011"
}
mock_response = Faraday::Response.new(status: 422, body: error_body)

allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)

expect {
client.checkout.payments_api.payments({})
}.to raise_error(Adyen::ValidationError) do |error|
expect(error.code).to eq(422)
expect(error.msg).to eq('It is mandatory to specify a legalEntityId when creating a new account holder. ErrorCode: 30_011')
expect(error.response[:errorCode]).to eq('30_011')
expect(error.response[:invalidFields]).to have_attributes(size: 1)
end
end

it 'raises ValidationError on 422 response with ServiceError (legacy)' do
client = Adyen::Client.new(api_key: 'api_key', env: :test)
mock_faraday_connection = double(Faraday::Connection)
error_body = {
status: 422,
errorCode: "14_030",
message: "Return URL is missing.",
errorType: "validation",
pspReference: "8816118280275544"
}
mock_response = Faraday::Response.new(status: 422, body: error_body)

allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)

expect {
client.checkout.payments_api.payments({})
}.to raise_error(Adyen::ValidationError) do |error|
expect(error.code).to eq(422)
expect(error.msg).to eq('Return URL is missing. ErrorCode: 14_030')
expect(error.response[:errorCode]).to eq('14_030')
end
end

it 'raises ServerError on 500 response and checks content' do
client = Adyen::Client.new(api_key: 'api_key', env: :test)
mock_faraday_connection = double(Faraday::Connection)
error_body = {
status: 500,
errorCode: "999",
message: "Unexpected error.",
errorType: "server error"
}
mock_response = Faraday::Response.new(status: 500, body: error_body)

allow(Faraday).to receive(:new).and_return(mock_faraday_connection)
allow(mock_faraday_connection).to receive_message_chain(:headers, :[]=)
allow(mock_faraday_connection).to receive(:post).and_return(mock_response)

expect {
client.checkout.payments_api.payments({})
}.to raise_error(Adyen::ServerError) do |error|
expect(error.code).to eq(500)
expect(error.msg).to eq('Unexpected error. ErrorCode: 999')
end
end
end