module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class IpgGateway < Gateway
self.test_url = 'https://test.ipg-online.com/ipgapi/services'
self.live_url = 'https://www5.ipg-online.com'
self.supported_countries = %w(AR)
self.default_currency = 'ARS'
self.supported_cardtypes = %i[visa master american_express discover]
self.homepage_url = 'https://www.ipg-online.com'
self.display_name = 'IPG'
CURRENCY_CODES = {
'ARS' => '032'
}
ACTION_REQUEST_ITEMS = %w(vault unstore)
def initialize(options = {})
requires!(options, :store_id, :user_id, :password, :pem, :pem_password)
@credentials = options
@hosted_data_id = nil
super
end
def purchase(money, payment, options = {})
xml = build_purchase_and_authorize_request(money, payment, options)
commit('sale', xml, options)
end
def authorize(money, payment, options = {})
xml = build_purchase_and_authorize_request(money, payment, options)
commit('preAuth', xml, options)
end
def capture(money, authorization, options = {})
xml = build_capture_and_refund_request(money, authorization, options)
commit('postAuth', xml, options)
end
def refund(money, authorization, options = {})
xml = build_capture_and_refund_request(money, authorization, options)
commit('return', xml, options)
end
def void(authorization, options = {})
xml = Builder::XmlMarkup.new(indent: 2)
add_transaction_details(xml, options.merge!({ order_id: authorization }))
commit('void', xml, options)
end
def store(credit_card, options = {})
@hosted_data_id = options[:hosted_data_id] || generate_unique_id
xml = Builder::XmlMarkup.new(indent: 2)
add_storage_item(xml, credit_card, options)
commit('vault', xml, options)
end
def unstore(hosted_data_id)
xml = Builder::XmlMarkup.new(indent: 2)
add_unstore_item(xml, hosted_data_id)
commit('unstore', xml)
end
def verify(credit_card, options = {})
options[:currency] = self.default_currency unless options[:currency] && !options[:currency].empty?
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 supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r(().+()), '\1[FILTERED]\2').
gsub(%r(().+()), '\1[FILTERED]\2').
gsub(%r(().+()), '\1[FILTERED]\2')
end
private
NAMESPACE_BASE_URL = 'http://ipg-online.com'
def build_purchase_and_authorize_request(money, payment, options)
xml = Builder::XmlMarkup.new(indent: 2)
add_credit_card(xml, payment, options)
add_sub_merchant(xml, options[:submerchant]) if options[:submerchant]
add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure]
add_stored_credentials(xml, options) if options[:stored_credential] || options[:recurring_type]
add_payment(xml, money, payment, options)
add_transaction_details(xml, options)
add_billing(xml, options[:billing]) if options[:billing]
add_shipping(xml, options[:shipping]) if options[:shipping]
xml
end
def build_capture_and_refund_request(money, authorization, options)
xml = Builder::XmlMarkup.new(indent: 2)
add_payment(xml, money, nil, options)
add_transaction_details(xml, options.merge!({ order_id: authorization }), true)
xml
end
def build_order_request(xml, action, body)
xml.tag!('ipg:IPGApiOrderRequest') do
xml.tag!('v1:Transaction') do
add_transaction_type(xml, action)
xml << body.target!
end
end
end
def build_action_request(xml, action, body)
xml.tag!('ns4:IPGApiActionRequest', ipg_action_namespaces) do
xml.tag!('ns2:Action') do
xml << body.target!
end
end
end
def build_soap_request(action, body)
xml = Builder::XmlMarkup.new(indent: 2)
xml.tag!('soapenv:Envelope', envelope_namespaces) do
xml.tag!('soapenv:Header')
xml.tag!('soapenv:Body') do
build_order_request(xml, action, body) unless ACTION_REQUEST_ITEMS.include?(action)
build_action_request(xml, action, body) if ACTION_REQUEST_ITEMS.include?(action)
end
end
xml.target!
end
def add_stored_credentials(xml, params)
recurring_type = params[:stored_credential][:initial_transaction] ? 'FIRST' : 'REPEAT' if params[:stored_credential]
recurring_type = params[:recurring_type] if params[:recurring_type]
xml.tag!('v1:recurringType', recurring_type)
end
def add_storage_item(xml, credit_card, options)
requires!(options.merge!({ credit_card: credit_card, hosted_data_id: @hosted_data_id }), :credit_card, :hosted_data_id)
xml.tag!('ns2:StoreHostedData') do
xml.tag!('ns2:DataStorageItem') do
add_credit_card(xml, credit_card, {}, 'ns2')
add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure]
xml.tag!('ns2:HostedDataID', @hosted_data_id) if @hosted_data_id
end
end
end
def add_unstore_item(xml, hosted_data_id)
requires!({}.merge!({ hosted_data_id: hosted_data_id }), :hosted_data_id)
xml.tag!('ns2:StoreHostedData') do
xml.tag!('ns2:DataStorageItem') do
xml.tag!('ns2:Function', 'delete')
xml.tag!('ns2:HostedDataID', hosted_data_id)
end
end
end
def add_transaction_type(xml, type)
xml.tag!('v1:CreditCardTxType') do
xml.tag!('v1:StoreId', @credentials[:store_id])
xml.tag!('v1:Type', type)
end
end
def add_credit_card(xml, payment, options = {}, credit_envelope = 'v1')
if payment&.is_a?(CreditCard)
requires!(options.merge!({ card_number: payment.number, month: payment.month, year: payment.year }), :card_number, :month, :year)
xml.tag!("#{credit_envelope}:CreditCardData") do
xml.tag!('v1:CardNumber', payment.number) if payment.number
xml.tag!('v1:ExpMonth', format(payment.month, :two_digits)) if payment.month
xml.tag!('v1:ExpYear', format(payment.year, :two_digits)) if payment.year
xml.tag!('v1:CardCodeValue', payment.verification_value) if payment.verification_value
xml.tag!('v1:Brand', options[:brand]) if options[:brand]
end
end
if options[:card_function_type]
xml.tag!('v1:cardFunction') do
xml.tag!('v1:Type', options[:card_function_type])
end
end
if options[:track_data]
xml.tag!("#{credit_envelope}:CreditCardData") do
xml.tag!('v1:TrackData', options[:track_data])
end
end
end
def add_sub_merchant(xml, submerchant)
xml.tag!('v1:SubMerchant') do
xml.tag!('v1:Mcc', submerchant[:mcc]) if submerchant[:mcc]
xml.tag!('v1:LegalName', submerchant[:legal_name]) if submerchant[:legal_name]
add_address(xml, submerchant[:address]) if submerchant[:address]
add_document(xml, submerchant[:document]) if submerchant[:document]
xml.tag!('v1:MerchantID', submerchant[:merchant_id]) if submerchant[:merchant_id]
end
end
def add_address(xml, address)
xml.tag!('v1:Address') do
xml.tag!('v1:Address1', address[:address1]) if address[:address1]
xml.tag!('v1:Address2', address[:address2]) if address[:address2]
xml.tag!('v1:Zip', address[:zip]) if address[:zip]
xml.tag!('v1:City', address[:city]) if address[:city]
xml.tag!('v1:State', address[:state]) if address[:state]
xml.tag!('v1:Country', address[:country]) if address[:country]
end
end
def add_document(xml, document)
xml.tag!('v1:Document') do
xml.tag!('v1:Type', document[:type]) if document[:type]
xml.tag!('v1:Number', document[:number]) if document[:number]
end
end
def add_three_d_secure(xml, three_d_secure)
xml.tag!('v1:CreditCard3DSecure') do
xml.tag!('v1:AuthenticationValue', three_d_secure[:cavv]) if three_d_secure[:cavv]
xml.tag!('v1:XID', three_d_secure[:xid]) if three_d_secure[:xid]
xml.tag!('v1:Secure3D2TransactionStatus', three_d_secure[:directory_response_status]) if three_d_secure[:directory_response_status]
xml.tag!('v1:Secure3D2AuthenticationResponse', three_d_secure[:authentication_response_status]) if three_d_secure[:authentication_response_status]
xml.tag!('v1:Secure3DProtocolVersion', three_d_secure[:version]) if three_d_secure[:version]
xml.tag!('v1:DirectoryServerTransactionId', three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id]
end
end
def add_transaction_details(xml, options, pre_order = false)
requires!(options, :order_id) if pre_order
xml.tag!('v1:TransactionDetails') do
xml.tag!('v1:OrderId', options[:order_id]) if options[:order_id]
xml.tag!('v1:MerchantTransactionId', options[:merchant_transaction_id]) if options[:merchant_transaction_id]
xml.tag!('v1:Ip', options[:ip]) if options[:ip]
xml.tag!('v1:Tdate', options[:t_date]) if options[:t_date]
xml.tag!('v1:IpgTransactionId', options[:ipg_transaction_id]) if options[:ipg_transaction_id]
xml.tag!('v1:ReferencedMerchantTransactionId', options[:referenced_merchant_transaction_id]) if options[:referenced_merchant_transaction_id]
xml.tag!('v1:TransactionOrigin', options[:transaction_origin]) if options[:transaction_origin]
xml.tag!('v1:InvoiceNumber', options[:invoice_number]) if options[:invoice_number]
xml.tag!('v1:DynamicMerchantName', options[:dynamic_merchant_name]) if options[:dynamic_merchant_name]
xml.tag!('v1:Comments', options[:comments]) if options[:comments]
if options[:terminal_id]
xml.tag!('v1:Terminal') do
xml.tag!('v1:TerminalID', options[:terminal_id]) if options[:terminal_id]
end
end
end
end
def add_payment(xml, money, payment, options)
requires!(options.merge!({ money: money }), :currency, :money)
xml.tag!('v1:Payment') do
xml.tag!('v1:HostedDataID', payment) if payment&.is_a?(String)
xml.tag!('v1:HostedDataStoreID', options[:hosted_data_store_id]) if options[:hosted_data_store_id]
xml.tag!('v1:DeclineHostedDataDuplicates', options[:decline_hosted_data_duplicates]) if options[:decline_hosted_data_duplicates]
xml.tag!('v1:SubTotal', options[:sub_total]) if options[:sub_total]
xml.tag!('v1:ValueAddedTax', options[:value_added_tax]) if options[:value_added_tax]
xml.tag!('v1:DeliveryAmount', options[:delivery_amount]) if options[:delivery_amount]
xml.tag!('v1:ChargeTotal', money)
xml.tag!('v1:Currency', CURRENCY_CODES[options[:currency]])
xml.tag!('v1:numberOfInstallments', options[:number_of_installments]) if options[:number_of_installments]
end
end
def add_billing(xml, billing)
xml.tag!('v1:Billing') do
xml.tag!('v1:CustomerID', billing[:customer_id]) if billing[:customer_id]
xml.tag!('v1:Name', billing[:name]) if billing[:name]
xml.tag!('v1:Company', billing[:company]) if billing[:company]
xml.tag!('v1:Address1', billing[:address_1]) if billing[:address_1]
xml.tag!('v1:Address2', billing[:address_2]) if billing[:address_2]
xml.tag!('v1:City', billing[:city]) if billing[:city]
xml.tag!('v1:State', billing[:state]) if billing[:state]
xml.tag!('v1:Zip', billing[:zip]) if billing[:zip]
xml.tag!('v1:Country', billing[:country]) if billing[:country]
xml.tag!('v1:Phone', billing[:phone]) if billing[:phone]
xml.tag!('v1:Fax', billing[:fax]) if billing[:fax]
xml.tag!('v1:Email', billing[:email]) if billing[:email]
end
end
def add_shipping(xml, shipping)
xml.tag!('v1:Shipping') do
xml.tag!('v1:Type', shipping[:type]) if shipping[:type]
xml.tag!('v1:Name', shipping[:name]) if shipping[:name]
xml.tag!('v1:Address1', shipping[:address_1]) if shipping[:address_1]
xml.tag!('v1:Address2', shipping[:address_2]) if shipping[:address_2]
xml.tag!('v1:City', shipping[:city]) if shipping[:city]
xml.tag!('v1:State', shipping[:state]) if shipping[:state]
xml.tag!('v1:Zip', shipping[:zip]) if shipping[:zip]
xml.tag!('v1:Country', shipping[:country]) if shipping[:country]
end
end
def build_header
{
'Content-Type' => 'text/xml; charset=utf-8',
'Authorization' => "Basic #{encoded_credentials}"
}
end
def encoded_credentials
Base64.encode64("WS#{@credentials[:store_id]}._.#{@credentials[:user_id]}:#{@credentials[:password]}").delete("\n")
end
def envelope_namespaces
{
'xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:ipg' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi",
'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1"
}
end
def ipg_order_namespaces
{
'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1",
'xmlns:ipgapi' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi"
}
end
def ipg_action_namespaces
{
'xmlns:ns4' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi",
'xmlns:ns2' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/a1",
'xmlns:ns3' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1"
}
end
def override_store_id(options)
@credentials[:store_id] = options[:store_id] if options[:store_id].present?
end
def commit(action, request, options = {})
override_store_id(options)
url = (test? ? test_url : live_url)
soap_request = build_soap_request(action, request)
response = parse(ssl_post(url, soap_request, build_header))
Response.new(
response[:success],
message_from(response),
response,
authorization: authorization_from(action, response),
avs_result: AVSResult.new(code: response[:AVSResponse]),
cvv_result: CVVResult.new(response[:ProcessorCCVResponse]),
test: test?,
error_code: error_code_from(response)
)
end
def parse(xml)
reply = {}
xml = REXML::Document.new(xml)
root = REXML::XPath.first(xml, '//ipgapi:IPGApiOrderResponse') || REXML::XPath.first(xml, '//ipgapi:IPGApiActionResponse') || REXML::XPath.first(xml, '//SOAP-ENV:Fault') || REXML::XPath.first(xml, '//ns4:IPGApiActionResponse')
reply[:success] = REXML::XPath.first(xml, '//faultcode') ? false : true
if REXML::XPath.first(xml, '//ns4:IPGApiActionResponse')
reply[:tpv_error_code] = REXML::XPath.first(root, '//ns2:Error').attributes['Code']
reply[:tpv_error_msg] = REXML::XPath.first(root, '//ns2:ErrorMessage').text
reply[:success] = false
end
root.elements.to_a.each do |node|
parse_element(reply, node)
end
reply[:hosted_data_id] = @hosted_data_id if @hosted_data_id
return reply
end
def parse_element(reply, node)
if node.has_elements?
node.elements.each { |e| parse_element(reply, e) }
else
if /item/.match?(node.parent.name)
parent = node.parent.name
parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
parent += '_'
end
reply["#{parent}#{node.name}".to_sym] ||= node.text
end
return reply
end
def message_from(response)
[response[:TransactionResult], response[:ErrorMessage]&.split(':')&.last&.strip].compact.join(', ')
end
def authorization_from(action, response)
return (action == 'vault' ? response[:hosted_data_id] : response[:OrderId])
end
def error_code_from(response)
response[:ErrorMessage]&.split(':')&.first unless response[:success]
end
def handle_response(response)
case response.code.to_i
when 200...300, 500
response.body
else
raise ResponseError.new(response)
end
end
end
end
end