module ActiveMerchant #:nodoc: module Billing #:nodoc: class SumUpGateway < Gateway self.live_url = 'https://api.sumup.com/v0.1/' self.supported_countries = %w(AT BE BG BR CH CL CO CY CZ DE DK EE ES FI FR GB GR HR HU IE IT LT LU LV MT NL NO PL PT RO SE SI SK US) self.currencies_with_three_decimal_places = %w(EUR BGN BRL CHF CZK DKK GBP HUF NOK PLN SEK USD) self.default_currency = 'USD' self.homepage_url = 'https://www.sumup.com/' self.display_name = 'SumUp' STANDARD_ERROR_CODE_MAPPING = { multiple_invalid_parameters: 'MULTIPLE_INVALID_PARAMETERS' } def initialize(options = {}) requires!(options, :access_token, :pay_to_email) super end def purchase(money, payment, options = {}) MultiResponse.run do |r| r.process { create_checkout(money, payment, options) } unless options[:checkout_id] r.process { complete_checkout(options[:checkout_id] || r.params['id'], payment, options) } end end def refund(money, authorization, options = {}) transaction_id = authorization.split('#').last post = money ? { amount: amount(money) } : {} add_merchant_data(post, options) commit('me/refund/' + transaction_id, post) end def supports_scrubbing? true end def scrub(transcript) transcript. gsub(%r((Authorization: Bearer )\w+), '\1[FILTERED]'). gsub(%r(("pay_to_email\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). gsub(%r(("cvv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]') end private def create_checkout(money, payment, options) post = {} add_merchant_data(post, options) add_invoice(post, money, options) add_address(post, options) add_customer_data(post, payment, options) add_3ds_data(post, options) commit('checkouts', post) end def complete_checkout(checkout_id, payment, options = {}) post = {} add_payment(post, payment, options) commit('checkouts/' + checkout_id, post, :put) end def add_customer_data(post, payment, options) post[:customer_id] = options[:customer_id] post[:personal_details] = { email: options[:email], first_name: payment&.first_name, last_name: payment&.last_name, tax_id: options[:tax_id] } end def add_merchant_data(post, options) # Required field: pay_to_email # Description: Email address of the merchant to whom the payment is made. post[:pay_to_email] = @options[:pay_to_email] end def add_address(post, options) post[:personal_details] ||= {} if address = (options[:billing_address] || options[:shipping_address] || options[:address]) post[:personal_details][:address] = { city: address[:city], state: address[:state], country: address[:country], line_1: address[:address1], postal_code: address[:zip] } end end def add_invoice(post, money, options) post[:checkout_reference] = options[:order_id] post[:amount] = amount(money) post[:currency] = options[:currency] || currency(money) post[:description] = options[:description] end def add_payment(post, payment, options) post[:payment_type] = options[:payment_type] || 'card' post[:card] = { name: payment.name, number: payment.number, expiry_month: format(payment.month, :two_digits), expiry_year: payment.year, cvv: payment.verification_value } end def add_3ds_data(post, options) post[:redirect_url] = options[:redirect_url] if options[:redirect_url] end def commit(action, post, method = :post) response = api_request(action, post.compact, method) succeeded = success_from(response) Response.new( 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(succeeded, response) ) end def api_request(action, post, method) 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) response = response.is_a?(Hash) ? response.symbolize_keys : response return format_errors(response) if raw_response.include?('error_code') && response.is_a?(Array) response end def parse(body) JSON.parse(body) end def success_from(response) (response.is_a?(Hash) && response[:next_step]) || response == 204 || %w(PENDING PAID).include?(response[:status]) || response[:transactions]&.all? { |transaction| transaction.symbolize_keys[:status] == 'SUCCESSFUL' } end def message_from(succeeded, response) if succeeded return 'Succeeded' if (response.is_a?(Hash) && response[:next_step]) || response == 204 return response[:status] end response[:message] || response[:error_message] || response[:status] 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('#') end def auth_headers { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{options[:access_token]}" } end def error_code_from(succeeded, response) response[:error_code] unless succeeded 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.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