lib/active_merchant/billing/gateways/exact.rb
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class ExactGateway < Gateway
self.live_url = self.test_url = 'https://secure2.e-xact.com/vplug-in/transaction/rpc-enc/service.asmx'
API_VERSION = '8.5'
TEST_LOGINS = [{ login: 'A00049-01', password: 'test1' },
{ login: 'A00427-01', password: 'testus' }]
TRANSACTIONS = { sale: '00',
authorization: '01',
capture: '32',
credit: '34' }
ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' }
SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request',
'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' }
SEND_AND_COMMIT_SOURCE_ATTRIBUTES = { 'xmlns:n2' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes',
'xsi:type' => 'n2:Transaction' }
POST_HEADERS = { 'soapAction' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit',
'Content-Type' => 'text/xml' }
SUCCESS = 'true'
SENSITIVE_FIELDS = %i[verification_str2 expiry_date card_number]
self.supported_countries = %w[CA US]
self.supported_cardtypes = %i[visa master american_express jcb discover]
self.homepage_url = 'http://www.e-xact.com'
self.display_name = 'E-xact'
def initialize(options = {})
requires!(options, :login, :password)
super
end
def authorize(money, credit_card, options = {})
commit(:authorization, build_sale_or_authorization_request(money, credit_card, options))
end
def purchase(money, credit_card, options = {})
commit(:sale, build_sale_or_authorization_request(money, credit_card, options))
end
def capture(money, authorization, options = {})
commit(:capture, build_capture_or_credit_request(money, authorization, options))
end
def credit(money, authorization, options = {})
ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
def refund(money, authorization, options = {})
commit(:credit, build_capture_or_credit_request(money, authorization, options))
end
private
def build_request(action, body)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
xml.tag! 'env:Body' do
xml.tag! 'n1:SendAndCommit', SEND_AND_COMMIT_ATTRIBUTES do
xml.tag! 'SendAndCommitSource', SEND_AND_COMMIT_SOURCE_ATTRIBUTES do
add_credentials(xml)
add_transaction_type(xml, action)
xml << body
end
end
end
end
xml.target!
end
def build_sale_or_authorization_request(money, credit_card, options)
xml = Builder::XmlMarkup.new
add_amount(xml, money)
add_credit_card(xml, credit_card)
add_customer_data(xml, options)
add_invoice(xml, options)
xml.target!
end
def build_capture_or_credit_request(money, identification, options)
xml = Builder::XmlMarkup.new
add_identification(xml, identification)
add_amount(xml, money)
add_customer_data(xml, options)
xml.target!
end
def add_credentials(xml)
xml.tag! 'ExactID', @options[:login]
xml.tag! 'Password', @options[:password]
end
def add_transaction_type(xml, action)
xml.tag! 'Transaction_Type', TRANSACTIONS[action]
end
def add_identification(xml, identification)
authorization_num, transaction_tag = identification.split(';')
xml.tag! 'Authorization_Num', authorization_num
xml.tag! 'Transaction_Tag', transaction_tag
end
def add_amount(xml, money)
xml.tag! 'DollarAmount', amount(money)
end
def add_credit_card(xml, credit_card)
xml.tag! 'Card_Number', credit_card.number
xml.tag! 'Expiry_Date', expdate(credit_card)
xml.tag! 'CardHoldersName', credit_card.name
if credit_card.verification_value?
xml.tag! 'CVD_Presence_Ind', '1'
xml.tag! 'VerificationStr2', credit_card.verification_value
end
end
def add_customer_data(xml, options)
xml.tag! 'Customer_Ref', options[:customer]
xml.tag! 'Client_IP', options[:ip]
xml.tag! 'Client_Email', options[:email]
end
def add_address(xml, options)
if address = options[:billing_address] || options[:address]
xml.tag! 'ZipCode', address[:zip]
end
end
def add_invoice(xml, options)
xml.tag! 'Reference_No', options[:order_id]
xml.tag! 'Reference_3', options[:description]
end
def expdate(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
def commit(action, request)
response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS))
Response.new(
successful?(response),
message_from(response),
response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs] },
cvv_result: response[:cvv2]
)
rescue ResponseError => e
case e.response.code
when '401'
return Response.new(false, "Invalid Login: #{e.response.body}", {}, test: test?)
else
raise
end
end
def successful?(response)
response[:transaction_approved] == SUCCESS
end
def authorization_from(response)
if response[:authorization_num] && response[:transaction_tag]
"#{response[:authorization_num]};#{response[:transaction_tag]}"
else
''
end
end
def message_from(response)
if response[:faultcode] && response[:faultstring]
response[:faultstring]
elsif response[:error_number] != '0'
response[:error_description]
else
result = response[:exact_message] || ''
result << " - #{response[:bank_message]}" unless response[:bank_message].blank?
result
end
end
def parse(xml)
response = {}
xml = REXML::Document.new(xml)
if root = REXML::XPath.first(xml, '//types:TransactionResult')
parse_elements(response, root)
elsif root = REXML::XPath.first(xml, '//soap:Fault')
parse_elements(response, root)
end
response.delete_if { |k, _v| SENSITIVE_FIELDS.include?(k) }
end
def parse_elements(response, root)
root.elements.to_a.each do |node|
response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip
end
end
end
end
end