Skip to content

Commit

Permalink
Adding Oauth Response for access tokens
Browse files Browse the repository at this point in the history
Add new OAuth Response error handling to PayTrace,
Quickbooks, Simetrik, Alelo, CheckoutV2 and Airwallex.
  • Loading branch information
Alma Malambo committed Aug 11, 2023
1 parent 21d987d commit 7f341bc
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 28 deletions.
9 changes: 7 additions & 2 deletions lib/active_merchant/billing/gateways/airwallex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,13 @@ def setup_access_token
'x-client-id' => @client_id,
'x-api-key' => @client_api_key
}
response = ssl_post(build_request_url(:login), nil, token_headers)
JSON.parse(response)['token']
raw_response = ssl_post(build_request_url(:login), nil, token_headers)
response = JSON.parse(raw_response)
if (token = response['token'])
token
else
raise OAuthResponseError.new(response['message'])
end
end

def build_request_url(action, id = nil)
Expand Down
22 changes: 18 additions & 4 deletions lib/active_merchant/billing/gateways/alelo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,14 @@ def fetch_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}

parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
Response.new(true, parsed[:access_token], parsed)
begin
raw_response = ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers)
rescue ResponseError => e
raise OAuthResponseError.new(e)
else
parsed = parse(raw_response)
Response.new(true, parsed[:access_token], parsed)
end
end

def remote_encryption_key(access_token)
Expand Down Expand Up @@ -144,9 +150,11 @@ def ensure_credentials(try_again = true)
access_token: access_token,
multiresp: multiresp.responses.present? ? multiresp : nil
}
rescue ActiveMerchant::OAuthResponseError => e
raise e
rescue ResponseError => e
# retry to generate a new access_token when the provided one is expired
raise e unless try_again && %w(401 404).include?(e.response.code) && @options[:access_token].present?
raise e unless retry?(try_again, e, :access_token)

@options.delete(:access_token)
@options.delete(:encryption_key)
Expand Down Expand Up @@ -206,9 +214,11 @@ def commit(action, body, options, try_again = true)
multiresp.process { resp }

multiresp
rescue ActiveMerchant::OAuthResponseError => e
raise OAuthResponseError.new(e)
rescue ActiveMerchant::ResponseError => e
# Retry on a possible expired encryption key
if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
if retry?(try_again, e, :encryption_key)
@options.delete(:encryption_key)
commit(action, body, options, false)
else
Expand All @@ -217,6 +227,10 @@ def commit(action, body, options, try_again = true)
end
end

def retry?(try_again, error, key)
try_again && %w(401 404).include?(error.response.code) && @options[key].present?
end

def success_from(action, response)
case action
when 'capture/transaction/refund'
Expand Down
10 changes: 8 additions & 2 deletions lib/active_merchant/billing/gateways/checkout_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,14 @@ def access_token_url

def setup_access_token
request = 'grant_type=client_credentials'
response = parse(ssl_post(access_token_url, request, access_token_header))
response['access_token']
begin
raw_response = ssl_post(access_token_url, request, access_token_header)
rescue ResponseError => e
raise OAuthResponseError.new(e)
else
response = parse(raw_response)
response['access_token']
end
end

def commit(action, post, options, authorization = nil, method = :post)
Expand Down
19 changes: 14 additions & 5 deletions lib/active_merchant/billing/gateways/pay_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,15 @@ def acquire_access_token
'Content-Type' => 'application/x-www-form-urlencoded'
}
response = ssl_post(url, data, oauth_headers)
json_response = JSON.parse(response)
json_response = parse(response)

@options[:access_token] = json_response['access_token'] if json_response['access_token']
response
if json_response.include?('error')
oauth_response = Response.new(false, json_response['error_description'])
raise OAuthResponseError.new(oauth_response)
else
@options[:access_token] = json_response['access_token'] if json_response['access_token']
response
end
end

private
Expand Down Expand Up @@ -373,6 +378,12 @@ def commit(action, parameters)
url = base_url + '/v1/' + action
raw_response = ssl_post(url, post_data(parameters), headers)
response = parse(raw_response)
handle_final_response(action, response)
rescue JSON::ParserError
unparsable_response(raw_response)
end

def handle_final_response(action, response)
success = success_from(response)

Response.new(
Expand All @@ -385,8 +396,6 @@ def commit(action, parameters)
test: test?,
error_code: success ? nil : error_code_from(response)
)
rescue JSON::ParserError
unparsable_response(raw_response)
end

def unparsable_response(raw_response)
Expand Down
19 changes: 14 additions & 5 deletions lib/active_merchant/billing/gateways/quickbooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,17 @@ def refresh_access_token
'Authorization' => "Basic #{basic_auth}"
}

response = ssl_post(REFRESH_URI, data, headers)
json_response = JSON.parse(response)
begin
response = ssl_post(REFRESH_URI, data, headers)
rescue ResponseError => e
raise OAuthResponseError.new(e)
else
json_response = JSON.parse(response)

@options[:access_token] = json_response['access_token'] if json_response['access_token']
@options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
response
@options[:access_token] = json_response['access_token'] if json_response['access_token']
@options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token']
response
end
end

def cvv_code_from(response)
Expand Down Expand Up @@ -358,6 +363,10 @@ def extract_response_body_or_raise(response_error)
rescue JSON::ParserError
raise response_error
end

error_code = JSON.parse(response_error.response.body)['code']
raise OAuthResponseError.new(response_error, error_code) if error_code == 'AuthenticationFailed'

response_error.response.body
end

Expand Down
16 changes: 11 additions & 5 deletions lib/active_merchant/billing/gateways/simetrik.rb
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,18 @@ def fetch_access_token
login_info[:client_secret] = @options[:client_secret]
login_info[:audience] = test? ? test_audience : live_audience
login_info[:grant_type] = 'client_credentials'
response = parse(ssl_post(auth_url(), login_info.to_json, {
'content-Type' => 'application/json'
}))

@access_token[:access_token] = response['access_token']
@access_token[:expires_at] = Time.new.to_i + response['expires_in']
begin
raw_response = ssl_post(auth_url(), login_info.to_json, {
'content-Type' => 'application/json'
})
rescue ResponseError => e
raise OAuthResponseError.new(e)
else
response = parse(raw_response)
@access_token[:access_token] = response['access_token']
@access_token[:expires_at] = Time.new.to_i + response['expires_in']
end
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion lib/active_merchant/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ def initialize(response, message = nil)
end

def to_s
"Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
if response&.message&.start_with?('Failed with')
response.message
else
"Failed with #{response.code if response.respond_to?(:code)} #{response.message if response.respond_to?(:message)}"
end
end
end

Expand Down
7 changes: 7 additions & 0 deletions test/remote/gateways/remote_airwallex_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ def setup
@stored_credential_mit_options = { initial_transaction: false, initiator: 'merchant', reason_type: 'recurring' }
end

def test_failed_access_token
assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AirwallexGateway.new({ client_id: 'YOUR_CLIENT_ID', client_api_key: 'YOUR_API_KEY' })
gateway.send :setup_access_token
end
end

def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
Expand Down
10 changes: 6 additions & 4 deletions test/remote/gateways/remote_alelo_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_access_token_success
end

def test_failure_access_token_with_invalid_keys
error = assert_raises(ActiveMerchant::ResponseError) do
error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = AleloGateway.new({ client_id: 'abc123', client_secret: 'abc456' })
gateway.send :fetch_access_token
end
Expand Down Expand Up @@ -145,9 +145,11 @@ def test_successful_purchase_with_geolocalitation
def test_invalid_login
gateway = AleloGateway.new(client_id: 'asdfghj', client_secret: '1234rtytre')

response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_match %r{invalid_client}, response.message
error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway.purchase(@amount, @credit_card, @options)
end

assert_match(/401/, error.message)
end

def test_transcript_scrubbing
Expand Down
16 changes: 16 additions & 0 deletions test/remote/gateways/remote_checkout_v2_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ def setup
)
end

def test_failed_access_token
assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
gateway.send :setup_access_token
end
end

def test_failed_purchase_with_failed_access_token
error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = CheckoutV2Gateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET' })
gateway.purchase(@amount, @credit_card, @options)
end

assert_equal error.message, 'Failed with 400 Bad Request'
end

def test_transcript_scrubbing
declined_card = credit_card('4000300011112220', verification_value: '423')
transcript = capture_transcript(@gateway) do
Expand Down
17 changes: 17 additions & 0 deletions test/remote/gateways/remote_pay_trace_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ def test_acquire_token
assert_not_nil response['access_token']
end

def test_failed_access_token
assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')
gateway.send :acquire_access_token
end
end

def test_failed_purchase_with_failed_access_token
gateway = PayTraceGateway.new(username: 'username', password: 'password', integrator_id: 'uniqueintegrator')

error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway.purchase(1000, @credit_card, @options)
end

assert_equal error.message, 'Failed with The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
end

def test_successful_purchase
response = @gateway.purchase(1000, @credit_card, @options)
assert_success response
Expand Down
17 changes: 17 additions & 0 deletions test/remote/gateways/remote_quickbooks_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ def setup
}
end

def test_failed_access_token
assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })
gateway.send :refresh_access_token
end
end

def test_failed_purchase_with_failed_access_token
gateway = QuickbooksGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', refresh_token: 'YOUR_REFRESH_TOKEN', access_token: 'YOUR_ACCESS_TOKEN' })

error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway.purchase(@amount, @credit_card, @options)
end

assert_equal error.message, 'Failed with 401 Unauthorized'
end

def test_successful_purchase
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
Expand Down
16 changes: 16 additions & 0 deletions test/remote/gateways/remote_simetrik_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ def setup
}
end

def test_failed_access_token
assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
gateway.send :fetch_access_token
end
end

def test_failed_authorize_with_failed_access_token
error = assert_raises(ActiveMerchant::OAuthResponseError) do
gateway = SimetrikGateway.new({ client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_API_KEY', audience: 'audience_url' })
gateway.authorize(@amount, @credit_card, @authorize_options_success)
end

assert_equal error.message, 'Failed with 401 Unauthorized'
end

def test_success_authorize
response = @gateway.authorize(@amount, @credit_card, @authorize_options_success)
assert_success response
Expand Down

0 comments on commit 7f341bc

Please sign in to comment.