lib/active_merchant/billing/gateways/trans_first.rb
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class TransFirstGateway < Gateway
self.test_url = 'https://ws.cert.transfirst.com'
self.live_url = 'https://webservices.primerchants.com'
self.supported_countries = ['US']
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'http://www.transfirst.com/'
self.display_name = 'TransFirst'
UNUSED_CREDIT_CARD_FIELDS = %w(UserId TrackData MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSInd POSEntryMode POSConditionCode EComInd AuthCharInd CardCertData CAVVData)
DECLINED = 'The transaction was declined'
ACTIONS = {
purchase: 'CCSale',
purchase_echeck: 'ACHDebit',
refund: 'CreditCardCredit',
refund_echeck: 'ACHVoidTransaction',
void: 'CreditCardAutoRefundorVoid'
}
ENDPOINTS = {
purchase: 'creditcard.asmx',
purchase_echeck: 'checkverifyws/checkverifyws.asmx',
refund: 'creditcard.asmx',
refund_echeck: 'checkverifyws/checkverifyws.asmx',
void: 'creditcard.asmx'
}
def initialize(options = {})
requires!(options, :login, :password)
super
end
def purchase(money, payment, options = {})
post = {}
add_amount(post, money)
add_payment(post, payment)
add_address(post, options)
add_invoice(post, options) if payment.credit_card?
add_pair(post, :RefID, options[:order_id], required: true)
commit((payment.is_a?(Check) ? :purchase_echeck : :purchase), post)
end
def refund(money, authorization, options = {})
post = {}
transaction_id, payment_type = split_authorization(authorization)
add_amount(post, money)
add_pair(post, :TransID, transaction_id)
add_pair(post, :RefID, options[:order_id], required: true)
commit((payment_type == 'check' ? :refund_echeck : :refund), post)
end
def void(authorization, options = {})
post = {}
transaction_id, = split_authorization(authorization)
add_pair(post, :TransID, transaction_id)
commit(:void, post)
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((&?RegKey=)\w*(&?)), '\1[FILTERED]\2').
gsub(%r((&?CardNumber=)\d*(&?)), '\1[FILTERED]\2').
gsub(%r((&?CVV2=)\d*(&?)), '\1[FILTERED]\2').
gsub(%r((&?TransRoute=)\d*(&?)), '\1[FILTERED]\2').
gsub(%r((&?BankAccountNo=)\d*(&?)), '\1[FILTERED]\2')
end
private
def add_amount(post, money)
add_pair(post, :Amount, amount(money), required: true)
end
def add_address(post, options)
address = options[:billing_address] || options[:address]
if address
add_pair(post, :Address, address[:address1], required: true)
add_pair(post, :ZipCode, address[:zip], required: true)
else
add_pair(post, :Address, '', required: true)
add_pair(post, :ZipCode, '', required: true)
end
end
def add_invoice(post, options)
add_pair(post, :SECCCode, options[:invoice], required: true)
add_pair(post, :PONumber, options[:invoice], required: true)
add_pair(post, :SaleTaxAmount, amount(options[:tax] || 0))
add_pair(post, :TaxIndicator, 0)
add_pair(post, :PaymentDesc, options[:description] || '', required: true)
add_pair(post, :CompanyName, options[:company_name] || '', required: true)
end
def add_payment(post, payment)
if payment.is_a?(Check)
add_echeck(post, payment)
else
add_credit_card(post, payment)
end
end
def add_credit_card(post, payment)
add_pair(post, :CardHolderName, payment.name, required: true)
add_pair(post, :CardNumber, payment.number, required: true)
add_pair(post, :Expiration, expdate(payment), required: true)
add_pair(post, :CVV2, payment.verification_value, required: true)
end
def add_echeck(post, payment)
add_pair(post, :TransRoute, payment.routing_number, required: true)
add_pair(post, :BankAccountNo, payment.account_number, required: true)
add_pair(post, :BankAccountType, add_or_use_default(payment.account_type, 'Checking'), required: true)
add_pair(post, :CheckType, add_or_use_default(payment.account_holder_type, 'Personal'), required: true)
add_pair(post, :Name, payment.name, required: true)
add_pair(post, :ProcessDate, Time.now.strftime('%m%d%y'), required: true)
add_pair(post, :Description, '', required: true)
end
def add_or_use_default(payment_data, default_value)
return payment_data.capitalize if payment_data
return default_value
end
def add_unused_fields(action, post)
return unless action == :purchase
UNUSED_CREDIT_CARD_FIELDS.each do |f|
post[f] = ''
end
end
def expdate(credit_card)
year = format(credit_card.year, :two_digits)
month = format(credit_card.month, :two_digits)
"#{month}#{year}"
end
def parse(data)
response = {}
xml = REXML::Document.new(data)
root = REXML::XPath.first(xml, '*')
if root.nil?
response[:message] = data.to_s.strip
else
root.elements.to_a.each do |node|
response[node.name.underscore.to_sym] = node.text
end
end
response
end
def commit(action, params)
response = parse(ssl_post(url(action), post_data(action, params)))
Response.new(
success_from(response),
message_from(response),
response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response[:avs_code] },
cvv_result: response[:cvv2_code]
)
end
def authorization_from(response)
if response[:status] == 'APPROVED'
"#{response[:trans_id]}|check"
else
"#{response[:trans_id]}|creditcard"
end
end
def success_from(response)
case response[:status]
when 'Authorized'
true
when 'Voided'
true
when 'APPROVED'
true
when 'VOIDED'
true
else
false
end
end
def message_from(response)
case response[:message]
when 'Call Voice Center'
DECLINED
else
response[:message]
end
end
def post_data(action, params = {})
add_unused_fields(action, params)
params[:MerchantID] = @options[:login]
params[:RegKey] = @options[:password]
params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def add_pair(post, key, value, options = {})
post[key] = value if !value.blank? || options[:required]
end
def url(action)
base_url = test? ? test_url : live_url
"#{base_url}/#{ENDPOINTS[action]}/#{ACTIONS[action]}"
end
def split_authorization(authorization)
authorization.split('|')
end
end
end
end