module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class MerchantWareGateway < Gateway
class_attribute :v4_live_url
self.live_url = self.test_url = 'https://ps1.merchantware.net/MerchantWARE/ws/RetailTransaction/TXRetail.asmx'
self.v4_live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
self.supported_countries = ['US']
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'http://merchantwarehouse.com/merchantware'
self.display_name = 'MerchantWARE'
ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/' }
ENV_NAMESPACES_V4 = { '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/' }
TX_NAMESPACE = 'http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail'
TX_NAMESPACE_V4 = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/'
ACTIONS = {
purchase: 'IssueKeyedSale',
authorize: 'IssueKeyedPreAuth',
capture: 'IssuePostAuth',
void: 'VoidPreAuthorization',
credit: 'IssueKeyedRefund',
reference_credit: 'IssueRefundByReference'
}
# Creates a new MerchantWareGateway
#
# The gateway requires that a valid login, password, and name be passed
# in the +options+ hash.
#
# ==== Options
#
# * :login - The MerchantWARE SiteID.
# * :password - The MerchantWARE Key.
# * :name - The MerchantWARE Name.
def initialize(options = {})
requires!(options, :login, :password, :name)
super
end
# Authorize a credit card for a given amount.
#
# ==== Parameters
# * money - The amount to be authorized as an Integer value in cents.
# * credit_card - The CreditCard details for the transaction.
# * options
# * :order_id - A unique reference for this order (required).
# * :billing_address - The billing address for the cardholder.
def authorize(money, credit_card, options = {})
request = build_purchase_request(:authorize, money, credit_card, options)
commit(:authorize, request)
end
# Authorize and immediately capture funds from a credit card.
#
# ==== Parameters
# * money - The amount to be authorized as anInteger value in cents.
# * credit_card - The CreditCard details for the transaction.
# * options
# * :order_id - A unique reference for this order (required).
# * :billing_address - The billing address for the cardholder.
def purchase(money, credit_card, options = {})
request = build_purchase_request(:purchase, money, credit_card, options)
commit(:purchase, request)
end
# Capture authorized funds from a credit card.
#
# ==== Parameters
# * money - The amount to be captured as anInteger value in cents.
# * authorization - The authorization string returned from the initial authorization.
def capture(money, authorization, options = {})
request = build_capture_request(:capture, money, authorization, options)
commit(:capture, request)
end
# Void a transaction.
#
# ==== Parameters
# * authorization - The authorization string returned from the initial authorization or purchase.
def void(authorization, options = {})
reference, options[:order_id] = split_reference(authorization)
request = v4_soap_request(:void) do |xml|
add_reference_token(xml, reference)
end
commit(:void, request, true)
end
# Refund an amount back a cardholder
#
# ==== Parameters
#
# * money - The amount to be refunded as an Integer value in cents.
# * identification - The credit card you want to refund or the authorization for the existing transaction you are refunding.
# * options
# * :order_id - A unique reference for this order (required when performing a non-referenced credit)
def credit(money, identification, options = {})
if identification.is_a?(String)
ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
else
perform_credit(money, identification, options)
end
end
def refund(money, reference, options = {})
perform_reference_credit(money, reference, options)
end
private
def soap_request(action)
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!
xml.tag! 'env:Envelope', ENV_NAMESPACES do
xml.tag! 'env:Body' do
xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do
add_credentials(xml)
yield xml
end
end
end
xml.target!
end
def v4_soap_request(action)
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!
xml.tag! 'soap:Envelope', ENV_NAMESPACES_V4 do
xml.tag! 'soap:Body' do
xml.tag! ACTIONS[:void], 'xmlns' => TX_NAMESPACE_V4 do
xml.tag! 'merchantName', @options[:name]
xml.tag! 'merchantSiteId', @options[:login]
xml.tag! 'merchantKey', @options[:password]
yield xml
end
end
end
xml.target!
end
def build_purchase_request(action, money, credit_card, options)
requires!(options, :order_id)
soap_request(action) do |xml|
add_invoice(xml, options)
add_amount(xml, money)
add_credit_card(xml, credit_card)
add_address(xml, options)
end
end
def build_capture_request(action, money, identification, options)
reference, options[:order_id] = split_reference(identification)
soap_request(action) do |xml|
add_reference(xml, reference)
add_invoice(xml, options)
add_amount(xml, money)
end
end
def perform_reference_credit(money, identification, options)
reference, options[:order_id] = split_reference(identification)
request = soap_request(:reference_credit) do |xml|
add_reference(xml, reference)
add_invoice(xml, options)
add_amount(xml, money, 'strOverrideAmount')
end
commit(:reference_credit, request)
end
def perform_credit(money, credit_card, options)
requires!(options, :order_id)
request = soap_request(:credit) do |xml|
add_invoice(xml, options)
add_amount(xml, money)
add_credit_card(xml, credit_card)
end
commit(:credit, request)
end
def add_credentials(xml)
xml.tag! 'strSiteId', @options[:login]
xml.tag! 'strKey', @options[:password]
xml.tag! 'strName', @options[:name]
end
def add_invoice(xml, options)
xml.tag! 'strOrderNumber', options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25)
end
def add_amount(xml, money, tag = 'strAmount')
xml.tag! tag, amount(money)
end
def add_reference(xml, reference)
xml.tag! 'strReferenceCode', reference
end
def add_reference_token(xml, reference)
xml.tag! 'token', reference
end
def add_address(xml, options)
if address = options[:billing_address] || options[:address]
xml.tag! 'strAVSStreetAddress', address[:address1]
xml.tag! 'strAVSZipCode', address[:zip]
end
end
def add_credit_card(xml, credit_card)
if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
xml.tag! 'trackData', credit_card.track_data
else
xml.tag! 'strPAN', credit_card.number
xml.tag! 'strExpDate', expdate(credit_card)
xml.tag! 'strCardHolder', credit_card.name
xml.tag! 'strCVCode', credit_card.verification_value if credit_card.verification_value?
end
end
def split_reference(reference)
reference.to_s.split(';')
end
def parse(action, data)
response = {}
xml = REXML::Document.new(data)
root = REXML::XPath.first(xml, "//#{ACTIONS[action]}Response/#{ACTIONS[action]}Result")
root.elements.each do |element|
response[element.name] = element.text
end
status, code, message = response['ApprovalStatus'].split(';')
response[:status] = status
if response[:success] = status == 'APPROVED'
response[:message] = status
else
response[:message] = message
response[:failure_code] = code
end
response
end
def parse_error(http_response)
response = {}
response[:http_code] = http_response.code
response[:http_message] = http_response.message
response[:success] = false
document = REXML::Document.new(http_response.body)
node = REXML::XPath.first(document, '//soap:Fault')
node.elements.each do |element|
response[element.name] = element.text
end
response[:message] = response['faultstring'].to_s.tr("\n", ' ')
response
rescue REXML::ParseException
response[:http_body] = http_response.body
response[:message] = 'Failed to parse the failed response'
response
end
def soap_action(action, v4 = false)
v4 ? "#{TX_NAMESPACE_V4}#{ACTIONS[action]}" : "#{TX_NAMESPACE}/#{ACTIONS[action]}"
end
def url(v4 = false)
v4 ? v4_live_url : live_url
end
def commit(action, request, v4 = false)
begin
data = ssl_post(url(v4), request,
'Content-Type' => 'text/xml; charset=utf-8',
'SOAPAction' => soap_action(action, v4))
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response)
end
Response.new(response[:success], response[:message], response,
test: test?,
authorization: authorization_from(response),
avs_result: { code: response['AVSResponse'] },
cvv_result: response['CVResponse'])
end
def authorization_from(response)
[response['ReferenceID'], response['OrderNumber']].join(';') if response[:success]
end
end
end
end