lib/active_merchant/billing/gateways/iveri.rb
require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class IveriGateway < Gateway
class_attribute :iveri_url
self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx'
self.iveri_url = 'https://portal.host.iveri.com/iVeriWebService/Service.asmx'
self.supported_countries = %w[US ZA GB]
self.default_currency = 'ZAR'
self.money_format = :cents
self.supported_cardtypes = %i[visa master american_express]
self.homepage_url = 'http://www.iveri.com'
self.display_name = 'iVeri'
def initialize(options = {})
requires!(options, :app_id, :cert_id)
super
end
def purchase(money, payment_method, options = {})
post = build_vxml_request('Debit', options) do |xml|
add_auth_purchase_params(xml, money, payment_method, options)
end
commit(post)
end
def authorize(money, payment_method, options = {})
post = build_vxml_request('Authorisation', options) do |xml|
add_auth_purchase_params(xml, money, payment_method, options)
end
commit(post)
end
def capture(money, authorization, options = {})
post = build_vxml_request('Debit', options) do |xml|
add_authorization(xml, authorization, options)
end
commit(post)
end
def refund(money, authorization, options = {})
post = build_vxml_request('Credit', options) do |xml|
add_amount(xml, money, options)
add_authorization(xml, authorization, options)
end
commit(post)
end
def void(authorization, options = {})
post = build_vxml_request('Void', options) do |xml|
add_authorization(xml, authorization, options)
end
commit(post)
end
def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(100, credit_card, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end
def verify_credentials
void = void('', options)
return true if void.message == 'Missing OriginalMerchantTrace'
false
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((CertificateID=\\\")[^\\]*), '\1[FILTERED]').
gsub(%r((<PAN>)[^&]*), '\1[FILTERED]').
gsub(%r((<CardSecurityCode>)[^&]*), '\1[FILTERED]')
end
private
def build_xml_envelope(vxml)
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
xml[:soap].Body do
xml.Execute 'xmlns' => 'http://iveri.com/' do
xml.validateRequest('false')
xml.protocol 'V_XML'
xml.protocolVersion '2.0'
xml.request vxml
end
end
end
end
builder.to_xml
end
def build_vxml_request(action, options)
builder = Nokogiri::XML::Builder.new do |xml|
xml.V_XML('Version' => '2.0', 'CertificateID' => @options[:cert_id], 'Direction' => 'Request') do
xml.Transaction('ApplicationID' => @options[:app_id], 'Command' => action, 'Mode' => mode) do
yield(xml)
end
end
end
builder.doc.root.to_xml
end
def add_auth_purchase_params(post, money, payment_method, options)
add_card_holder_authentication(post, options)
add_amount(post, money, options)
add_electronic_commerce_indicator(post, options) unless options[:three_d_secure]
add_payment_method(post, payment_method, options)
add_three_ds(post, options)
end
def add_amount(post, money, options)
post.Amount(amount(money))
post.Currency(options[:currency] || default_currency)
end
def add_electronic_commerce_indicator(post, options)
post.ElectronicCommerceIndicator(options[:eci]) if options[:eci]
end
def add_authorization(post, authorization, options)
post.MerchantReference(split_auth(authorization)[2])
post.TransactionIndex(split_auth(authorization)[1])
post.OriginalRequestID(split_auth(authorization)[0])
end
def add_payment_method(post, payment_method, options)
post.ExpiryDate("#{format(payment_method.month, :two_digits)}#{payment_method.year}")
add_new_reference(post, options)
post.CardSecurityCode(payment_method.verification_value)
post.PAN(payment_method.number)
end
def add_new_reference(post, options)
post.MerchantReference(options[:order_id] || generate_unique_id)
end
def add_card_holder_authentication(post, options)
post.CardHolderAuthenticationID(options[:xid]) if options[:xid]
post.CardHolderAuthenticationData(options[:cavv]) if options[:cavv]
end
def commit(post)
raw_response =
begin
ssl_post(url, build_xml_envelope(post), headers(post))
rescue ActiveMerchant::ResponseError => e
e.response.body
end
parsed = parse(raw_response)
succeeded = success_from(parsed)
Response.new(
succeeded,
message_from(parsed, succeeded),
parsed,
authorization: authorization_from(parsed),
error_code: error_code_from(parsed, succeeded),
test: test?
)
end
def mode
test? ? 'Test' : 'Live'
end
def url
@options[:url_override].to_s == 'iveri' ? iveri_url : live_url
end
def headers(post)
{
'Content-Type' => 'text/xml; charset=utf-8',
'Content-Length' => post.size.to_s,
'SOAPAction' => 'http://iveri.com/Execute'
}
end
def parse(body)
parsed = {}
vxml = Nokogiri::XML(body).remove_namespaces!.xpath('//Envelope/Body/ExecuteResponse/ExecuteResult').inner_text
doc = Nokogiri::XML(vxml)
doc.xpath('*').each do |node|
if node.elements.empty?
parsed[underscore(node.name)] = node.text
else
node.elements.each do |childnode|
parse_element(parsed, childnode)
end
end
end
parsed
end
def parse_element(parsed, node)
if !node.attributes.empty?
node.attributes.each do |a|
parsed[underscore(node.name) + '_' + underscore(a[1].name)] = a[1].value
end
end
if node.elements.empty?
parsed[underscore(node.name)] = node.text
else
node.elements.each { |e| parse_element(parsed, e) }
end
end
def success_from(response)
response['result_status'] == '0'
end
def message_from(response, succeeded)
if succeeded
'Succeeded'
else
response['result_description'] || response['result_acquirer_description']
end
end
def authorization_from(response)
"#{response['transaction_request_id']}|#{response['transaction_index']}|#{response['merchant_reference']}"
end
def split_auth(authorization)
request_id, transaction_index, merchant_reference = authorization.to_s.split('|')
[request_id, transaction_index, merchant_reference]
end
def error_code_from(response, succeeded)
response['result_code'] unless succeeded
end
def underscore(camel_cased_word)
camel_cased_word.to_s.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
gsub(/([a-z\d])([A-Z])/, '\1_\2').
tr('-', '_').
downcase
end
def add_three_ds(post, options)
return unless three_d_secure = options[:three_d_secure]
post.ElectronicCommerceIndicator(formatted_three_ds_eci(three_d_secure[:eci])) if three_d_secure[:eci]
post.CardHolderAuthenticationID(three_d_secure[:xid]) if three_d_secure[:xid]
post.CardHolderAuthenticationData(three_d_secure[:cavv]) if three_d_secure[:cavv]
post.ThreeDSecure_ProtocolVersion(three_d_secure[:version]) if three_d_secure[:version]
post.ThreeDSecure_DSTransID(three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id]
post.ThreeDSecure_VEResEnrolled(formatted_enrollment(three_d_secure[:enrolled])) if three_d_secure[:enrolled]
end
def formatted_enrollment(val)
case val
when 'Y', 'N', 'U' then val
when true, 'true' then 'Y'
when false, 'false' then 'N'
end
end
def formatted_three_ds_eci(val)
case val
when '05', '02' then 'ThreeDSecure'
when '06', '01' then 'ThreeDSecureAttempted'
when '07' then 'SecureChannel'
else val
end
end
end
end
end