diff --git a/CHANGELOG b/CHANGELOG index b07c15a9d37..ac3d2a64664 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -58,6 +58,7 @@ * Vantiv Express: New Xml gateway [DustinHaefele] #4956 * Shift4 V2: Add unstore function [javierpedrozaing] #4953 * CommerceHub: Add 3DS global support [sinourain] #4957 +* SumUp Gateway: Fix refund method [sinourain] #4924 == Version 1.135.0 (August 24, 2023) * PaymentExpress: Correct endpoints [steveh] #4827 diff --git a/lib/active_merchant/billing/gateways/sum_up.rb b/lib/active_merchant/billing/gateways/sum_up.rb index c2824c9f39f..ea3e4e7a4b0 100644 --- a/lib/active_merchant/billing/gateways/sum_up.rb +++ b/lib/active_merchant/billing/gateways/sum_up.rb @@ -35,9 +35,8 @@ def void(authorization, options = {}) end def refund(money, authorization, options = {}) - transaction_id = authorization.split('#')[-1] - payment_currency = options[:currency] || currency(money) - post = money ? { amount: localized_amount(money, payment_currency) } : {} + transaction_id = authorization.split('#').last + post = money ? { amount: amount(money) } : {} add_merchant_data(post, options) commit('me/refund/' + transaction_id, post) @@ -106,10 +105,9 @@ def add_address(post, options) end def add_invoice(post, money, options) - payment_currency = options[:currency] || currency(money) post[:checkout_reference] = options[:order_id] - post[:amount] = localized_amount(money, payment_currency) - post[:currency] = payment_currency + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) post[:description] = options[:description] end @@ -127,29 +125,31 @@ def add_payment(post, payment, options) def commit(action, post, method = :post) response = api_request(action, post.compact, method) + succeeded = success_from(response) Response.new( - success_from(response), - message_from(response), - response, + succeeded, + message_from(succeeded, response), + action.include?('refund') ? { response_code: response.to_s } : response, authorization: authorization_from(response), test: test?, - error_code: error_code_from(response) + error_code: error_code_from(succeeded, response) ) end def api_request(action, post, method) - begin - raw_response = ssl_request(method, live_url + action, post.to_json, auth_headers) - rescue ResponseError => e - raw_response = e.response.body - end - + raw_response = + begin + ssl_request(method, live_url + action, post.to_json, auth_headers) + rescue ResponseError => e + e.response.body + end response = parse(raw_response) - # Multiple invalid parameters - response = format_multiple_errors(response) if raw_response.include?('error_code') && response.is_a?(Array) + response = response.is_a?(Hash) ? response.symbolize_keys : response - return response.symbolize_keys + return format_errors(response) if raw_response.include?('error_code') && response.is_a?(Array) + + response end def parse(body) @@ -157,6 +157,8 @@ def parse(body) end def success_from(response) + return true if response == 204 + return false unless %w(PENDING EXPIRED PAID).include?(response[:status]) response[:transactions].each do |transaction| @@ -166,13 +168,19 @@ def success_from(response) true end - def message_from(response) - return response[:status] if success_from(response) + def message_from(succeeded, response) + if succeeded + return 'Succeeded' if response.is_a?(Integer) + + return response[:status] + end response[:message] || response[:error_message] end def authorization_from(response) + return nil if response.is_a?(Integer) + return response[:id] unless response[:transaction_id] [response[:id], response[:transaction_id]].join('#') @@ -185,21 +193,36 @@ def auth_headers } end - def error_code_from(response) - response[:error_code] unless success_from(response) + def error_code_from(succeeded, response) + response[:error_code] unless succeeded end - def format_multiple_errors(responses) - errors = responses.map do |response| - { error_code: response['error_code'], param: response['param'] } - end - + def format_error(error, key) { + :error_code => error['error_code'], + key => error['param'] + } + end + + def format_errors(errors) + return format_error(errors.first, :message) if errors.size == 1 + + return { error_code: STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], message: 'Validation error', - errors: errors + errors: errors.map { |error| format_error(error, :param) } } end + + def handle_response(response) + case response.code.to_i + # to get the response code (204) when the body is nil + when 200...300 + response.body || response.code + else + raise ResponseError.new(response) + end + end end end end diff --git a/test/fixtures.yml b/test/fixtures.yml index 7cecdaafde8..69d60952694 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -1343,6 +1343,10 @@ sum_up: access_token: SOMECREDENTIAL pay_to_email: SOMECREDENTIAL +sum_up_account_for_successful_purchases: + access_token: SOMECREDENTIAL + pay_to_email: SOMECREDENTIAL + # Working credentials, no need to replace swipe_checkout: login: 2077103073D8B5 diff --git a/test/remote/gateways/remote_sum_up_test.rb b/test/remote/gateways/remote_sum_up_test.rb index e9bc8d9582c..98468e7b80b 100644 --- a/test/remote/gateways/remote_sum_up_test.rb +++ b/test/remote/gateways/remote_sum_up_test.rb @@ -123,6 +123,32 @@ def test_failed_void_invalid_checkout_id assert_equal 'Resource not found', response.message end + # In Sum Up the account can only return checkout/purchase in pending or success status, + # to obtain a successful refund we will need an account that returns the checkout/purchase in successful status + # + # For this example configure in the fixtures => :sum_up_account_for_successful_purchases + def test_successful_refund + gateway = SumUpGateway.new(fixtures(:sum_up_account_for_successful_purchases)) + purchase = gateway.purchase(@amount, @credit_card, @options) + transaction_id = purchase.params['transaction_id'] + assert_not_nil transaction_id + + response = gateway.refund(@amount, transaction_id, {}) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_partial_refund + gateway = SumUpGateway.new(fixtures(:sum_up_account_for_successful_purchases)) + purchase = gateway.purchase(@amount * 10, @credit_card, @options) + transaction_id = purchase.params['transaction_id'] + assert_not_nil transaction_id + + response = gateway.refund(@amount, transaction_id, {}) + assert_success response + assert_equal 'Succeeded', response.message + end + def test_failed_refund_for_pending_checkout purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase diff --git a/test/unit/gateways/sum_up_test.rb b/test/unit/gateways/sum_up_test.rb index 5fc1995f19c..b19510fbab5 100644 --- a/test/unit/gateways/sum_up_test.rb +++ b/test/unit/gateways/sum_up_test.rb @@ -73,7 +73,7 @@ def test_success_from def test_message_from response = @gateway.send(:parse, successful_complete_checkout_response) - message_from = @gateway.send(:message_from, response.symbolize_keys) + message_from = @gateway.send(:message_from, true, response.symbolize_keys) assert_equal 'PENDING', message_from end @@ -83,15 +83,15 @@ def test_authorization_from assert_equal '8d8336a1-32e2-4f96-820a-5c9ee47e76fc', authorization_from end - def test_format_multiple_errors + def test_format_errors responses = @gateway.send(:parse, failed_complete_checkout_array_response) - error_code = @gateway.send(:format_multiple_errors, responses) - assert_equal format_multiple_errors_response, error_code + error_code = @gateway.send(:format_errors, responses) + assert_equal format_errors_response, error_code end def test_error_code_from response = @gateway.send(:parse, failed_complete_checkout_response) - error_code_from = @gateway.send(:error_code_from, response.symbolize_keys) + error_code_from = @gateway.send(:error_code_from, false, response.symbolize_keys) assert_equal 'CHECKOUT_SESSION_IS_EXPIRED', error_code_from end @@ -422,6 +422,11 @@ def failed_complete_checkout_array_response "message": "Validation error", "param": "card", "error_code": "The card is expired" + }, + { + "message": "Validation error", + "param": "card", + "error_code": "The value located under the \'$.card.number\' path is not a valid card number" } ] RESPONSE @@ -484,11 +489,11 @@ def failed_refund_response RESPONSE end - def format_multiple_errors_response + def format_errors_response { error_code: 'MULTIPLE_INVALID_PARAMETERS', message: 'Validation error', - errors: [{ error_code: 'The card is expired', param: 'card' }] + errors: [{ error_code: 'The card is expired', param: 'card' }, { error_code: "The value located under the '$.card.number' path is not a valid card number", param: 'card' }] } end end