lib/active_merchant/billing/gateways/borgun.rb
require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class BorgunGateway < Gateway
self.display_name = 'Borgun'
self.homepage_url = 'http://www.borgun.com'
self.test_url = 'https://gatewaytest.borgun.is/ws/Heimir.pub.ws:Authorization'
self.live_url = 'https://gateway01.borgun.is/ws/Heimir.pub.ws:Authorization'
self.supported_countries = %w[IS GB HU CZ DE DK SE]
self.default_currency = 'ISK'
self.money_format = :cents
self.supported_cardtypes = %i[visa master american_express diners_club discover jcb]
self.homepage_url = 'https://www.borgun.is/'
def initialize(options = {})
requires!(options, :processor, :merchant_id, :username, :password)
super
end
def purchase(money, payment, options = {})
post = {}
action = ''
if options[:apply_3d_secure] == '1'
add_3ds_preauth_fields(post, options)
action = '3ds_preauth'
else
post[:TransType] = '1'
add_3ds_fields(post, options)
action = 'sale'
end
add_invoice(post, money, options)
add_payment_method(post, payment)
commit(action, post, options)
end
def authorize(money, payment, options = {})
post = {}
action = ''
if options[:apply_3d_secure] == '1'
add_3ds_preauth_fields(post, options)
action = '3ds_preauth'
else
post[:TransType] = '5'
add_3ds_fields(post, options)
action = 'authonly'
end
add_invoice(post, money, options)
add_payment_method(post, payment)
commit(action, post, options)
end
def capture(money, authorization, options = {})
post = {}
post[:TransType] = '1'
add_invoice(post, money, options)
add_reference(post, authorization)
commit('capture', post)
end
def refund(money, authorization, options = {})
post = {}
post[:TransType] = '3'
add_invoice(post, money, options)
add_reference(post, authorization)
commit('refund', post)
end
def void(authorization, options = {})
post = {}
# TransType, TrAmount, and currency must match original values from auth or purchase.
_, _, _, _, _, transtype, tramount, currency = split_authorization(authorization)
post[:TransType] = transtype
options[:currency] = options[:currency] || CURRENCY_CODES.key(currency)
add_invoice(post, tramount.to_i, options)
add_reference(post, authorization)
commit('void', post)
end
def supports_scrubbing
true
end
def scrub(transcript)
transcript.gsub(%r((<PAN>)[^&]*(</PAN>))i, '\1[FILTERED]\2').
gsub(%r((<CVC2>)[^&]*(</CVC2>))i, '\1[FILTERED]\2').
gsub(%r(((?:\r\n)?Authorization: Basic )[^\r\n]+(\r\n)?), '\1[FILTERED]\2')
end
private
CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") }
CURRENCY_CODES['ISK'] = '352'
CURRENCY_CODES['EUR'] = '978'
CURRENCY_CODES['USD'] = '840'
CURRENCY_CODES['GBP'] = '826'
def add_3ds_fields(post, options)
post[:ThreeDSMessageId] = options[:three_ds_message_id] if options[:three_ds_message_id]
post[:ThreeDS_PARes] = options[:three_ds_pares] if options[:three_ds_pares]
post[:ThreeDS_CRes] = options[:three_ds_cres] if options[:three_ds_cres]
end
def add_3ds_preauth_fields(post, options)
post[:SaleDescription] = options[:sale_description] || ''
post[:MerchantReturnURL] = options[:redirect_url] if options[:redirect_url]
end
def add_invoice(post, money, options)
post[:TrAmount] = amount(money)
post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
# The ISK currency must have a currency exponent of 2 on the 3DS request but not on the auth request
if post[:TrCurrency] == '352' && options[:apply_3d_secure] != '1'
post[:TrCurrencyExponent] = 0
else
post[:TrCurrencyExponent] = 2
end
post[:TerminalID] = options[:terminal_id] || '1'
end
def add_payment_method(post, payment_method)
post[:PAN] = payment_method.number
post[:ExpDate] = format(payment_method.year, :two_digits) + format(payment_method.month, :two_digits)
post[:CVC2] = payment_method.verification_value
post[:DateAndTime] = Time.now.strftime('%y%m%d%H%M%S')
post[:RRN] = 'AMRCNT' + six_random_digits
end
def add_reference(post, authorization)
dateandtime, _batch, transaction, rrn, authcode, = split_authorization(authorization)
post[:DateAndTime] = dateandtime
post[:Transaction] = transaction
post[:RRN] = rrn
post[:AuthCode] = authcode
end
def parse(xml, options = nil)
response = {}
doc = Nokogiri::XML(CGI.unescapeHTML(xml))
body = options[:apply_3d_secure] == '1' ? doc.xpath('//get3DSAuthenticationReply') : doc.xpath('//getAuthorizationReply')
body = doc.xpath('//cancelAuthorizationReply') if body.length == 0
body.children.each do |node|
if node.text?
next
elsif node.elements.size == 0
response[node.name.downcase.to_sym] = node.text
else
node.elements.each do |childnode|
name = "#{node.name.downcase}_#{childnode.name.downcase}"
response[name.to_sym] = childnode.text
end
end
end
response
end
def commit(action, post, options = {})
post[:Version] = '1000'
post[:Processor] = @options[:processor]
post[:MerchantID] = @options[:merchant_id]
request = build_request(action, post, options)
raw = ssl_post(url(action), request, headers)
pairs = parse(raw, options)
success = success_from(pairs)
Response.new(
success,
message_from(success, pairs),
pairs,
authorization: authorization_from(pairs, options),
test: test?
)
end
def success_from(response)
(response[:actioncode] == '000') || (response[:status_resultcode] == '0')
end
def message_from(succeeded, response)
if succeeded
'Succeeded'
else
response[:message] || response[:status_errormessage] || "Error with ActionCode=#{response[:actioncode]}"
end
end
def authorization_from(response, options)
authorization = [
response[:dateandtime],
response[:batch],
response[:transaction],
response[:rrn],
response[:authcode],
response[:transtype],
response[:tramount],
response[:trcurrency]
].join('|')
authorization == '|||||||' ? nil : authorization
end
def split_authorization(authorization)
dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency = authorization.split('|')
[dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency]
end
def headers
{
'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:password].to_s)
}
end
def build_request(action, post, options = {})
mode = action == 'void' ? 'cancel' : 'get'
transaction_type = action == '3ds_preauth' ? '3DSAuthentication' : 'Authorization'
xml = Builder::XmlMarkup.new indent: 18
xml.instruct!(:xml, version: '1.0', encoding: 'utf-8')
xml.tag!("#{mode}#{transaction_type}") do
post.each do |field, value|
xml.tag!(field, value)
end
build_airline_xml(xml, options[:passenger_itinerary_data]) if options[:passenger_itinerary_data]
end
inner = CGI.escapeHTML(xml.target!)
envelope(mode, action).sub(/{{ :body }}/, inner)
end
def build_airline_xml(xml, airline_data)
xml.tag!('PassengerItineraryData') do
xml.tag!('A1') do
airline_data.each do |field, value|
xml.tag!(field, value)
end
end
end
end
def envelope(mode, action)
if action == '3ds_preauth'
transaction_action = "#{mode}3DSAuthentication"
request_action = "#{mode}Auth3DSReqXml"
else
transaction_action = "#{mode}AuthorizationInput"
request_action = "#{mode}AuthReqXml"
end
<<-XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:aut="http://Borgun/Heimir/pub/ws/Authorization">
<soapenv:Header/>
<soapenv:Body>
<aut:#{transaction_action}>
<#{request_action}>
{{ :body }}
</#{request_action}>
</aut:#{transaction_action}>
</soapenv:Body>
</soapenv:Envelope>
XML
end
def url(action)
(test? ? test_url : live_url)
end
def six_random_digits
(0...6).map { rand(48..57).chr }.join
end
end
end
end