require 'klarna' module ActiveMerchant module Billing class KlarnaGateway < Gateway self.supported_countries = %w(AD AT BE BG CA CH CZ DE DK EE ES FI FR GB GR HR HU IE IS IT LI LT LU LV MC MT NL NO PL PT RO SE SI SK US) self.supported_cardtypes = %i[visa master american_express discover jcb diners_club] self.display_name = 'Klarna' self.homepage_url = 'https://www.klarna.com/' def initialize(options={}) requires!(options, :login, :password, :zone) @options = options super Klarna.configure do |config| config.environment = @options[:test] ? 'test' : 'production' config.api_key = @options[:login] config.api_secret = @options[:password] config.user_agent = "Klarna Gateway/Rails/#{::Rails.version}" config.zone = options[:zone].downcase.to_sym end end def create_session(options) post = {} prepare_billing_address(post, options) prepare_line_items(post, options) prepare_order_data(post, options) response = Klarna.client(:payment).create_session(post) if response.success? message = "Session Created #{response.session_id}" generate_success_response(response, message) else generate_failure_response(response) end end def update_session(session_id, options) post = {} prepare_billing_address(post, options) prepare_line_items(post, options) prepare_order_data(post, options) response = Klarna.client(:payment).update_session(session_id, post) if response.success? message = "Session Updated #{session_id}" generate_success_response(response, message) else generate_failure_response(response) end end def purchase(amount, authorize_token, options={}) post = {} prepare_billing_address(post, options) prepare_line_items(post, options) prepare_order_data(post, options) post["order_amount"] = amount.to_f customer_order(amount, authorize_token, post) end def authorize(amount, authorize_token, options={}) response = Klarna.client(:payment).place_order(authorize_token, options) if response.success? message = "Placed order #{response.order_id}" generate_success_response(response, message, response.order_id, response.fraud_status) else generate_failure_response(response) end end def customer_order(amount, customer_token, options={}) response = Klarna.client(:customer_token).place_order(customer_token, options) if response.success? message = "Placed order #{response.order_id}" generate_success_response(response, message, response.order_id, response.fraud_status) else generate_failure_response(response) end end def capture(amount, order_id, options={}) response = Klarna.client.capture(order_id, {captured_amount: amount, shipping_info: options[:shipping_info]}) if response.success? message = "Captured order with Klarna id: '#{order_id}' Capture id: '#{response['Capture-ID']}'" generate_success_response(response, message, order_id) else generate_failure_response(response) end end def refund(amount, order_id, options={}) # Get the refunded line items for better customer communications post = {} prepare_line_items(post, options) response = Klarna.client(:refund).create(order_id, {refunded_amount: amount}) if response.success? message = "Refunded order with Klarna id: #{order_id}" generate_success_response(response, message, response['Refund-ID']) else generate_failure_response(response) end end def store(authorize_token, options={}) post = {} prepare_billing_address(post, options) prepare_customer_data(post, options) response = Klarna.client(:payment).customer_token(authorize_token, post) if response.success? message = "Client token Created" generate_success_response(response, message) else generate_failure_response(response) end end def get_customer_token(authorize_token) response = Klarna.client(:customer_token).get(authorize_token) if response.success? message = "Client token details" generate_success_response(response, message) else generate_failure_response(response) end end alias_method :credit, :refund def get(order_id) Klarna.client.get(order_id) end def acknowledge(order_id) response = Klarna.client.acknowledge(order_id) if response.success? message = "Extended Period for order with Klarna id: #{order_id}" generate_success_response(response, message) else generate_failure_response(response) end end def extend_period(order_id) response = Klarna.client.extend(order_id) if response.success? message = "Extended Period for order with Klarna id: #{order_id}" generate_success_response(response, message) else generate_failure_response(response) end end def release(order_id) response = Klarna.client.release(order_id) if response.success? message = "Released reamining amount for order with Klarna id: #{order_id}" generate_success_response(response, message, order_id) else generate_failure_response(response) end end def cancel(order_id) response = Klarna.client.cancel(order_id) if response.success? message = "Cancelled order with Klarna id: #{order_id}" generate_success_response(response, message, order_id) else generate_failure_response(response) end end def shipping_info(order_id, capture_id, shipping_info) response = Klarna.client(:capture).shipping_info( order_id, capture_id, shipping_info ) if response.success? message = "Updated shipment info for order: #{order_id}, capture: #{capture_id}" generate_success_response(response, message) else generate_failure_response(response) end end def customer_details(order_id, data) response = Klarna.client.customer_details( order_id, data ) if response.success? message = "Updated customer details for order: #{order_id}" generate_success_response(response, message) else generate_failure_response(response) end end private def generate_success_response(response, message, authorization=nil, fraud_status=nil) ActiveMerchant::Billing::Response.new( true, message, response.body || {}, { authorization:, fraud_status:, response_http_code: response.http_response&.code, request_endpoint: response.request_endpoint, request_method: response.request_method, request_body: response.request_body, response_type: get_response_type(response) } ) end def generate_failure_response(response) ActiveMerchant::Billing::Response.new( false, response.error_code.to_s, response.body || {}, { error_code: response.error_code, response_http_code: response.http_response&.code, request_endpoint: response.request_endpoint, request_method: response.request_method, request_body: response.request_body, response_type: get_response_type(response) } ) end def get_response_type(response) code = response.code&.to_i if code == (200 || 201) 0 elsif (500..599).include? code 1 else 2 end end def prepare_line_items(post, options) return unless options[:order_line_items].present? post["order_lines"] = options[:order_line_items].map do |item| final_amount = item["final_amount"] ? item["final_amount"].to_f * 100 : nil unit_price = item["price"] ? item["price"].to_f * 100 : nil { "name" => item["name"], "quantity" => item["quantity"], "total_amount" => final_amount, "unit_price" => unit_price } end end def prepare_billing_address(post, options) return unless options[:billing_address].present? firstname, lastname = split_names(options[:billing_address][:name]) post["billing_address"] = {} post["billing_address"]["given_name"] = firstname post["billing_address"]["family_name"] = lastname post["billing_address"]["email"] = options[:email] post["billing_address"]["street_address"] = options[:billing_address][:address1] post["billing_address"]["street_address2"] = options[:billing_address][:address2] post["billing_address"]["organization_name"] = options[:billing_address][:company] post["billing_address"]["city"] = options[:billing_address][:city] post["billing_address"]["region"] = options[:billing_address][:state] post["billing_address"]["postal_code"] = options[:billing_address][:zip] post["billing_address"]["country"] = options[:billing_address][:country] end def prepare_order_data(post, options) post["auto_capture"] = true post["intent"] = "buy_and_tokenize" post["purchase_country"] = options.dig(:billing_address, :country) post["purchase_currency"] = options[:currency] post["order_amount"] = options[:total]&.to_f post["order_tax_amount"] = options[:tax]&.to_f end def prepare_customer_data(post, options) post["purchase_country"] = options.dig(:billing_address, :country) post["purchase_currency"] = options[:currency] post["intended_use"] = "SUBSCRIPTION" post["description"] = "For Recurring Payments" post["locale"] = options[:locale] end end end end